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