summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReinhard Tartler <siretart@tauware.de>2011-10-27 19:53:48 -0400
committerReinhard Tartler <siretart@tauware.de>2011-10-27 19:53:48 -0400
commit757294d769a7defe6a531a09fba9674cc6b388f7 (patch)
treeca47acead4e3676acd942de5f443d7bdc5fdd90c
Import boxbackup_0.11.1~r2837.orig.tar.gz
[dgit import orig boxbackup_0.11.1~r2837.orig.tar.gz]
-rw-r--r--.hgignore37
-rw-r--r--.svnrevision1
-rw-r--r--BUGS.txt15
-rw-r--r--COPYING.txt491
-rw-r--r--LICENSE-DUAL.txt59
-rw-r--r--LICENSE-GPL.txt41
-rw-r--r--VERSION.txt2
-rw-r--r--bin/bbackupctl/bbackupctl.cpp366
-rw-r--r--bin/bbackupd/BackupClientContext.cpp578
-rw-r--r--bin/bbackupd/BackupClientContext.h237
-rw-r--r--bin/bbackupd/BackupClientDeleteList.cpp229
-rw-r--r--bin/bbackupd/BackupClientDeleteList.h75
-rw-r--r--bin/bbackupd/BackupClientDirectoryRecord.cpp1876
-rw-r--r--bin/bbackupd/BackupClientDirectoryRecord.h171
-rw-r--r--bin/bbackupd/BackupClientInodeToIDMap.cpp327
-rw-r--r--bin/bbackupd/BackupClientInodeToIDMap.h73
-rw-r--r--bin/bbackupd/BackupDaemon.cpp2883
-rw-r--r--bin/bbackupd/BackupDaemon.h525
-rw-r--r--bin/bbackupd/BackupDaemonInterface.h167
-rw-r--r--bin/bbackupd/ClientException.txt11
-rw-r--r--bin/bbackupd/Makefile.extra7
-rw-r--r--bin/bbackupd/Win32BackupService.cpp48
-rw-r--r--bin/bbackupd/Win32BackupService.h21
-rw-r--r--bin/bbackupd/Win32ServiceFunctions.cpp384
-rw-r--r--bin/bbackupd/Win32ServiceFunctions.h19
-rwxr-xr-xbin/bbackupd/bbackupd-config.in601
-rw-r--r--bin/bbackupd/bbackupd.cpp56
-rw-r--r--bin/bbackupd/win32/NotifySysAdmin.vbs113
-rw-r--r--bin/bbackupd/win32/bbackupd.conf238
-rw-r--r--bin/bbackupd/win32/installer.iss51
-rw-r--r--bin/bbackupobjdump/bbackupobjdump.cpp83
-rw-r--r--bin/bbackupquery/BackupQueries.cpp2340
-rw-r--r--bin/bbackupquery/BackupQueries.h346
-rw-r--r--bin/bbackupquery/BoxBackupCompareParams.h107
-rw-r--r--bin/bbackupquery/Makefile.extra6
-rw-r--r--bin/bbackupquery/bbackupquery.cpp441
-rw-r--r--bin/bbackupquery/documentation.txt187
-rwxr-xr-xbin/bbackupquery/makedocumentation.pl.in75
-rw-r--r--bin/bbstoreaccounts/bbstoreaccounts.cpp626
-rw-r--r--bin/bbstored/BBStoreDHousekeeping.cpp240
-rw-r--r--bin/bbstored/BackupCommands.cpp970
-rw-r--r--bin/bbstored/BackupConstants.h21
-rw-r--r--bin/bbstored/BackupStoreContext.cpp1785
-rw-r--r--bin/bbstored/BackupStoreContext.h186
-rw-r--r--bin/bbstored/BackupStoreDaemon.cpp371
-rw-r--r--bin/bbstored/BackupStoreDaemon.h100
-rw-r--r--bin/bbstored/HousekeepStoreAccount.cpp1067
-rw-r--r--bin/bbstored/HousekeepStoreAccount.h111
-rw-r--r--bin/bbstored/Makefile.extra9
-rw-r--r--bin/bbstored/backupprotocol.txt234
-rwxr-xr-xbin/bbstored/bbstored-certs.in319
-rwxr-xr-xbin/bbstored/bbstored-config.in245
-rw-r--r--bin/bbstored/bbstored.cpp37
-rw-r--r--bin/s3simulator/s3simulator.cpp32
-rwxr-xr-xbootstrap5
-rwxr-xr-xcleanupforcvs.pl196
-rwxr-xr-xconfig.guess1456
-rwxr-xr-xconfig.sub1549
-rw-r--r--configure.ac420
-rwxr-xr-xcontrib/bbadmin/accounts.cgi580
-rw-r--r--contrib/bbadmin/apache.conf14
-rw-r--r--contrib/bbadmin/bb.css70
-rw-r--r--contrib/bbreporter/LICENSE674
-rwxr-xr-xcontrib/bbreporter/bbreporter.py538
-rw-r--r--contrib/debian/README.txt9
-rw-r--r--contrib/debian/bbackupd.in59
-rw-r--r--contrib/debian/bbstored.in59
-rw-r--r--contrib/mac_osx/org.boxbackup.bbackupd.plist.in20
-rw-r--r--contrib/mac_osx/org.boxbackup.bbstored.plist.in21
-rw-r--r--contrib/redhat/README.txt7
-rw-r--r--contrib/redhat/bbackupd.in83
-rw-r--r--contrib/redhat/bbstored.in83
-rw-r--r--contrib/rpm/README.txt16
-rw-r--r--contrib/rpm/boxbackup.spec244
-rw-r--r--contrib/solaris/bbackupd-manifest.xml.in59
-rwxr-xr-xcontrib/solaris/bbackupd-smf-method.in24
-rw-r--r--contrib/solaris/bbstored-manifest.xml.in60
-rwxr-xr-xcontrib/solaris/bbstored-smf-method.in23
-rw-r--r--contrib/suse/README.txt5
-rw-r--r--contrib/suse/bbackupd.in103
-rw-r--r--contrib/suse/bbstored.in104
-rwxr-xr-xcontrib/windows/installer/boxbackup.mpi.in3392
-rwxr-xr-xcontrib/windows/installer/tools/InstallService.bat3
-rwxr-xr-xcontrib/windows/installer/tools/KillBackupProcess.bat3
-rwxr-xr-xcontrib/windows/installer/tools/QueryOutputAll.bat5
-rwxr-xr-xcontrib/windows/installer/tools/QueryOutputCurrent.bat5
-rwxr-xr-xcontrib/windows/installer/tools/ReloadConfig.bat3
-rwxr-xr-xcontrib/windows/installer/tools/RemoteControl.exebin0 -> 156536 bytes
-rwxr-xr-xcontrib/windows/installer/tools/RemoveService.bat3
-rwxr-xr-xcontrib/windows/installer/tools/RestartService.bat5
-rwxr-xr-xcontrib/windows/installer/tools/ShowUsage.bat3
-rwxr-xr-xcontrib/windows/installer/tools/StartService.bat3
-rwxr-xr-xcontrib/windows/installer/tools/StopService.bat3
-rwxr-xr-xcontrib/windows/installer/tools/Sync.bat3
-rw-r--r--distribution/COMMON-MANIFEST.txt47
-rw-r--r--distribution/boxbackup/CONTACT.txt6
-rw-r--r--distribution/boxbackup/DISTRIBUTION-MANIFEST.txt95
-rw-r--r--distribution/boxbackup/DOCUMENTATION.txt6
-rw-r--r--distribution/boxbackup/LINUX.txt29
-rw-r--r--distribution/boxbackup/NETBSD.txt8
-rw-r--r--distribution/boxbackup/THANKS.txt69
-rw-r--r--distribution/boxbackup/VERSION.txt2
-rw-r--r--docs/Makefile183
-rw-r--r--docs/api-notes/INDEX.txt61
-rw-r--r--docs/api-notes/Win32_Clients.txt13
-rw-r--r--docs/api-notes/backup_encryption.txt109
-rw-r--r--docs/api-notes/bin_bbackupd.txt88
-rw-r--r--docs/api-notes/bin_bbstored.txt54
-rw-r--r--docs/api-notes/common/lib_common.txt52
-rw-r--r--docs/api-notes/common/lib_common/BoxTime.txt7
-rw-r--r--docs/api-notes/common/lib_common/CollectInBufferStream.txt26
-rw-r--r--docs/api-notes/common/lib_common/Configuration.txt102
-rw-r--r--docs/api-notes/common/lib_common/Conversion.txt14
-rw-r--r--docs/api-notes/common/lib_common/ExcludeList.txt21
-rw-r--r--docs/api-notes/common/lib_common/FdGetLine.txt11
-rw-r--r--docs/api-notes/common/lib_common/Guards.txt5
-rw-r--r--docs/api-notes/common/lib_common/IOStream.txt89
-rw-r--r--docs/api-notes/common/lib_common/IOStreamGetLine.txt29
-rw-r--r--docs/api-notes/common/lib_common/MainHelper.txt4
-rw-r--r--docs/api-notes/common/lib_common/WaitForEvent.txt16
-rw-r--r--docs/api-notes/common/lib_common/xStream.txt40
-rw-r--r--docs/api-notes/common/lib_compress.txt8
-rw-r--r--docs/api-notes/common/lib_compress/CompressStream.txt27
-rw-r--r--docs/api-notes/common/lib_crypto.txt28
-rw-r--r--docs/api-notes/common/lib_crypto/CipherContext.txt28
-rw-r--r--docs/api-notes/common/lib_crypto/RollingChecksum.txt36
-rw-r--r--docs/api-notes/common/lib_server.txt9
-rw-r--r--docs/api-notes/common/lib_server/Daemon.txt96
-rw-r--r--docs/api-notes/common/lib_server/Protocol.txt120
-rw-r--r--docs/api-notes/common/lib_server/ServerStream.txt29
-rw-r--r--docs/api-notes/common/lib_server/ServerTLS.txt6
-rw-r--r--docs/api-notes/common/lib_server/SocketStream.txt8
-rw-r--r--docs/api-notes/common/lib_server/SocketStreamTLS.txt11
-rw-r--r--docs/api-notes/common/lib_server/TLSContext.txt16
-rw-r--r--docs/api-notes/common/memory_leaks.txt44
-rw-r--r--docs/api-notes/encrypt_rsync.txt66
-rw-r--r--docs/api-notes/lib_backupclient.txt46
-rw-r--r--docs/api-notes/lib_backupstore.txt30
-rw-r--r--docs/api-notes/raidfile/RaidFileRead.txt14
-rw-r--r--docs/api-notes/raidfile/RaidFileWrite.txt36
-rw-r--r--docs/api-notes/raidfile/lib_raidfile.txt73
-rw-r--r--docs/api-notes/win32_build_on_cygwin_using_mingw.txt113
-rw-r--r--docs/api-notes/win32_build_on_linux_using_mingw.txt108
-rw-r--r--docs/api-notes/windows_porting.txt100
-rw-r--r--docs/docbook/adminguide.xml1981
-rw-r--r--docs/docbook/bb-book.xsl17
-rw-r--r--docs/docbook/bb-man.xsl9
-rw-r--r--docs/docbook/bb-nochunk-book.xsl17
-rw-r--r--docs/docbook/bbackupctl.xml205
-rw-r--r--docs/docbook/bbackupd-config.xml153
-rw-r--r--docs/docbook/bbackupd.conf.xml479
-rw-r--r--docs/docbook/bbackupd.xml209
-rw-r--r--docs/docbook/bbackupquery.xml506
-rw-r--r--docs/docbook/bbstoreaccounts.xml386
-rw-r--r--docs/docbook/bbstored-certs.xml180
-rw-r--r--docs/docbook/bbstored-config.xml148
-rw-r--r--docs/docbook/bbstored.conf.xml211
-rw-r--r--docs/docbook/bbstored.xml90
-rw-r--r--docs/docbook/html/bbdoc-man.css104
-rw-r--r--docs/docbook/html/bbdoc.css112
-rw-r--r--docs/docbook/html/favicon.icobin0 -> 67646 bytes
-rw-r--r--docs/docbook/html/images/arrow.pngbin0 -> 197 bytes
-rw-r--r--docs/docbook/html/images/bblogo.pngbin0 -> 5882 bytes
-rw-r--r--docs/docbook/html/images/stepahead.pngbin0 -> 298 bytes
-rw-r--r--docs/docbook/instguide.xml766
-rw-r--r--docs/docbook/raidfile-config.xml198
-rw-r--r--docs/docbook/raidfile.conf.xml143
-rw-r--r--docs/images/bblogo-alpha.xcfbin0 -> 93980 bytes
-rw-r--r--docs/tools/generate_except_xml.pl74
-rw-r--r--docs/xsl-generic/VERSION113
-rw-r--r--docs/xsl-generic/common/af.xml1223
-rw-r--r--docs/xsl-generic/common/am.xml1223
-rw-r--r--docs/xsl-generic/common/ar.xml1223
-rw-r--r--docs/xsl-generic/common/autoidx-kimber.xsl43
-rw-r--r--docs/xsl-generic/common/autoidx-kosek.xsl150
-rw-r--r--docs/xsl-generic/common/az.xml666
-rw-r--r--docs/xsl-generic/common/bg.xml718
-rw-r--r--docs/xsl-generic/common/bn.xml1223
-rw-r--r--docs/xsl-generic/common/bs.xml656
-rw-r--r--docs/xsl-generic/common/ca.xml1223
-rw-r--r--docs/xsl-generic/common/charmap.xml185
-rw-r--r--docs/xsl-generic/common/charmap.xsl221
-rw-r--r--docs/xsl-generic/common/common.xml622
-rw-r--r--docs/xsl-generic/common/common.xsl1981
-rw-r--r--docs/xsl-generic/common/cs.xml694
-rw-r--r--docs/xsl-generic/common/cy.xml1239
-rw-r--r--docs/xsl-generic/common/da.xml658
-rw-r--r--docs/xsl-generic/common/de.xml660
-rw-r--r--docs/xsl-generic/common/el.xml1223
-rw-r--r--docs/xsl-generic/common/en.xml1223
-rw-r--r--docs/xsl-generic/common/entities.ent47
-rw-r--r--docs/xsl-generic/common/eo.xml1223
-rw-r--r--docs/xsl-generic/common/es.xml670
-rw-r--r--docs/xsl-generic/common/et.xml1223
-rw-r--r--docs/xsl-generic/common/eu.xml1223
-rw-r--r--docs/xsl-generic/common/fa.xml1223
-rw-r--r--docs/xsl-generic/common/fi.xml664
-rw-r--r--docs/xsl-generic/common/fr.xml684
-rw-r--r--docs/xsl-generic/common/ga.xml1223
-rw-r--r--docs/xsl-generic/common/gentext.xsl831
-rw-r--r--docs/xsl-generic/common/gu.xml1223
-rw-r--r--docs/xsl-generic/common/he.xml1223
-rw-r--r--docs/xsl-generic/common/hi.xml1223
-rw-r--r--docs/xsl-generic/common/hr.xml1223
-rw-r--r--docs/xsl-generic/common/hu.xml1223
-rw-r--r--docs/xsl-generic/common/id.xml1223
-rw-r--r--docs/xsl-generic/common/insertfile.xsl111
-rw-r--r--docs/xsl-generic/common/it.xml1223
-rw-r--r--docs/xsl-generic/common/ja.xml1223
-rw-r--r--docs/xsl-generic/common/kn.xml1223
-rw-r--r--docs/xsl-generic/common/ko.xml1223
-rw-r--r--docs/xsl-generic/common/l10n.dtd63
-rw-r--r--docs/xsl-generic/common/l10n.xml127
-rw-r--r--docs/xsl-generic/common/l10n.xsl441
-rw-r--r--docs/xsl-generic/common/la.xml1223
-rw-r--r--docs/xsl-generic/common/labels.xsl869
-rw-r--r--docs/xsl-generic/common/lt.xml672
-rw-r--r--docs/xsl-generic/common/lv.xml1223
-rw-r--r--docs/xsl-generic/common/mn.xml724
-rw-r--r--docs/xsl-generic/common/nl.xml1223
-rw-r--r--docs/xsl-generic/common/nn.xml1223
-rw-r--r--docs/xsl-generic/common/no.xml1223
-rw-r--r--docs/xsl-generic/common/olink.xsl1149
-rw-r--r--docs/xsl-generic/common/or.xml1223
-rw-r--r--docs/xsl-generic/common/pa.xml1223
-rw-r--r--docs/xsl-generic/common/pi.xsl346
-rw-r--r--docs/xsl-generic/common/pl.xml1223
-rw-r--r--docs/xsl-generic/common/pt.xml1223
-rw-r--r--docs/xsl-generic/common/pt_br.xml1223
-rw-r--r--docs/xsl-generic/common/refentry.xml781
-rw-r--r--docs/xsl-generic/common/refentry.xsl1277
-rw-r--r--docs/xsl-generic/common/ro.xml1223
-rw-r--r--docs/xsl-generic/common/ru.xml720
-rw-r--r--docs/xsl-generic/common/sk.xml1223
-rw-r--r--docs/xsl-generic/common/sl.xml1223
-rw-r--r--docs/xsl-generic/common/sq.xml1223
-rw-r--r--docs/xsl-generic/common/sr.xml714
-rw-r--r--docs/xsl-generic/common/sr_Latn.xml673
-rw-r--r--docs/xsl-generic/common/stripns.xsl342
-rw-r--r--docs/xsl-generic/common/subtitles.xsl155
-rw-r--r--docs/xsl-generic/common/sv.xml658
-rw-r--r--docs/xsl-generic/common/ta.xml1223
-rw-r--r--docs/xsl-generic/common/table.xsl514
-rw-r--r--docs/xsl-generic/common/targetdatabase.dtd49
-rw-r--r--docs/xsl-generic/common/targets.xsl272
-rw-r--r--docs/xsl-generic/common/th.xml1223
-rw-r--r--docs/xsl-generic/common/titles.xsl740
-rw-r--r--docs/xsl-generic/common/tl.xml1223
-rw-r--r--docs/xsl-generic/common/tr.xml660
-rw-r--r--docs/xsl-generic/common/uk.xml1223
-rw-r--r--docs/xsl-generic/common/utility.xml259
-rw-r--r--docs/xsl-generic/common/utility.xsl290
-rw-r--r--docs/xsl-generic/common/vi.xml1223
-rw-r--r--docs/xsl-generic/common/xh.xml1223
-rw-r--r--docs/xsl-generic/common/zh_cn.xml654
-rw-r--r--docs/xsl-generic/common/zh_tw.xml1223
-rw-r--r--docs/xsl-generic/highlighting/c-hl.xml105
-rw-r--r--docs/xsl-generic/highlighting/common.xsl62
-rw-r--r--docs/xsl-generic/highlighting/delphi-hl.xml174
-rw-r--r--docs/xsl-generic/highlighting/ini-hl.xml43
-rw-r--r--docs/xsl-generic/highlighting/java-hl.xml98
-rw-r--r--docs/xsl-generic/highlighting/m2-hl.xml86
-rw-r--r--docs/xsl-generic/highlighting/myxml-hl.xml131
-rw-r--r--docs/xsl-generic/highlighting/php-hl.xml127
-rw-r--r--docs/xsl-generic/highlighting/xslthl-config.xml11
-rw-r--r--docs/xsl-generic/html/admon.xsl132
-rw-r--r--docs/xsl-generic/html/annotations.xsl169
-rw-r--r--docs/xsl-generic/html/autoidx-kimber.xsl168
-rw-r--r--docs/xsl-generic/html/autoidx-kosek.xsl125
-rw-r--r--docs/xsl-generic/html/autoidx-ng.xsl20
-rw-r--r--docs/xsl-generic/html/autoidx.xsl645
-rw-r--r--docs/xsl-generic/html/autotoc.xsl675
-rw-r--r--docs/xsl-generic/html/biblio-iso690.xsl1300
-rw-r--r--docs/xsl-generic/html/biblio.xsl1228
-rw-r--r--docs/xsl-generic/html/block.xsl434
-rw-r--r--docs/xsl-generic/html/callout.xsl201
-rw-r--r--docs/xsl-generic/html/changebars.xsl100
-rw-r--r--docs/xsl-generic/html/chunk-code.xsl670
-rw-r--r--docs/xsl-generic/html/chunk-common.xsl1886
-rw-r--r--docs/xsl-generic/html/chunk.xsl52
-rw-r--r--docs/xsl-generic/html/chunker.xsl439
-rw-r--r--docs/xsl-generic/html/chunkfast.xsl72
-rw-r--r--docs/xsl-generic/html/chunktoc.xsl468
-rw-r--r--docs/xsl-generic/html/component.xsl401
-rw-r--r--docs/xsl-generic/html/division.xsl228
-rw-r--r--docs/xsl-generic/html/docbook.xsl479
-rw-r--r--docs/xsl-generic/html/ebnf.xsl329
-rw-r--r--docs/xsl-generic/html/footnote.xsl299
-rw-r--r--docs/xsl-generic/html/formal.xsl400
-rw-r--r--docs/xsl-generic/html/glossary.xsl482
-rw-r--r--docs/xsl-generic/html/graphics.xsl1489
-rw-r--r--docs/xsl-generic/html/highlight.xsl54
-rw-r--r--docs/xsl-generic/html/html-rtf.xsl336
-rw-r--r--docs/xsl-generic/html/html.xsl241
-rw-r--r--docs/xsl-generic/html/htmltbl.xsl55
-rw-r--r--docs/xsl-generic/html/index.xsl229
-rw-r--r--docs/xsl-generic/html/info.xsl43
-rw-r--r--docs/xsl-generic/html/inline.xsl1439
-rw-r--r--docs/xsl-generic/html/keywords.xsl35
-rw-r--r--docs/xsl-generic/html/lists.xsl1103
-rw-r--r--docs/xsl-generic/html/maketoc.xsl86
-rw-r--r--docs/xsl-generic/html/manifest.xsl22
-rw-r--r--docs/xsl-generic/html/math.xsl270
-rw-r--r--docs/xsl-generic/html/oldchunker.xsl202
-rw-r--r--docs/xsl-generic/html/onechunk.xsl37
-rw-r--r--docs/xsl-generic/html/param.xsl412
-rw-r--r--docs/xsl-generic/html/pi.xsl1240
-rw-r--r--docs/xsl-generic/html/profile-chunk-code.xsl609
-rw-r--r--docs/xsl-generic/html/profile-chunk.xsl52
-rw-r--r--docs/xsl-generic/html/profile-docbook.xsl411
-rw-r--r--docs/xsl-generic/html/profile-onechunk.xsl37
-rw-r--r--docs/xsl-generic/html/qandaset.xsl389
-rw-r--r--docs/xsl-generic/html/refentry.xsl309
-rw-r--r--docs/xsl-generic/html/sections.xsl622
-rw-r--r--docs/xsl-generic/html/synop.xsl1596
-rw-r--r--docs/xsl-generic/html/table.xsl1120
-rw-r--r--docs/xsl-generic/html/task.xsl76
-rw-r--r--docs/xsl-generic/html/titlepage.templates.xml662
-rw-r--r--docs/xsl-generic/html/titlepage.templates.xsl3622
-rw-r--r--docs/xsl-generic/html/titlepage.xsl1031
-rw-r--r--docs/xsl-generic/html/toc.xsl173
-rw-r--r--docs/xsl-generic/html/verbatim.xsl376
-rw-r--r--docs/xsl-generic/html/xref.xsl1348
-rw-r--r--docs/xsl-generic/lib/lib.xsl480
-rw-r--r--docs/xsl-generic/manpages/block.xsl296
-rw-r--r--docs/xsl-generic/manpages/charmap.groff.xsl5985
-rw-r--r--docs/xsl-generic/manpages/docbook.xsl293
-rw-r--r--docs/xsl-generic/manpages/endnotes.xsl535
-rw-r--r--docs/xsl-generic/manpages/html-synop.xsl1605
-rw-r--r--docs/xsl-generic/manpages/info.xsl630
-rw-r--r--docs/xsl-generic/manpages/inline.xsl190
-rw-r--r--docs/xsl-generic/manpages/lists.xsl368
-rw-r--r--docs/xsl-generic/manpages/other.xsl674
-rw-r--r--docs/xsl-generic/manpages/param.xsl167
-rw-r--r--docs/xsl-generic/manpages/profile-docbook.xsl259
-rw-r--r--docs/xsl-generic/manpages/refentry.xsl256
-rw-r--r--docs/xsl-generic/manpages/synop.xsl305
-rw-r--r--docs/xsl-generic/manpages/table.xsl633
-rw-r--r--docs/xsl-generic/manpages/utility.xsl452
-rw-r--r--infrastructure/BoxPlatform.pm.in132
-rw-r--r--infrastructure/buildenv-testmain-template.cpp390
-rw-r--r--infrastructure/m4/ac_cxx_exceptions.m424
-rw-r--r--infrastructure/m4/ac_cxx_namespaces.m425
-rw-r--r--infrastructure/m4/ax_bswap64.m452
-rw-r--r--infrastructure/m4/ax_check_bdb_v1.m443
-rw-r--r--infrastructure/m4/ax_check_define_pragma.m425
-rw-r--r--infrastructure/m4/ax_check_dirent_d_type.m445
-rw-r--r--infrastructure/m4/ax_check_llong_minmax.m476
-rw-r--r--infrastructure/m4/ax_check_malloc_workaround.m438
-rw-r--r--infrastructure/m4/ax_check_mount_point.m460
-rw-r--r--infrastructure/m4/ax_check_nonaligned_access.m457
-rw-r--r--infrastructure/m4/ax_check_ssl.m437
-rw-r--r--infrastructure/m4/ax_check_syscall_lseek.m469
-rw-r--r--infrastructure/m4/ax_compare_version.m4162
-rw-r--r--infrastructure/m4/ax_config_scripts.m416
-rw-r--r--infrastructure/m4/ax_func_syscall.m450
-rw-r--r--infrastructure/m4/ax_path_bdb.m4615
-rw-r--r--infrastructure/m4/ax_random_device.m431
-rw-r--r--infrastructure/m4/ax_split_version.m419
-rw-r--r--infrastructure/m4/vl_lib_readline.m4135
-rwxr-xr-xinfrastructure/makebuildenv.pl.in973
-rwxr-xr-xinfrastructure/makedistribution.pl.in363
-rwxr-xr-xinfrastructure/makeparcels.pl.in396
-rwxr-xr-xinfrastructure/mingw/configure.sh38
-rw-r--r--infrastructure/msvc/2003/bbackupctl.vcproj159
-rw-r--r--infrastructure/msvc/2003/bbackupd.vcproj219
-rw-r--r--infrastructure/msvc/2003/boxbackup.sln57
-rw-r--r--infrastructure/msvc/2003/boxquery.vcproj174
-rw-r--r--infrastructure/msvc/2003/common.vcproj672
-rw-r--r--infrastructure/msvc/2003/win32test.vcproj148
-rw-r--r--infrastructure/msvc/2005/bbackupctl.vcproj222
-rw-r--r--infrastructure/msvc/2005/bbackupd.vcproj299
-rw-r--r--infrastructure/msvc/2005/boxbackup.sln55
-rw-r--r--infrastructure/msvc/2005/boxbackup.suobin0 -> 58880 bytes
-rw-r--r--infrastructure/msvc/2005/boxquery.vcproj246
-rw-r--r--infrastructure/msvc/2005/common.vcproj896
-rw-r--r--infrastructure/msvc/2005/win32test.vcproj218
-rw-r--r--infrastructure/msvc/getversion.pl19
-rw-r--r--infrastructure/parcelpath.pl17
-rw-r--r--infrastructure/printversion.pl12
-rwxr-xr-xinfrastructure/setupexternal.pl55
-rw-r--r--lib/backupclient/BackupClientCryptoKeys.cpp85
-rw-r--r--lib/backupclient/BackupClientCryptoKeys.h55
-rw-r--r--lib/backupclient/BackupClientFileAttributes.cpp1186
-rw-r--r--lib/backupclient/BackupClientFileAttributes.h78
-rw-r--r--lib/backupclient/BackupClientMakeExcludeList.cpp75
-rw-r--r--lib/backupclient/BackupClientMakeExcludeList.h48
-rw-r--r--lib/backupclient/BackupClientRestore.cpp918
-rw-r--r--lib/backupclient/BackupClientRestore.h36
-rw-r--r--lib/backupclient/BackupDaemonConfigVerify.cpp132
-rw-r--r--lib/backupclient/BackupDaemonConfigVerify.h18
-rw-r--r--lib/backupclient/BackupStoreConstants.h44
-rw-r--r--lib/backupclient/BackupStoreDirectory.cpp568
-rw-r--r--lib/backupclient/BackupStoreDirectory.h268
-rw-r--r--lib/backupclient/BackupStoreException.h17
-rw-r--r--lib/backupclient/BackupStoreException.txt71
-rw-r--r--lib/backupclient/BackupStoreFile.cpp1556
-rw-r--r--lib/backupclient/BackupStoreFile.h228
-rw-r--r--lib/backupclient/BackupStoreFileCmbDiff.cpp326
-rw-r--r--lib/backupclient/BackupStoreFileCmbIdx.cpp324
-rw-r--r--lib/backupclient/BackupStoreFileCombine.cpp410
-rw-r--r--lib/backupclient/BackupStoreFileCryptVar.cpp31
-rw-r--r--lib/backupclient/BackupStoreFileCryptVar.h39
-rw-r--r--lib/backupclient/BackupStoreFileDiff.cpp1046
-rw-r--r--lib/backupclient/BackupStoreFileEncodeStream.cpp715
-rw-r--r--lib/backupclient/BackupStoreFileEncodeStream.h135
-rw-r--r--lib/backupclient/BackupStoreFileRevDiff.cpp258
-rw-r--r--lib/backupclient/BackupStoreFileWire.h74
-rw-r--r--lib/backupclient/BackupStoreFilename.cpp281
-rw-r--r--lib/backupclient/BackupStoreFilename.h107
-rw-r--r--lib/backupclient/BackupStoreFilenameClear.cpp335
-rw-r--r--lib/backupclient/BackupStoreFilenameClear.h60
-rw-r--r--lib/backupclient/BackupStoreObjectDump.cpp227
-rw-r--r--lib/backupclient/BackupStoreObjectMagic.h31
-rw-r--r--lib/backupclient/Makefile.extra16
-rw-r--r--lib/backupclient/RunStatusProvider.h29
-rw-r--r--lib/backupstore/BackupStoreAccountDatabase.cpp373
-rw-r--r--lib/backupstore/BackupStoreAccountDatabase.h75
-rw-r--r--lib/backupstore/BackupStoreAccounts.cpp170
-rw-r--r--lib/backupstore/BackupStoreAccounts.h52
-rw-r--r--lib/backupstore/BackupStoreCheck.cpp776
-rw-r--r--lib/backupstore/BackupStoreCheck.h199
-rw-r--r--lib/backupstore/BackupStoreCheck2.cpp916
-rw-r--r--lib/backupstore/BackupStoreCheckData.cpp208
-rw-r--r--lib/backupstore/BackupStoreConfigVerify.cpp57
-rw-r--r--lib/backupstore/BackupStoreConfigVerify.h18
-rw-r--r--lib/backupstore/BackupStoreInfo.cpp593
-rw-r--r--lib/backupstore/BackupStoreInfo.h111
-rw-r--r--lib/backupstore/BackupStoreRefCountDatabase.cpp321
-rw-r--r--lib/backupstore/BackupStoreRefCountDatabase.h128
-rw-r--r--lib/backupstore/StoreStructure.cpp95
-rw-r--r--lib/backupstore/StoreStructure.h32
-rw-r--r--lib/common/Archive.h161
-rw-r--r--lib/common/BannerText.h18
-rw-r--r--lib/common/BeginStructPackForWire.h23
-rw-r--r--lib/common/Box.h185
-rw-r--r--lib/common/BoxConfig-MSVC.h402
-rw-r--r--lib/common/BoxException.cpp21
-rw-r--r--lib/common/BoxException.h38
-rw-r--r--lib/common/BoxPlatform.h201
-rw-r--r--lib/common/BoxPortsAndFiles.h.in44
-rw-r--r--lib/common/BoxTime.cpp96
-rw-r--r--lib/common/BoxTime.h46
-rw-r--r--lib/common/BoxTimeToText.cpp76
-rw-r--r--lib/common/BoxTimeToText.h19
-rw-r--r--lib/common/BoxTimeToUnix.h34
-rw-r--r--lib/common/BufferedStream.cpp207
-rw-r--r--lib/common/BufferedStream.h43
-rw-r--r--lib/common/BufferedWriteStream.cpp181
-rw-r--r--lib/common/BufferedWriteStream.h44
-rw-r--r--lib/common/CollectInBufferStream.cpp274
-rw-r--r--lib/common/CollectInBufferStream.h60
-rw-r--r--lib/common/CommonException.h17
-rw-r--r--lib/common/CommonException.txt47
-rw-r--r--lib/common/Configuration.cpp920
-rw-r--r--lib/common/Configuration.h147
-rw-r--r--lib/common/Conversion.h98
-rw-r--r--lib/common/ConversionException.txt8
-rw-r--r--lib/common/ConversionString.cpp129
-rw-r--r--lib/common/DebugAssertFailed.cpp37
-rw-r--r--lib/common/DebugMemLeakFinder.cpp552
-rw-r--r--lib/common/DebugPrintf.cpp83
-rw-r--r--lib/common/EndStructPackForWire.h23
-rw-r--r--lib/common/EventWatchFilesystemObject.cpp112
-rw-r--r--lib/common/EventWatchFilesystemObject.h48
-rw-r--r--lib/common/ExcludeList.cpp481
-rw-r--r--lib/common/ExcludeList.h76
-rw-r--r--lib/common/FdGetLine.cpp228
-rw-r--r--lib/common/FdGetLine.h65
-rw-r--r--lib/common/FileModificationTime.cpp64
-rw-r--r--lib/common/FileModificationTime.h22
-rw-r--r--lib/common/FileStream.cpp447
-rw-r--r--lib/common/FileStream.h66
-rw-r--r--lib/common/Guards.h121
-rw-r--r--lib/common/IOStream.cpp251
-rw-r--r--lib/common/IOStream.h73
-rw-r--r--lib/common/IOStreamGetLine.cpp227
-rw-r--r--lib/common/IOStreamGetLine.h71
-rw-r--r--lib/common/InvisibleTempFileStream.cpp39
-rw-r--r--lib/common/InvisibleTempFileStream.h35
-rw-r--r--lib/common/Logging.cpp518
-rw-r--r--lib/common/Logging.h346
-rw-r--r--lib/common/MainHelper.h43
-rw-r--r--lib/common/Makefile.extra11
-rw-r--r--lib/common/MemBlockStream.cpp235
-rw-r--r--lib/common/MemBlockStream.h52
-rw-r--r--lib/common/MemLeakFindOff.h27
-rw-r--r--lib/common/MemLeakFindOn.h25
-rw-r--r--lib/common/MemLeakFinder.h63
-rw-r--r--lib/common/NamedLock.cpp170
-rw-r--r--lib/common/NamedLock.h41
-rw-r--r--lib/common/PartialReadStream.cpp138
-rw-r--r--lib/common/PartialReadStream.h46
-rw-r--r--lib/common/PathUtils.cpp34
-rw-r--r--lib/common/PathUtils.h26
-rw-r--r--lib/common/ReadGatherStream.cpp263
-rw-r--r--lib/common/ReadGatherStream.h67
-rw-r--r--lib/common/ReadLoggingStream.cpp203
-rw-r--r--lib/common/ReadLoggingStream.h58
-rw-r--r--lib/common/SelfFlushingStream.h71
-rw-r--r--lib/common/StreamableMemBlock.cpp364
-rw-r--r--lib/common/StreamableMemBlock.h71
-rw-r--r--lib/common/TemporaryDirectory.h46
-rw-r--r--lib/common/Test.cpp486
-rw-r--r--lib/common/Test.h167
-rw-r--r--lib/common/Timer.cpp626
-rw-r--r--lib/common/Timer.h89
-rw-r--r--lib/common/UnixUser.cpp123
-rw-r--r--lib/common/UnixUser.h37
-rw-r--r--lib/common/Utils.cpp315
-rw-r--r--lib/common/Utils.h44
-rw-r--r--lib/common/WaitForEvent.cpp197
-rw-r--r--lib/common/WaitForEvent.h152
-rw-r--r--lib/common/ZeroStream.cpp170
-rw-r--r--lib/common/ZeroStream.h39
-rwxr-xr-xlib/common/makeexception.pl.in283
-rw-r--r--lib/compress/Compress.h197
-rw-r--r--lib/compress/CompressException.h17
-rw-r--r--lib/compress/CompressException.txt12
-rw-r--r--lib/compress/CompressStream.cpp425
-rw-r--r--lib/compress/CompressStream.h62
-rw-r--r--lib/compress/Makefile.extra7
-rw-r--r--lib/crypto/CipherAES.cpp163
-rw-r--r--lib/crypto/CipherAES.h50
-rw-r--r--lib/crypto/CipherBlowfish.cpp220
-rw-r--r--lib/crypto/CipherBlowfish.h60
-rw-r--r--lib/crypto/CipherContext.cpp620
-rw-r--r--lib/crypto/CipherContext.h83
-rw-r--r--lib/crypto/CipherDescription.cpp73
-rw-r--r--lib/crypto/CipherDescription.h59
-rw-r--r--lib/crypto/CipherException.h17
-rw-r--r--lib/crypto/CipherException.txt18
-rw-r--r--lib/crypto/MD5Digest.cpp82
-rw-r--r--lib/crypto/MD5Digest.h57
-rw-r--r--lib/crypto/Makefile.extra7
-rw-r--r--lib/crypto/Random.cpp128
-rw-r--r--lib/crypto/Random.h25
-rw-r--r--lib/crypto/RollingChecksum.cpp62
-rw-r--r--lib/crypto/RollingChecksum.h107
-rw-r--r--lib/httpserver/HTTPException.txt16
-rw-r--r--lib/httpserver/HTTPQueryDecoder.cpp159
-rw-r--r--lib/httpserver/HTTPQueryDecoder.h47
-rw-r--r--lib/httpserver/HTTPRequest.cpp780
-rw-r--r--lib/httpserver/HTTPRequest.h189
-rw-r--r--lib/httpserver/HTTPResponse.cpp648
-rw-r--r--lib/httpserver/HTTPResponse.h175
-rw-r--r--lib/httpserver/HTTPServer.cpp247
-rw-r--r--lib/httpserver/HTTPServer.h81
-rw-r--r--lib/httpserver/Makefile.extra7
-rw-r--r--lib/httpserver/S3Client.cpp243
-rw-r--r--lib/httpserver/S3Client.h72
-rw-r--r--lib/httpserver/S3Simulator.cpp309
-rw-r--r--lib/httpserver/S3Simulator.h40
-rw-r--r--lib/httpserver/cdecode.cpp92
-rw-r--r--lib/httpserver/cdecode.h28
-rw-r--r--lib/httpserver/cencode.cpp113
-rw-r--r--lib/httpserver/cencode.h32
-rw-r--r--lib/httpserver/decode.h77
-rw-r--r--lib/httpserver/encode.h87
-rw-r--r--lib/intercept/intercept.cpp673
-rw-r--r--lib/intercept/intercept.h54
-rw-r--r--lib/raidfile/Makefile.extra7
-rw-r--r--lib/raidfile/RaidFileController.cpp227
-rw-r--r--lib/raidfile/RaidFileController.h108
-rw-r--r--lib/raidfile/RaidFileException.h17
-rw-r--r--lib/raidfile/RaidFileException.txt28
-rw-r--r--lib/raidfile/RaidFileRead.cpp1724
-rw-r--r--lib/raidfile/RaidFileRead.h73
-rw-r--r--lib/raidfile/RaidFileUtil.cpp210
-rw-r--r--lib/raidfile/RaidFileUtil.h97
-rw-r--r--lib/raidfile/RaidFileWrite.cpp930
-rw-r--r--lib/raidfile/RaidFileWrite.h68
-rwxr-xr-xlib/raidfile/raidfile-config.in100
-rw-r--r--lib/server/ConnectionException.txt27
-rw-r--r--lib/server/Daemon.cpp1024
-rw-r--r--lib/server/Daemon.h112
-rw-r--r--lib/server/LocalProcessStream.cpp180
-rw-r--r--lib/server/LocalProcessStream.h20
-rw-r--r--lib/server/Makefile.extra11
-rw-r--r--lib/server/OverlappedIO.h42
-rw-r--r--lib/server/Protocol.cpp1160
-rw-r--r--lib/server/Protocol.h201
-rw-r--r--lib/server/ProtocolObject.cpp125
-rw-r--r--lib/server/ProtocolObject.h41
-rw-r--r--lib/server/ProtocolUncertainStream.cpp206
-rw-r--r--lib/server/ProtocolUncertainStream.h47
-rw-r--r--lib/server/ProtocolWire.h43
-rw-r--r--lib/server/SSLLib.cpp111
-rw-r--r--lib/server/SSLLib.h36
-rw-r--r--lib/server/ServerControl.cpp228
-rw-r--r--lib/server/ServerControl.h18
-rw-r--r--lib/server/ServerException.h46
-rw-r--r--lib/server/ServerException.txt39
-rw-r--r--lib/server/ServerStream.h418
-rw-r--r--lib/server/ServerTLS.h80
-rw-r--r--lib/server/Socket.cpp184
-rw-r--r--lib/server/Socket.h56
-rw-r--r--lib/server/SocketListen.h312
-rw-r--r--lib/server/SocketStream.cpp514
-rw-r--r--lib/server/SocketStream.h75
-rw-r--r--lib/server/SocketStreamTLS.cpp492
-rw-r--r--lib/server/SocketStreamTLS.h61
-rw-r--r--lib/server/TLSContext.cpp131
-rw-r--r--lib/server/TLSContext.h41
-rw-r--r--lib/server/WinNamedPipeListener.h232
-rw-r--r--lib/server/WinNamedPipeStream.cpp620
-rw-r--r--lib/server/WinNamedPipeStream.h67
-rwxr-xr-xlib/server/makeprotocol.pl.in1093
-rwxr-xr-xlib/win32/MSG00001.binbin0 -> 32 bytes
-rw-r--r--lib/win32/emu.cpp1843
-rw-r--r--lib/win32/emu.h424
-rwxr-xr-xlib/win32/getopt.h98
-rwxr-xr-xlib/win32/getopt_long.cpp550
-rwxr-xr-xlib/win32/messages.h57
-rw-r--r--lib/win32/messages.mc22
-rwxr-xr-xlib/win32/messages.rc2
-rw-r--r--modules.txt50
-rw-r--r--parcels.txt86
-rwxr-xr-xruntest.pl.in144
-rw-r--r--test/backupdiff/difftestfiles.cpp295
-rw-r--r--test/backupdiff/testbackupdiff.cpp605
-rw-r--r--test/backupdiff/testextra2
-rw-r--r--test/backupstore/Makefile.extra1
-rw-r--r--test/backupstore/testbackupstore.cpp2178
-rw-r--r--test/backupstore/testextra4
-rw-r--r--test/backupstore/testfiles/accounts.txt0
-rw-r--r--test/backupstore/testfiles/bbackupd.keysbin0 -> 1024 bytes
-rw-r--r--test/backupstore/testfiles/bbstored.conf17
-rw-r--r--test/backupstore/testfiles/bbstored_multi.conf16
-rw-r--r--test/backupstore/testfiles/clientCerts.pem11
-rw-r--r--test/backupstore/testfiles/clientPrivKey.pem15
-rw-r--r--test/backupstore/testfiles/clientReq.pem10
-rw-r--r--test/backupstore/testfiles/clientTrustedCAs.pem11
-rw-r--r--test/backupstore/testfiles/query.conf34
-rw-r--r--test/backupstore/testfiles/raidfile.conf10
-rw-r--r--test/backupstore/testfiles/root.pem26
-rw-r--r--test/backupstore/testfiles/root.srl1
-rw-r--r--test/backupstore/testfiles/rootcert.pem11
-rw-r--r--test/backupstore/testfiles/rootkey.pem15
-rw-r--r--test/backupstore/testfiles/rootreq.pem10
-rw-r--r--test/backupstore/testfiles/serverCerts.pem11
-rw-r--r--test/backupstore/testfiles/serverPrivKey.pem15
-rw-r--r--test/backupstore/testfiles/serverReq.pem10
-rw-r--r--test/backupstore/testfiles/serverTrustedCAs.pem11
-rw-r--r--test/backupstorefix/testbackupstorefix.cpp614
-rw-r--r--test/backupstorefix/testextra5
-rwxr-xr-xtest/backupstorefix/testfiles/testbackupstorefix.pl.in221
-rw-r--r--test/backupstorepatch/testbackupstorepatch.cpp670
-rw-r--r--test/backupstorepatch/testextra6
-rw-r--r--test/basicserver/Makefile.extra21
-rw-r--r--test/basicserver/TestCommands.cpp101
-rw-r--r--test/basicserver/TestContext.cpp16
-rw-r--r--test/basicserver/TestContext.h7
-rw-r--r--test/basicserver/testbasicserver.cpp772
-rw-r--r--test/basicserver/testfiles/clientCerts.pem14
-rw-r--r--test/basicserver/testfiles/clientPrivKey.pem15
-rw-r--r--test/basicserver/testfiles/clientReq.pem11
-rw-r--r--test/basicserver/testfiles/clientTrustedCAs.pem14
-rw-r--r--test/basicserver/testfiles/key-creation.txt83
-rw-r--r--test/basicserver/testfiles/root.pem29
-rw-r--r--test/basicserver/testfiles/root.srl1
-rw-r--r--test/basicserver/testfiles/rootcert.pem14
-rw-r--r--test/basicserver/testfiles/rootkey.pem15
-rw-r--r--test/basicserver/testfiles/rootreq.pem11
-rw-r--r--test/basicserver/testfiles/serverCerts.pem14
-rw-r--r--test/basicserver/testfiles/serverPrivKey.pem15
-rw-r--r--test/basicserver/testfiles/serverReq.pem11
-rw-r--r--test/basicserver/testfiles/serverTrustedCAs.pem14
-rw-r--r--test/basicserver/testfiles/srv1.conf6
-rw-r--r--test/basicserver/testfiles/srv1b.conf6
-rw-r--r--test/basicserver/testfiles/srv2.conf6
-rw-r--r--test/basicserver/testfiles/srv3.conf9
-rw-r--r--test/basicserver/testfiles/srv4.conf6
-rw-r--r--test/basicserver/testprotocol.txt42
-rw-r--r--test/bbackupd/Makefile.extra14
-rw-r--r--test/bbackupd/testbbackupd.cpp4077
-rw-r--r--test/bbackupd/testextra4
-rw-r--r--test/bbackupd/testfiles/accounts.txt0
-rw-r--r--test/bbackupd/testfiles/bbackupd-exclude.conf.in47
-rw-r--r--test/bbackupd/testfiles/bbackupd-snapshot.conf.in56
-rw-r--r--test/bbackupd/testfiles/bbackupd-symlink.conf.in55
-rw-r--r--test/bbackupd/testfiles/bbackupd-temploc.conf55
-rw-r--r--test/bbackupd/testfiles/bbackupd.conf.in55
-rw-r--r--test/bbackupd/testfiles/bbackupd.keysbin0 -> 1024 bytes
-rw-r--r--test/bbackupd/testfiles/bbstored.conf17
-rw-r--r--test/bbackupd/testfiles/clientCerts.pem11
-rw-r--r--test/bbackupd/testfiles/clientPrivKey.pem15
-rw-r--r--test/bbackupd/testfiles/clientTrustedCAs.pem11
-rwxr-xr-xtest/bbackupd/testfiles/extcheck1.pl.in58
-rwxr-xr-xtest/bbackupd/testfiles/extcheck2.pl.in50
-rwxr-xr-xtest/bbackupd/testfiles/notifyscript.pl.in24
-rw-r--r--test/bbackupd/testfiles/raidfile.conf10
-rw-r--r--test/bbackupd/testfiles/serverCerts.pem11
-rw-r--r--test/bbackupd/testfiles/serverPrivKey.pem15
-rw-r--r--test/bbackupd/testfiles/serverTrustedCAs.pem11
-rw-r--r--test/bbackupd/testfiles/spacetest1.tgzbin0 -> 288 bytes
-rw-r--r--test/bbackupd/testfiles/spacetest2.tgzbin0 -> 203 bytes
-rwxr-xr-xtest/bbackupd/testfiles/syncallowscript.pl.in33
-rw-r--r--test/bbackupd/testfiles/test2.tgzbin0 -> 25190 bytes
-rw-r--r--test/bbackupd/testfiles/test3.tgzbin0 -> 44957 bytes
-rw-r--r--test/bbackupd/testfiles/test_base.tgzbin0 -> 14950 bytes
-rw-r--r--test/bbackupd/testfiles/testexclude.tgzbin0 -> 377 bytes
-rw-r--r--test/common/testcommon.cpp882
-rw-r--r--test/common/testfiles/config1.txt40
-rw-r--r--test/common/testfiles/config10.txt37
-rw-r--r--test/common/testfiles/config11.txt39
-rw-r--r--test/common/testfiles/config12.txt33
-rw-r--r--test/common/testfiles/config13.txt15
-rw-r--r--test/common/testfiles/config14.txt41
-rw-r--r--test/common/testfiles/config15.txt45
-rw-r--r--test/common/testfiles/config16.txt42
-rw-r--r--test/common/testfiles/config2.txt39
-rw-r--r--test/common/testfiles/config3.txt39
-rw-r--r--test/common/testfiles/config4.txt40
-rw-r--r--test/common/testfiles/config5.txt37
-rw-r--r--test/common/testfiles/config6.txt39
-rw-r--r--test/common/testfiles/config7.txt39
-rw-r--r--test/common/testfiles/config8.txt37
-rw-r--r--test/common/testfiles/config9.txt38
-rw-r--r--test/common/testfiles/config9b.txt38
-rw-r--r--test/common/testfiles/config9c.txt38
-rw-r--r--test/common/testfiles/config9d.txt38
-rw-r--r--test/common/testfiles/fdgetlinetest.txt20
-rw-r--r--test/compress/testcompress.cpp261
-rw-r--r--test/crypto/testcrypto.cpp314
-rw-r--r--test/httpserver/testfiles/httpserver.conf8
-rw-r--r--test/httpserver/testfiles/photos/puppy.jpg1
-rw-r--r--test/httpserver/testfiles/s3simulator.conf10
-rwxr-xr-xtest/httpserver/testfiles/testrequests.pl143
-rw-r--r--test/httpserver/testhttpserver.cpp480
-rw-r--r--test/raidfile/testextra7
-rw-r--r--test/raidfile/testfiles/raidfile.conf30
-rw-r--r--test/raidfile/testraidfile.cpp981
-rw-r--r--test/win32/Makefile5
-rw-r--r--test/win32/testlibwin32.cpp345
-rw-r--r--test/win32/timezone.cpp87
-rw-r--r--win32.bat33
737 files changed, 230534 insertions, 0 deletions
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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+ <signature of Ty Coon>, 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 <cstdio>
+#include <cstdlib>
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include <cstdlib>
+
+#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] <command>\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<Configuration> 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", &currentState) != 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 <signal.h>
+#endif
+
+#ifdef HAVE_SYS_TIME_H
+ #include <sys/time.h>
+#endif
+
+#include "BoxPortsAndFiles.h"
+#include "BoxTime.h"
+#include "BackupClientContext.h"
+#include "SocketStreamTLS.h"
+#include "Socket.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreException.h"
+#include "BackupDaemon.h"
+#include "autogen_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<BackupProtocolClientVersion> 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<BackupProtocolClientLoginConfirmed> 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<BackupProtocolObjectCl> 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<IOStream> nameStream(connection.ReceiveStream());
+
+ // Path
+ std::string path;
+
+ // Remember this is in reverse order!
+ for(int l = 0; l < numElements; ++l)
+ {
+ BackupStoreFilenameClear elementName;
+ elementName.ReadFromStream(*nameStream, GetTimeout());
+
+ // Store leafname for caller?
+ if(l == 0 && pLeafname)
+ {
+ *pLeafname = elementName;
+ }
+
+ // Is it part of the filename in the location?
+ if(l < (numElements - 1))
+ {
+ // Part of filename within
+ path = (path.empty())?(elementName.GetClearFilename()):(elementName.GetClearFilename() + DIRECTORY_SEPARATOR_ASCHAR + path);
+ }
+ else
+ {
+ // Location name -- look up in daemon's records
+ std::string locPath;
+ if(!mrResolver.FindLocationPathName(elementName.GetClearFilename(), locPath))
+ {
+ // Didn't find the location... so can't give the local filename
+ return false;
+ }
+
+ // Add in location path
+ path = (path.empty())?(locPath):(locPath + DIRECTORY_SEPARATOR_ASCHAR + path);
+ }
+ }
+
+ // Is it a directory?
+ rIsDirectoryOut = ((names->GetFlags() & 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 <string>
+
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupClientContext
+// Purpose:
+// Created: 2003/10/08
+//
+// --------------------------------------------------------------------------
+class BackupClientContext : public DiffTimer
+{
+public:
+ BackupClientContext
+ (
+ LocationResolver &rResolver,
+ TLSContext &rTLSContext,
+ const std::string &rHostname,
+ int32_t Port,
+ uint32_t AccountNumber,
+ bool ExtendedLogging,
+ bool ExtendedLogToFile,
+ std::string ExtendedLogFile,
+ ProgressNotifier &rProgressNotifier
+ );
+ 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 <algorithm>
+
+#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<std::pair<int64_t, BackupStoreFilename> >::iterator
+ delEntry(mFileNoDeleteList.begin());
+ while(delEntry != mFileNoDeleteList.end())
+ {
+ if((delEntry)->first == DirectoryID
+ && (delEntry)->second == rFilename)
+ {
+ // Found!
+ break;
+ }
+ ++delEntry;
+ }
+
+ // Only add it to the delete list if it wasn't in the no delete list
+ if(delEntry == mFileNoDeleteList.end())
+ {
+ mFileList.push_back(FileToDelete(DirectoryID, rFilename,
+ rLocalPath));
+ }
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext)
+// Purpose: Perform all the pending deletes
+// Created: 10/11/03
+//
+// --------------------------------------------------------------------------
+void BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext)
+{
+ // Anything to do?
+ if(mDirectoryList.empty() && mFileList.empty())
+ {
+ // Nothing!
+ return;
+ }
+
+ // Get a connection
+ BackupProtocolClient &connection(rContext.GetConnection());
+
+ // Do the deletes
+ for(std::vector<DirToDelete>::iterator i(mDirectoryList.begin());
+ i != mDirectoryList.end(); ++i)
+ {
+ connection.QueryDeleteDirectory(i->mObjectID);
+ rContext.GetProgressNotifier().NotifyDirectoryDeleted(
+ i->mObjectID, i->mLocalPath);
+ }
+
+ // Clear the directory list
+ mDirectoryList.clear();
+
+ // Delete the files
+ for(std::vector<FileToDelete>::iterator i(mFileList.begin());
+ i != mFileList.end(); ++i)
+ {
+ connection.QueryDeleteFile(i->mDirectoryID, i->mFilename);
+ rContext.GetProgressNotifier().NotifyFileDeleted(
+ i->mDirectoryID, i->mLocalPath);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDeleteList::StopDirectoryDeletion(int64_t)
+// Purpose: Stop a directory being deleted
+// Created: 19/11/03
+//
+// --------------------------------------------------------------------------
+void BackupClientDeleteList::StopDirectoryDeletion(int64_t ObjectID)
+{
+ // First of all, is it in the delete vector?
+ std::vector<DirToDelete>::iterator delEntry(mDirectoryList.begin());
+ for(; delEntry != mDirectoryList.end(); delEntry++)
+ {
+ if(delEntry->mObjectID == ObjectID)
+ {
+ // Found!
+ break;
+ }
+ }
+ if(delEntry != mDirectoryList.end())
+ {
+ // erase this entry
+ mDirectoryList.erase(delEntry);
+ }
+ else
+ {
+ // Haven't been asked to delete it yet, put it in the
+ // no delete list
+ mDirectoryNoDeleteList.insert(ObjectID);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDeleteList::StopFileDeletion(int64_t, const BackupStoreFilename &)
+// Purpose: Stop a file from being deleted
+// Created: 19/11/03
+//
+// --------------------------------------------------------------------------
+void BackupClientDeleteList::StopFileDeletion(int64_t DirectoryID,
+ const BackupStoreFilename &rFilename)
+{
+ // Find this in the delete list
+ std::vector<FileToDelete>::iterator delEntry(mFileList.begin());
+ while(delEntry != mFileList.end())
+ {
+ if(delEntry->mDirectoryID == DirectoryID
+ && delEntry->mFilename == rFilename)
+ {
+ // Found!
+ break;
+ }
+ ++delEntry;
+ }
+
+ if(delEntry != mFileList.end())
+ {
+ // erase this entry
+ mFileList.erase(delEntry);
+ }
+ else
+ {
+ // Haven't been asked to delete it yet, put it in the no delete list
+ mFileNoDeleteList.push_back(std::pair<int64_t, BackupStoreFilename>(DirectoryID, rFilename));
+ }
+}
+
diff --git a/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 <vector>
+#include <utility>
+#include <set>
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupClientDeleteList
+// Purpose: List of pending deletes for backup
+// Created: 10/11/03
+//
+// --------------------------------------------------------------------------
+class BackupClientDeleteList
+{
+private:
+ class FileToDelete
+ {
+ public:
+ int64_t mDirectoryID;
+ BackupStoreFilename mFilename;
+ std::string mLocalPath;
+ FileToDelete(int64_t DirectoryID,
+ const BackupStoreFilename& rFilename,
+ const std::string& rLocalPath);
+ };
+
+ class DirToDelete
+ {
+ public:
+ int64_t mObjectID;
+ std::string mLocalPath;
+ DirToDelete(int64_t ObjectID, const std::string& rLocalPath);
+ };
+
+public:
+ BackupClientDeleteList();
+ ~BackupClientDeleteList();
+
+ void AddDirectoryDelete(int64_t ObjectID,
+ const std::string& rLocalPath);
+ void AddFileDelete(int64_t DirectoryID,
+ const BackupStoreFilename &rFilename,
+ const std::string& rLocalPath);
+
+ void StopDirectoryDeletion(int64_t ObjectID);
+ void StopFileDeletion(int64_t DirectoryID,
+ const BackupStoreFilename &rFilename);
+
+ void PerformDeletions(BackupClientContext &rContext);
+
+private:
+ std::vector<DirToDelete> mDirectoryList;
+ std::set<int64_t> mDirectoryNoDeleteList; // note: things only get in this list if they're not present in mDirectoryList when they are 'added'
+ std::vector<FileToDelete> mFileList;
+ std::vector<std::pair<int64_t, BackupStoreFilename> > mFileNoDeleteList;
+};
+
+#endif // BACKUPCLIENTDELETELIST__H
+
diff --git a/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 <dirent.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+
+#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<std::string, BackupStoreDirectory::Entry *> DecryptedEntriesMap_t;
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDirectoryRecord::BackupClientDirectoryRecord()
+// Purpose: Constructor
+// Created: 2003/10/08
+//
+// --------------------------------------------------------------------------
+BackupClientDirectoryRecord::BackupClientDirectoryRecord(int64_t ObjectID, const std::string &rSubDirName)
+ : mObjectID(ObjectID),
+ mSubDirName(rSubDirName),
+ mInitialSyncDone(false),
+ mSyncDone(false),
+ mpPendingEntries(0)
+{
+ ::memset(mStateChecksum, 0, sizeof(mStateChecksum));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDirectoryRecord::~BackupClientDirectoryRecord()
+// Purpose: Destructor
+// Created: 2003/10/08
+//
+// --------------------------------------------------------------------------
+BackupClientDirectoryRecord::~BackupClientDirectoryRecord()
+{
+ // Make deletion recursive
+ DeleteSubDirectories();
+
+ // Delete maps
+ if(mpPendingEntries != 0)
+ {
+ delete mpPendingEntries;
+ mpPendingEntries = 0;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDirectoryRecord::DeleteSubDirectories();
+// Purpose: Delete all sub directory entries
+// Created: 2003/10/09
+//
+// --------------------------------------------------------------------------
+void BackupClientDirectoryRecord::DeleteSubDirectories()
+{
+ // Delete all pointers
+ for(std::map<std::string, BackupClientDirectoryRecord *>::iterator i = mSubDirectories.begin();
+ i != mSubDirectories.end(); ++i)
+ {
+ delete i->second;
+ }
+
+ // Empty list
+ mSubDirectories.clear();
+}
+
+// --------------------------------------------------------------------------
+//
+// 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<std::string, BackupClientDirectoryRecord *>::iterator
+ i = mSubDirectories.begin();
+ i != mSubDirectories.end(); ++i)
+ {
+ i->second->mSyncDone = false;
+ }
+
+ // Work out the time in the future after which the file should
+ // be uploaded regardless. This is a simple way to avoid having
+ // too many problems with file servers when they have clients
+ // with badly out of sync clocks.
+ rParams.mUploadAfterThisTimeInTheFuture = GetCurrentBoxTime() +
+ rParams.mMaxFileTimeInFuture;
+
+ // Build the current state checksum to compare against while
+ // getting info from dirs. Note checksum is used locally only,
+ // so byte order isn't considered.
+ MD5Digest currentStateChecksum;
+
+ EMU_STRUCT_STAT dest_st;
+ // Stat the directory, to get attribute info
+ // If it's a symbolic link, we want the link target here
+ // (as we're about to back up the contents of the directory)
+ {
+ if(EMU_STAT(rLocalPath.c_str(), &dest_st) != 0)
+ {
+ // The directory has probably been deleted, so
+ // just ignore this error. In a future scan, this
+ // deletion will be noticed, deleted from server,
+ // and this object deleted.
+ rNotifier.NotifyDirStatFailed(this, 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<std::string> dirs;
+ std::vector<std::string> 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<BackupStoreDirectory::Entry *> 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<BackupProtocolClientSuccess> 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<IOStream> 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<BackupStoreDirectory::Entry *> &)
+// Purpose: Update the items stored on the server. The rFiles vector will be erased after it's used to save space.
+// Returns true if all items were updated successfully. (If not, the failures will have been logged).
+// Created: 2003/10/09
+//
+// --------------------------------------------------------------------------
+bool BackupClientDirectoryRecord::UpdateItems(
+ BackupClientDirectoryRecord::SyncParams &rParams,
+ const std::string &rLocalPath,
+ const std::string &rRemotePath,
+ BackupStoreDirectory *pDirOnStore,
+ std::vector<BackupStoreDirectory::Entry *> &rEntriesLeftOver,
+ std::vector<std::string> &rFiles,
+ const std::vector<std::string> &rDirs)
+{
+ BackupClientContext& rContext(rParams.mrContext);
+ ProgressNotifier& rNotifier(rContext.GetProgressNotifier());
+
+ bool allUpdatedSuccessfully = true;
+
+ // Decrypt all the directory entries.
+ // It would be nice to be able to just compare the encrypted versions, however this doesn't work
+ // in practise because there can be multiple encodings of the same filename using different
+ // methods (although each method will result in the same string for the same filename.) This
+ // happens when the server fixes a broken store, and gives plain text generated filenames.
+ // So if we didn't do things like this, then you wouldn't be able to recover from bad things
+ // happening with the server.
+ DecryptedEntriesMap_t decryptedEntries;
+ if(pDirOnStore != 0)
+ {
+ BackupStoreDirectory::Iterator i(*pDirOnStore);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ decryptedEntries[BackupStoreFilenameClear(en->GetName()).GetClearFilename()] = en;
+ }
+ }
+
+ // Do files
+ for(std::vector<std::string>::const_iterator f = rFiles.begin();
+ f != rFiles.end(); ++f)
+ {
+ // Send keep-alive message if needed
+ rContext.DoKeepAlive();
+
+ // Filename of this file
+ std::string filename(MakeFullPath(rLocalPath, *f));
+
+ // 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<std::string, box_time_t>::const_iterator i(mpPendingEntries->find(*f));
+ if(i != mpPendingEntries->end())
+ {
+ // found it -- set flag
+ pendingFirstSeenTime = i->second;
+ }
+ }
+
+ // If pDirOnStore == 0, then this must have been after an initial sync:
+ ASSERT(pDirOnStore != 0 || mInitialSyncDone);
+ // So, if pDirOnStore == 0, then we know that everything before syncPeriodStart
+ // is either on the server, or in the toupload list. If the directory had changed,
+ // we'd have got a directory listing.
+ //
+ // At this point, if (pDirOnStore == 0 && en == 0), we can assume it's on the server with a
+ // mod time < syncPeriodStart, or didn't exist before that time.
+ //
+ // But if en != 0, then we need to compare modification times to avoid uploading it again.
+
+ // Need to update?
+ //
+ // Condition for upload:
+ // modification time within sync period
+ // if it's been seen before but not uploaded, is the time from this first sight longer than the MaxUploadWait
+ // and if we know about it from a directory listing, that it hasn't got the same upload time as on the store
+
+ bool doUpload = false;
+
+ // 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<std::string, box_time_t>;
+ }
+ // Adding to mPendingEntries list
+ if(pendingFirstSeenTime == 0)
+ {
+ // Haven't seen this before -- add to list!
+ (*mpPendingEntries)[*f] = modTime;
+ }
+ }
+
+ // Zero pointer in rEntriesLeftOver, if we have a pointer to zero
+ if(en != 0)
+ {
+ for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l)
+ {
+ if(rEntriesLeftOver[l] == en)
+ {
+ rEntriesLeftOver[l] = 0;
+ break;
+ }
+ }
+ }
+
+ // Does this file need an entry in the ID map?
+ if(fileSize >= rParams.mFileTrackingSizeThreshold)
+ {
+ // Get the map
+ BackupClientInodeToIDMap &idMap(rContext.GetNewIDMap());
+
+ // Need to get an ID from somewhere...
+ if(latestObjectID != 0)
+ {
+ // 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 &currentIDMap(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<std::string>::const_iterator d = rDirs.begin();
+ d != rDirs.end(); ++d)
+ {
+ // Send keep-alive message if needed
+ rContext.DoKeepAlive();
+
+ // Get the local filename
+ std::string dirname(MakeFullPath(rLocalPath, *d));
+
+ // 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<std::string, BackupClientDirectoryRecord *>::iterator
+ e(mSubDirectories.find(*d));
+
+ if(e != mSubDirectories.end())
+ {
+ // In the list, just use this pointer
+ psubDirRecord = e->second;
+ }
+ else
+ {
+ // Note: if we have exceeded our storage limit, then
+ // we should not upload any more data, nor create any
+ // DirectoryRecord representing data that would have
+ // been uploaded. This step will be repeated when
+ // there is some space available.
+ bool doCreateDirectoryRecord = true;
+
+ // Need to create the record. But do we need to create the directory on the server?
+ int64_t subDirObjectID = 0;
+ if(en != 0)
+ {
+ // No. Exists on the server, and we know about it from the listing.
+ subDirObjectID = en->GetObjectID();
+ }
+ else if(rContext.StorageLimitExceeded())
+ // know we've got a connection if we get this far,
+ // as dir will have been modified.
+ {
+ doCreateDirectoryRecord = false;
+ }
+ else
+ {
+ // Yes, creation required!
+ // It is known that 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<BackupProtocolClientSuccess> 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<std::string, BackupClientDirectoryRecord *>::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<std::string, BackupClientDirectoryRecord *>::iterator
+ e(mSubDirectories.find(rFilename));
+
+ if(e != mSubDirectories.end())
+ {
+ // A record exists for this, remove it
+ BackupClientDirectoryRecord *psubDirRecord = e->second;
+ mSubDirectories.erase(e);
+
+ // And delete the object
+ delete psubDirRecord;
+ }
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDirectoryRecord::UploadFile(
+// BackupClientDirectoryRecord::SyncParams &,
+// const std::string &,
+// const BackupStoreFilename &,
+// int64_t, box_time_t, box_time_t, bool)
+// Purpose: Private. Upload a file to the server. May send
+// a patch instead of the whole thing
+// Created: 20/1/04
+//
+// --------------------------------------------------------------------------
+int64_t BackupClientDirectoryRecord::UploadFile(
+ BackupClientDirectoryRecord::SyncParams &rParams,
+ const std::string &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<BackupProtocolClientSuccess> 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<IOStream> blockIndexStream(connection.ReceiveStream());
+
+ //
+ // Diff the file
+ //
+
+ rContext.ManageDiffProcess();
+
+ bool isCompletelyDifferent = false;
+ std::auto_ptr<IOStream> patchStream(
+ BackupStoreFile::EncodeFileDiff(
+ rFilename.c_str(),
+ mObjectID, /* containing directory */
+ rStoreFilename, diffFromID, *blockIndexStream,
+ connection.GetTimeout(),
+ &rContext, // DiffTimer implementation
+ 0 /* not interested in the modification time */,
+ &isCompletelyDifferent));
+
+ rContext.UnManageDiffProcess();
+
+ //
+ // Upload the patch to the store
+ //
+ std::auto_ptr<BackupProtocolClientSuccess> stored(connection.QueryStoreFile(mObjectID, ModificationTime,
+ AttributesHash, isCompletelyDifferent?(0):(diffFromID), rStoreFilename, *patchStream));
+
+ // Get object ID from the result
+ objID = stored->GetObjectID();
+
+ // 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<IOStream> upload(
+ BackupStoreFile::EncodeFile(rFilename.c_str(),
+ mObjectID, rStoreFilename, NULL,
+ &rParams,
+ &(rParams.mrRunStatusProvider)));
+
+ // Send to store
+ std::auto_ptr<BackupProtocolClientSuccess> stored(
+ connection.QueryStoreFile(
+ mObjectID, ModificationTime,
+ AttributesHash,
+ 0 /* no diff from file ID */,
+ rStoreFilename, *upload));
+
+ // Get object ID from the result
+ objID = stored->GetObjectID();
+ }
+ }
+ 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<std::string, box_time_t>;
+ if (!mpPendingEntries)
+ {
+ throw std::bad_alloc();
+ }
+
+ for (int v = 0; v < iCount; v++)
+ {
+ std::string strItem;
+ box_time_t btItem;
+
+ rArchive.Read(strItem);
+ rArchive.Read(btItem);
+ (*mpPendingEntries)[strItem] = btItem;
+ }
+ }
+
+ //
+ //
+ //
+ iCount = 0;
+ rArchive.Read(iCount);
+
+ if (iCount > 0)
+ {
+ for (int v = 0; v < iCount; v++)
+ {
+ std::string strItem;
+ rArchive.Read(strItem);
+
+ BackupClientDirectoryRecord* pSubDirRecord =
+ new BackupClientDirectoryRecord(0, "");
+ // will be deserialized anyway, give it id 0 for now
+
+ if (!pSubDirRecord)
+ {
+ throw std::bad_alloc();
+ }
+
+ /***** RECURSE *****/
+ pSubDirRecord->Deserialize(rArchive);
+ mSubDirectories[strItem] = pSubDirRecord;
+ }
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientDirectoryRecord::Serialize(Archive & rArchive)
+// Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction.
+//
+// Created: 2005/04/11
+//
+// --------------------------------------------------------------------------
+void BackupClientDirectoryRecord::Serialize(Archive & rArchive) const
+{
+ //
+ //
+ //
+ rArchive.Write(mObjectID);
+ rArchive.Write(mSubDirName);
+ rArchive.Write(mInitialSyncDone);
+ rArchive.Write(mSyncDone);
+
+ //
+ //
+ //
+ int64_t iCount = 0;
+
+ // when reading back the archive, we will
+ // need to know how many items there are.
+ iCount = sizeof(mStateChecksum) / sizeof(mStateChecksum[0]);
+ rArchive.Write(iCount);
+
+ for (int v = 0; v < iCount; v++)
+ {
+ rArchive.Write(mStateChecksum[v]);
+ }
+
+ //
+ //
+ //
+ if (!mpPendingEntries)
+ {
+ iCount = 0;
+ rArchive.Write(iCount);
+ }
+ else
+ {
+ iCount = mpPendingEntries->size();
+ rArchive.Write(iCount);
+
+ for (std::map<std::string, box_time_t>::const_iterator
+ i = mpPendingEntries->begin();
+ i != mpPendingEntries->end(); i++)
+ {
+ rArchive.Write(i->first);
+ rArchive.Write(i->second);
+ }
+ }
+ //
+ //
+ //
+ iCount = mSubDirectories.size();
+ rArchive.Write(iCount);
+
+ for (std::map<std::string, BackupClientDirectoryRecord*>::const_iterator
+ i = mSubDirectories.begin();
+ i != mSubDirectories.end(); i++)
+ {
+ const BackupClientDirectoryRecord* pSubItem = i->second;
+ ASSERT(pSubItem);
+
+ rArchive.Write(i->first);
+ pSubItem->Serialize(rArchive);
+ }
+}
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 <string>
+#include <map>
+
+#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<BackupStoreDirectory::Entry *> &rEntriesLeftOver,
+ std::vector<std::string> &rFiles,
+ const std::vector<std::string> &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<std::string, box_time_t> *mpPendingEntries;
+ std::map<std::string, BackupClientDirectoryRecord *> mSubDirectories;
+ // mpPendingEntries is a pointer rather than simple a member
+ // variable, because most of the time it'll be empty. This would
+ // waste a lot of memory because of STL allocation policies.
+};
+
+#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 <sys/types.h>
+ #include <fcntl.h>
+ #include <limits.h>
+ #include <db.h>
+ #include <sys/stat.h>
+#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<int64_t, int64_t>(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<InodeRefType, std::pair<int64_t, int64_t> >::const_iterator i(mMap.find(InodeRef));
+
+ // Found?
+ if(i == mMap.end())
+ {
+ return false;
+ }
+
+ // Yes. Return the details
+ rObjectIDOut = i->second.first;
+ rInDirectoryOut = i->second.second;
+ return true;
+#else
+ if(mEmpty)
+ {
+ // Map is empty
+ return false;
+ }
+
+ if(dbp == 0)
+ {
+ 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 <sys/types.h>
+
+#include <map>
+#include <utility>
+
+// Use in memory implementation if there isn't access to the Berkely DB on this platform
+#ifndef HAVE_DB
+ #define BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
+#endif
+
+// avoid having to include the DB files when not necessary
+#ifndef BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION
+#ifdef BERKELY_V4
+ class Db;
+#else
+ class DB;
+#endif
+#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<InodeRefType, std::pair<int64_t, int64_t> > mMap;
+#else
+ bool mReadOnly;
+ bool mEmpty;
+#ifdef BERKELY_V4
+ Db *dbp; // c++ style implimentation
+#else
+ DB *dbp; // C style interface, use notation from documentation
+#endif // BERKELY_V4
+#endif // BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
+};
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+#ifdef HAVE_SIGNAL_H
+ #include <signal.h>
+#endif
+#ifdef HAVE_SYS_PARAM_H
+ #include <sys/param.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+ #include <sys/wait.h>
+#endif
+#ifdef HAVE_SYS_MOUNT_H
+ #include <sys/mount.h>
+#endif
+#ifdef HAVE_MNTENT_H
+ #include <mntent.h>
+#endif
+#ifdef HAVE_SYS_MNTTAB_H
+ #include <cstdio>
+ #include <sys/mnttab.h>
+#endif
+#ifdef HAVE_PROCESS_H
+ #include <process.h>
+#endif
+
+#include <iostream>
+
+#include "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 <name> Service name for -i and -r options\n";
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupDaemon::GetConfigVerify()
+// Purpose: Get configuration specification
+// Created: 2003/10/08
+//
+// --------------------------------------------------------------------------
+const ConfigurationVerify *BackupDaemon::GetConfigVerify() const
+{
+ // Defined elsewhere
+ return &BackupDaemonConfigVerify;
+}
+
+#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<Location *>::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> 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<Location *>::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<IOStream> pscript(LocalProcessStream(script,
+ pid));
+
+ // Read in the result
+ IOStreamGetLine getLine(*pscript);
+ std::string line;
+ if(getLine.GetLine(line, true, 30000)) // 30 seconds should be enough
+ {
+ // Got a string, interpret
+ if(line == "now")
+ {
+ // Script says do it now. Obey.
+ waitInSeconds = -1;
+ }
+ else
+ {
+ try
+ {
+ // How many seconds to wait?
+ waitInSeconds = BoxConvert::Convert<int32_t, const std::string&>(line);
+ }
+ catch(ConversionException &e)
+ {
+ BOX_ERROR("Invalid output from "
+ "SyncAllowScript: '" <<
+ line << "' (" << script << ")");
+ throw;
+ }
+
+ BOX_NOTICE("Delaying sync by " << waitInSeconds
+ << " seconds due to SyncAllowScript "
+ << "(" << script << ")");
+ }
+ }
+
+ }
+ 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<BackupProtocolClientSuccess> 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<IOStream> dirstream(connection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, connection.GetTimeout());
+
+ // Map of mount names to ID map index
+ std::map<std::string, int> mounts;
+ int numIDMaps = 0;
+
+#ifdef HAVE_MOUNTS
+#if !defined(HAVE_STRUCT_STATFS_F_MNTONNAME) && !defined(HAVE_STRUCT_STATVFS_F_MNTONNAME)
+ // Linux and others can't tell you where a directory is mounted. So we
+ // have to read the mount entries from /etc/mtab! Bizarre that the OS
+ // itself can't tell you, but there you go.
+ std::set<std::string, mntLenCompare> mountPoints;
+ // BLOCK
+ FILE *mountPointsFile = 0;
+
+#ifdef HAVE_STRUCT_MNTENT_MNT_DIR
+ // Open mounts file
+ mountPointsFile = ::setmntent("/proc/mounts", "r");
+ if(mountPointsFile == 0)
+ {
+ mountPointsFile = ::setmntent("/etc/mtab", "r");
+ }
+ if(mountPointsFile == 0)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+
+ try
+ {
+ // Read all the entries, and put them in the set
+ struct mntent *entry = 0;
+ while((entry = ::getmntent(mountPointsFile)) != 0)
+ {
+ BOX_TRACE("Found mount point at " << entry->mnt_dir);
+ mountPoints.insert(std::string(entry->mnt_dir));
+ }
+
+ // Close mounts file
+ ::endmntent(mountPointsFile);
+ }
+ catch(...)
+ {
+ ::endmntent(mountPointsFile);
+ throw;
+ }
+#else // ! HAVE_STRUCT_MNTENT_MNT_DIR
+ // Open mounts file
+ mountPointsFile = ::fopen("/etc/mnttab", "r");
+ if(mountPointsFile == 0)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+
+ try
+ {
+ // Read all the entries, and put them in the set
+ struct mnttab entry;
+ while(getmntent(mountPointsFile, &entry) == 0)
+ {
+ BOX_TRACE("Found mount point at " << entry.mnt_mountp);
+ mountPoints.insert(std::string(entry.mnt_mountp));
+ }
+
+ // Close mounts file
+ ::fclose(mountPointsFile);
+ }
+ catch(...)
+ {
+ ::fclose(mountPointsFile);
+ throw;
+ }
+#endif // HAVE_STRUCT_MNTENT_MNT_DIR
+ // Check sorting and that things are as we expect
+ ASSERT(mountPoints.size() > 0);
+#ifndef BOX_RELEASE_BUILD
+ {
+ std::set<std::string, mntLenCompare>::reverse_iterator i(mountPoints.rbegin());
+ ASSERT(*i == "/");
+ }
+#endif // n BOX_RELEASE_BUILD
+#endif // n HAVE_STRUCT_STATFS_F_MNTONNAME || n HAVE_STRUCT_STATVFS_F_MNTONNAME
+#endif // HAVE_MOUNTS
+
+ // Then... go through each of the entries in the configuration,
+ // making sure there's a directory created for it.
+ std::vector<std::string> locNames =
+ rLocationsConf.GetSubConfigurationNames();
+
+ for(std::vector<std::string>::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<Location> 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<std::string, mntLenCompare>::const_iterator i(mountPoints.begin());
+ BOX_TRACE(mountPoints.size()
+ << " potential mount points");
+ for(; i != mountPoints.end(); ++i)
+ {
+ // Compare first n characters with the filename
+ // If it matches, the file belongs in that mount point
+ // (sorting order ensures this)
+ BOX_TRACE("checking against mount point " << *i);
+ if(::strncmp(i->c_str(), 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<std::string, int>::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<BackupProtocolClientSuccess>
+ 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<int64_t,std::string>
+ (en->GetObjectID(), name));
+ // Log this
+ BOX_INFO("Unused location in root: " << name);
+ }
+ ASSERT(mUnusedRootDirEntries.size() > 0);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupDaemon::SetupIDMapsForSync()
+// Purpose: Sets up ID maps for the sync process -- make sure they're all there
+// Created: 11/11/03
+//
+// --------------------------------------------------------------------------
+void BackupDaemon::SetupIDMapsForSync()
+{
+ // 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<BackupClientInodeToIDMap *> &)
+// Purpose: Fills the vector with the right number of empty ID maps
+// Created: 11/11/03
+//
+// --------------------------------------------------------------------------
+void BackupDaemon::FillIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVector, bool NewMaps)
+{
+ ASSERT(rVector.size() == 0);
+ rVector.reserve(mIDMapMounts.size());
+
+ for(unsigned int l = 0; l < mIDMapMounts.size(); ++l)
+ {
+ // Create the object
+ BackupClientInodeToIDMap *pmap = new BackupClientInodeToIDMap();
+ try
+ {
+ // Get the base filename of this map
+ std::string filename;
+ MakeMapBaseName(l, filename);
+
+ // If it's a new one, add a suffix
+ if(NewMaps)
+ {
+ filename += ".n";
+ }
+
+ // 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<BackupClientInodeToIDMap *> &)
+// Purpose: Deletes the contents of a vector of ID maps
+// Created: 11/11/03
+//
+// --------------------------------------------------------------------------
+void BackupDaemon::DeleteIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVector)
+{
+ while(!rVector.empty())
+ {
+ // Pop off list
+ BackupClientInodeToIDMap *toDel = rVector.back();
+ rVector.pop_back();
+
+ // Close and delete
+ 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<Location *>::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<std::pair<int64_t,std::string> >::iterator
+ i(mUnusedRootDirEntries.begin());
+ i != mUnusedRootDirEntries.end(); ++i)
+ {
+ connection.QueryDeleteDirectory(i->first);
+ rContext.GetProgressNotifier().NotifyFileDeleted(
+ i->first, i->second);
+ }
+
+ // Reset state
+ mDeleteUnusedRootDirEntriesAfter = 0;
+ mUnusedRootDirEntries.clear();
+}
+
+// --------------------------------------------------------------------------
+
+typedef struct
+{
+ int32_t mMagicValue; // also the version number
+ int32_t mNumEntries;
+ int64_t mObjectID; // this object ID
+ int64_t mContainerID; // ID of container
+ uint64_t mAttributesModTime;
+ int32_t mOptionsPresent; // bit mask of optional sections / features present
+
+} loc_StreamFormat;
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupDaemon::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<int64_t, std::string>(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 <vector>
+#include <string>
+#include <memory>
+
+#include "BackupClientContext.h"
+#include "BackupClientDirectoryRecord.h"
+#include "BoxTime.h"
+#include "Daemon.h"
+#include "Logging.h"
+#include "Socket.h"
+#include "SocketListen.h"
+#include "SocketStream.h"
+#include "TLSContext.h"
+
+#include "autogen_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<BackupClientInodeToIDMap *> &rVector);
+ void DeleteAllIDMaps()
+ {
+ DeleteIDMapVector(mCurrentIDMaps);
+ DeleteIDMapVector(mNewIDMaps);
+ }
+ void FillIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVector, bool NewMaps);
+
+ void SetupIDMapsForSync();
+ void CommitIDMapsAfterSync();
+ void DeleteCorruptBerkelyDbFiles();
+
+ void MakeMapBaseName(unsigned int MountNumber, std::string &rNameOut) const;
+
+ void SetState(int State);
+
+ void WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut);
+ void CloseCommandConnection();
+ void SendSyncStartOrFinish(bool SendStart);
+
+ void DeleteUnusedRootDirEntries(BackupClientContext &rContext);
+
+#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<BackupClientDirectoryRecord> mpDirectoryRecord;
+ int mIDMapIndex;
+ ExcludeList *mpExcludeFiles;
+ ExcludeList *mpExcludeDirs;
+ };
+
+ typedef const std::vector<Location *> Locations;
+ Locations GetLocations() { return mLocations; }
+
+private:
+ int mState; // what the daemon is currently doing
+
+ std::vector<Location *> mLocations;
+
+ std::vector<std::string> mIDMapMounts;
+ std::vector<BackupClientInodeToIDMap *> mCurrentIDMaps;
+ std::vector<BackupClientInodeToIDMap *> mNewIDMaps;
+
+ int mDeleteRedundantLocationsAfter;
+
+ // For the command socket
+ class CommandSocketInfo
+ {
+ public:
+ CommandSocketInfo();
+ ~CommandSocketInfo();
+ private:
+ CommandSocketInfo(const CommandSocketInfo &); // no copying
+ CommandSocketInfo &operator=(const CommandSocketInfo &);
+ public:
+#ifdef WIN32
+ WinNamedPipeListener<1 /* listen backlog */> mListeningSocket;
+ std::auto_ptr<WinNamedPipeStream> mpConnectedSocket;
+#else
+ SocketListen<SocketStream, 1 /* listen backlog */> mListeningSocket;
+ std::auto_ptr<SocketStream> mpConnectedSocket;
+#endif
+ IOStreamGetLine *mpGetLine;
+ };
+
+ // Using a socket?
+ std::auto_ptr<CommandSocketInfo> mapCommandSocketInfo;
+
+ // Stop notifications being repeated.
+ SysadminNotifier::EventCode mLastNotifiedEvent;
+
+ // Unused entries in the root directory wait a while before being deleted
+ box_time_t mDeleteUnusedRootDirEntriesAfter; // time to delete them
+ std::vector<std::pair<int64_t,std::string> > mUnusedRootDirEntries;
+
+ int64_t mClientStoreMarker;
+ bool mStorageLimitExceeded;
+ bool mReadErrorsOnFilesystemObjects;
+ box_time_t mLastSyncTime, mNextSyncTime;
+ box_time_t mCurrentSyncStartTime, mUpdateStoreInterval;
+ 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 <string>
+// #include <map>
+
+// #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 <unistd.h>
+#endif
+#ifdef HAVE_PROCESS_H
+ #include <process.h>
+#endif
+
+extern void TerminateService(void);
+extern unsigned int WINAPI RunService(LPVOID lpParameter);
+
+// Global variables
+
+TCHAR* gServiceName = TEXT("Box Backup Service");
+SERVICE_STATUS gServiceStatus;
+SERVICE_STATUS_HANDLE gServiceStatusHandle = 0;
+HANDLE gStopServiceEvent = 0;
+DWORD gServiceReturnCode = 0;
+
+#define SERVICE_NAME "boxbackup"
+
+void ShowMessage(char *s)
+{
+ MessageBox(0, s, "Box Backup Message",
+ MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY);
+}
+
+void ErrorHandler(char *s, DWORD err)
+{
+ char buf[256];
+ memset(buf, 0, sizeof(buf));
+ _snprintf(buf, sizeof(buf)-1, "%s: %s", s,
+ GetErrorMessage(err).c_str());
+ BOX_ERROR(buf);
+ MessageBox(0, buf, "Error",
+ MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY);
+ ExitProcess(err);
+}
+
+void WINAPI ServiceControlHandler( DWORD controlCode )
+{
+ switch ( controlCode )
+ {
+ case SERVICE_CONTROL_INTERROGATE:
+ break;
+
+ case SERVICE_CONTROL_SHUTDOWN:
+ case SERVICE_CONTROL_STOP:
+ Beep(1000,100);
+ TerminateService();
+ gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
+ SetServiceStatus(gServiceStatusHandle, &gServiceStatus);
+
+ SetEvent(gStopServiceEvent);
+ return;
+
+ case SERVICE_CONTROL_PAUSE:
+ break;
+
+ case SERVICE_CONTROL_CONTINUE:
+ break;
+
+ default:
+ if ( controlCode >= 128 && controlCode <= 255 )
+ // user defined control code
+ break;
+ else
+ // unrecognised control code
+ break;
+ }
+
+ SetServiceStatus( gServiceStatusHandle, &gServiceStatus );
+}
+
+// ServiceMain is called when the SCM wants to
+// start the service. When it returns, the service
+// has stopped. It therefore waits on an event
+// just before the end of the function, and
+// that event gets set when it is time to stop.
+// It also returns on any error because the
+// service cannot start if there is an eror.
+
+static char* spConfigFileName;
+
+VOID ServiceMain(DWORD argc, LPTSTR *argv)
+{
+ // initialise service status
+ gServiceStatus.dwServiceType = SERVICE_WIN32;
+ gServiceStatus.dwCurrentState = SERVICE_STOPPED;
+ gServiceStatus.dwControlsAccepted = 0;
+ gServiceStatus.dwWin32ExitCode = NO_ERROR;
+ gServiceStatus.dwServiceSpecificExitCode = NO_ERROR;
+ gServiceStatus.dwCheckPoint = 0;
+ gServiceStatus.dwWaitHint = 0;
+
+ gServiceStatusHandle = RegisterServiceCtrlHandler(gServiceName,
+ ServiceControlHandler);
+
+ if (gServiceStatusHandle)
+ {
+ // service is starting
+ gServiceStatus.dwCurrentState = SERVICE_START_PENDING;
+ SetServiceStatus(gServiceStatusHandle, &gServiceStatus);
+
+ // do initialisation here
+ gStopServiceEvent = CreateEvent(0, TRUE, FALSE, 0);
+ if (!gStopServiceEvent)
+ {
+ gServiceStatus.dwControlsAccepted &=
+ ~(SERVICE_ACCEPT_STOP |
+ SERVICE_ACCEPT_SHUTDOWN);
+ gServiceStatus.dwCurrentState = SERVICE_STOPPED;
+ SetServiceStatus(gServiceStatusHandle, &gServiceStatus);
+ return;
+ }
+
+ HANDLE ourThread = (HANDLE)_beginthreadex(
+ NULL,
+ 0,
+ RunService,
+ spConfigFileName,
+ CREATE_SUSPENDED,
+ NULL);
+
+ SetThreadPriority(ourThread, THREAD_PRIORITY_LOWEST);
+ ResumeThread(ourThread);
+
+ // we are now running so tell the SCM
+ gServiceStatus.dwControlsAccepted |=
+ (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
+ gServiceStatus.dwCurrentState = SERVICE_RUNNING;
+ SetServiceStatus(gServiceStatusHandle, &gServiceStatus);
+
+ // do cleanup here
+ WaitForSingleObject(gStopServiceEvent, INFINITE);
+ CloseHandle(gStopServiceEvent);
+ gStopServiceEvent = 0;
+
+ // service was stopped
+ gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
+ SetServiceStatus(gServiceStatusHandle, &gServiceStatus);
+
+ // service is now stopped
+ gServiceStatus.dwControlsAccepted &=
+ ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
+
+ gServiceStatus.dwCurrentState = SERVICE_STOPPED;
+
+ if (gServiceReturnCode != 0)
+ {
+ gServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
+ gServiceStatus.dwServiceSpecificExitCode = gServiceReturnCode;
+ }
+
+ SetServiceStatus(gServiceStatusHandle, &gServiceStatus);
+ }
+}
+
+int OurService(const char* pConfigFileName)
+{
+ spConfigFileName = strdup(pConfigFileName);
+
+ SERVICE_TABLE_ENTRY serviceTable[] =
+ {
+ { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain },
+ { NULL, NULL }
+ };
+ BOOL success;
+
+ // Register with the SCM
+ success = StartServiceCtrlDispatcher(serviceTable);
+
+ free(spConfigFileName);
+ spConfigFileName = NULL;
+
+ if (!success)
+ {
+ ErrorHandler("Failed to start service. Did you start "
+ "Box Backup from the Service Control Manager? "
+ "(StartServiceCtrlDispatcher)", GetLastError());
+ return 1;
+ }
+
+ return 0;
+}
+
+int InstallService(const char* pConfigFileName, const std::string& rServiceName)
+{
+ if (pConfigFileName != NULL)
+ {
+ EMU_STRUCT_STAT st;
+
+ if (emu_stat(pConfigFileName, &st) != 0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to open configuration file "
+ "'" << pConfigFileName << "'");
+ return 1;
+ }
+
+ if (!(st.st_mode & S_IFREG))
+ {
+
+ BOX_ERROR("Failed to open configuration file '" <<
+ pConfigFileName << "': not a file");
+ return 1;
+ }
+ }
+
+ SC_HANDLE scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE);
+
+ if (!scm)
+ {
+ BOX_ERROR("Failed to open service control manager: " <<
+ GetErrorMessage(GetLastError()));
+ return 1;
+ }
+
+ char cmd[MAX_PATH];
+ GetModuleFileName(NULL, cmd, sizeof(cmd)-1);
+ cmd[sizeof(cmd)-1] = 0;
+
+ std::string cmdWithArgs(cmd);
+ cmdWithArgs += " -s -S \"" + rServiceName + "\"";
+
+ if (pConfigFileName != NULL)
+ {
+ cmdWithArgs += " \"";
+ cmdWithArgs += pConfigFileName;
+ cmdWithArgs += "\"";
+ }
+
+ std::string serviceDesc = "Box Backup (" + rServiceName + ")";
+
+ SC_HANDLE newService = CreateService(
+ scm,
+ rServiceName.c_str(),
+ serviceDesc.c_str(),
+ SERVICE_ALL_ACCESS,
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_AUTO_START,
+ SERVICE_ERROR_NORMAL,
+ cmdWithArgs.c_str(),
+ 0,0,0,0,0);
+
+ DWORD err = GetLastError();
+ CloseServiceHandle(scm);
+
+ if (!newService)
+ {
+ switch (err)
+ {
+ case ERROR_SERVICE_EXISTS:
+ {
+ BOX_ERROR("Failed to create Box Backup "
+ "service: it already exists");
+ }
+ break;
+
+ case ERROR_SERVICE_MARKED_FOR_DELETE:
+ {
+ BOX_ERROR("Failed to create Box Backup "
+ "service: it is waiting to be deleted");
+ }
+ break;
+
+ case ERROR_DUPLICATE_SERVICE_NAME:
+ {
+ BOX_ERROR("Failed to create Box Backup "
+ "service: a service with this name "
+ "already exists");
+ }
+ break;
+
+ default:
+ {
+ BOX_ERROR("Failed to create Box Backup "
+ "service: error " <<
+ GetErrorMessage(GetLastError()));
+ }
+ }
+
+ return 1;
+ }
+
+ BOX_INFO("Created Box Backup service");
+
+ SERVICE_DESCRIPTION desc;
+ desc.lpDescription = "Backs up your data files over the Internet";
+
+ if (!ChangeServiceConfig2(newService, SERVICE_CONFIG_DESCRIPTION,
+ &desc))
+ {
+ BOX_WARNING("Failed to set description for Box Backup "
+ "service: " << GetErrorMessage(GetLastError()));
+ }
+
+ CloseServiceHandle(newService);
+
+ return 0;
+}
+
+int RemoveService(const std::string& rServiceName)
+{
+ SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
+
+ if (!scm)
+ {
+ BOX_ERROR("Failed to open service control manager: " <<
+ GetErrorMessage(GetLastError()));
+ return 1;
+ }
+
+ SC_HANDLE service = OpenService(scm, rServiceName.c_str(),
+ SERVICE_ALL_ACCESS|DELETE);
+ DWORD err = GetLastError();
+ CloseServiceHandle(scm);
+
+ if (!service)
+ {
+ if (err == ERROR_SERVICE_DOES_NOT_EXIST ||
+ err == ERROR_IO_PENDING)
+ // hello microsoft? anyone home?
+ {
+ BOX_ERROR("Failed to open Box Backup service: "
+ "not installed or not found");
+ }
+ else
+ {
+ BOX_ERROR("Failed to open Box Backup service: " <<
+ GetErrorMessage(err));
+ }
+ return 1;
+ }
+
+ SERVICE_STATUS status;
+ if (!ControlService(service, SERVICE_CONTROL_STOP, &status))
+ {
+ err = GetLastError();
+ if (err != ERROR_SERVICE_NOT_ACTIVE)
+ {
+ BOX_WARNING("Failed to stop Box Backup service: " <<
+ GetErrorMessage(err));
+ }
+ }
+
+ BOOL deleted = DeleteService(service);
+ err = GetLastError();
+ CloseServiceHandle(service);
+
+ if (deleted)
+ {
+ BOX_INFO("Box Backup service deleted");
+ return 0;
+ }
+ else if (err == ERROR_SERVICE_MARKED_FOR_DELETE)
+ {
+ BOX_ERROR("Failed to remove Box Backup service: "
+ "it is already being deleted");
+ }
+ else
+ {
+ BOX_ERROR("Failed to remove Box Backup service: " <<
+ GetErrorMessage(err));
+ }
+
+ return 1;
+}
+
+#endif // WIN32
diff --git a/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 <store-full|read-error|backup-error|backup-start|backup-finish>" >&2
+ exit 2
+elif [ "\$1" = store-full ]; then
+ $sendmail \$SENDTO <<EOM
+Subject: \$SUBJECT (store full)
+To: \$SENDTO
+
+
+The store account for $hostname is full.
+
+=============================
+FILES ARE NOT BEING BACKED UP
+=============================
+
+Please adjust the limits on account $account_num on server $server.
+
+EOM
+elif [ "\$1" = read-error ]; then
+$sendmail \$SENDTO <<EOM
+Subject: \$SUBJECT (read errors)
+To: \$SENDTO
+
+
+Errors occured reading some files or directories for backup on $hostname.
+
+===================================
+THESE FILES ARE NOT BEING BACKED UP
+===================================
+
+Check the logs on $hostname for the files and directories which caused
+these errors, and take appropriate action.
+
+Other files are being backed up.
+
+EOM
+elif [ "\$1" = backup-start -o "\$1" = backup-finish ]; then
+ # do nothing by default
+ true
+else
+$sendmail \$SENDTO <<EOM
+Subject: \$SUBJECT (unknown)
+To: \$SENDTO
+
+
+The backup daemon on $hostname reported an unknown error (\$1).
+
+==========================
+FILES MAY NOT BE BACKED UP
+==========================
+
+Please check the logs on $hostname.
+
+EOM
+fi
+__EOS
+
+close NOTIFY;
+chmod 0700,$notify_script or die "Can't chmod $notify_script";
+
+
+# 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;
+
+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 <cstdio>
+#include <cstring>
+
+#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 <input file>\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 <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_DIRENT_H
+ #include <dirent.h>
+#endif
+
+#include <cstring>
+#include <limits>
+#include <iostream>
+#include <ostream>
+#include <set>
+
+#include "BackupClientFileAttributes.h"
+#include "BackupClientMakeExcludeList.h"
+#include "BackupClientRestore.h"
+#include "BackupQueries.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFilenameClear.h"
+#include "BoxTimeToText.h"
+#include "CommonException.h"
+#include "Configuration.h"
+#include "ExcludeList.h"
+#include "FileModificationTime.h"
+#include "FileStream.h"
+#include "IOStream.h"
+#include "Logging.h"
+#include "PathUtils.h"
+#include "SelfFlushingStream.h"
+#include "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<std::string> 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<std::string>::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<std::string> 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<std::string> &, const bool *)
+// Purpose: List directories (optionally recursive)
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandList(const std::vector<std::string> &args, const bool *opts)
+{
+ #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<IOStream> 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<std::pair<std::string, int64_t> > *pStack)
+{
+ // Split up string into elements
+ std::vector<std::string> dirElements;
+ SplitString(rDirName, '/', dirElements);
+
+ // Start from current stack, or root, whichever is required
+ std::vector<std::pair<std::string, int64_t> > stack;
+ int64_t dirID = 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<BackupProtocolClientSuccess> 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<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+
+ // Then... find the directory within it
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreFilenameClear dirname(dirElements[e]);
+ BackupStoreDirectory::Entry *en = i.FindMatchingClearName(dirname);
+ if(en == 0)
+ {
+ // Not found
+ return 0;
+ }
+
+ // Object ID for next round of searching
+ dirID = en->GetObjectID();
+
+ // Push onto stack
+ stack.push_back(std::pair<std::string, int64_t>(dirElements[e], dirID));
+ }
+ }
+ }
+
+ // If required, copy the new stack to the caller
+ if(pStack)
+ {
+ *pStack = stack;
+ }
+
+ return dirID;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::GetCurrentDirectoryID()
+// Purpose: Returns the ID of the current directory
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+int64_t BackupQueries::GetCurrentDirectoryID()
+{
+ // Special case for root
+ if(mDirStack.size() == 0)
+ {
+ return 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<std::string> &)
+// Purpose: Change directory command
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const bool *opts)
+{
+ if(args.size() != 1 || args[0].size() == 0)
+ {
+ BOX_ERROR("Incorrect usage. cd [-o] [-d] <directory>");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+#ifdef WIN32
+ std::string dirName;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return;
+#else
+ const std::string& dirName(args[0]);
+#endif
+
+ std::vector<std::pair<std::string, int64_t> > newStack;
+ int64_t id = FindDirectoryObjectID(dirName, opts['o'], opts['d'],
+ &newStack);
+
+ if(id == 0)
+ {
+ BOX_ERROR("Directory '" << args[0] << "' not found.");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ // Store new stack
+ mDirStack = newStack;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &)
+// Purpose: Change local directory command
+// Created: 2003/10/11
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args)
+{
+ if(args.size() != 1 || args[0].size() == 0)
+ {
+ BOX_ERROR("Incorrect usage. lcd <local-directory>");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ // Try changing directory
+#ifdef WIN32
+ std::string dirName;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), dirName))
+ {
+ BOX_ERROR("Failed to convert path from console encoding.");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+ int result = ::chdir(dirName.c_str());
+#else
+ int result = ::chdir(args[0].c_str());
+#endif
+ if(result != 0)
+ {
+ if(errno == ENOENT || errno == ENOTDIR)
+ {
+ BOX_ERROR("Directory '" << args[0] << "' does not exist.");
+ }
+ else
+ {
+ BOX_LOG_SYS_ERROR("Failed to change to directory "
+ "'" << args[0] << "'");
+ }
+
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+ // Report current dir
+ char wd[PATH_MAX];
+ if(::getcwd(wd, PATH_MAX) == 0)
+ {
+ BOX_LOG_SYS_ERROR("Error getting current directory");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+
+#ifdef WIN32
+ if(!ConvertUtf8ToConsole(wd, dirName))
+ {
+ BOX_ERROR("Failed to convert new path from console encoding.");
+ SetReturnCode(ReturnCode::Command_Error);
+ return;
+ }
+ BOX_INFO("Local current directory is now '" << dirName << "'.");
+#else
+ BOX_INFO("Local current directory is now '" << wd << "'.");
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandGetObject(const std::vector<std::string> &, const bool *)
+// Purpose: Gets an object without any translation.
+// Created: 2003/10/11
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const bool *opts)
+{
+ // Check args
+ if(args.size() != 2)
+ {
+ BOX_ERROR("Incorrect usage. getobject <object-id> "
+ "<local-filename>");
+ return;
+ }
+
+ int64_t id = ::strtoll(args[0].c_str(), 0, 16);
+ if(id == std::numeric_limits<long long>::min() || id == std::numeric_limits<long long>::max() || id == 0)
+ {
+ BOX_ERROR("Not a valid object ID (specified in hex).");
+ 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<BackupProtocolClientSuccess> getobj(mrConnection.QueryGetObject(id));
+ if(getobj->GetObjectID() != BackupProtocolClientGetObject::NoObject)
+ {
+ // Stream that object out to the file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+ objectStream->CopyStreamTo(out);
+
+ BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(id) <<
+ " fetched successfully.");
+ }
+ 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<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+ BackupStoreDirectory::Entry *en;
+
+ if(opts['i'])
+ {
+ // Specified as ID.
+ fileId = ::strtoll(rNameOrIdString.c_str(), 0, 16);
+ if(fileId == std::numeric_limits<long long>::min() ||
+ fileId == std::numeric_limits<long long>::max() ||
+ fileId == 0)
+ {
+ BOX_ERROR("Not a valid object ID (specified in hex).");
+ return 0;
+ }
+
+ // Check that the item is actually in the directory
+ en = dir.FindEntryByID(fileId);
+ if(en == 0)
+ {
+ BOX_ERROR("File ID " <<
+ BOX_FORMAT_OBJECTID(fileId) <<
+ " not found in current directory on store.\n"
+ "(You can only access files by ID from the "
+ "current directory.)");
+ return 0;
+ }
+ }
+ else
+ {
+ // Specified by name, find the object in the directory to get the ID
+ BackupStoreDirectory::Iterator i(dir);
+ en = i.FindMatchingClearName(fn);
+ if(en == 0)
+ {
+ BOX_ERROR("Filename '" << rNameOrIdString << "' "
+ "not found in current directory on store.\n"
+ "(Subdirectories in path not searched.)");
+ return 0;
+ }
+
+ fileId = en->GetObjectID();
+ }
+
+ *pDirIdOut = dirId;
+
+ if(pFlagsOut)
+ {
+ *pFlagsOut = en->GetFlags();
+ }
+
+ if(pFileNameOut)
+ {
+ BackupStoreFilenameClear entryName(en->GetName());
+ *pFileNameOut = entryName.GetClearFilename();
+ }
+
+ return fileId;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandGet(const std::vector<std::string> &, const bool *)
+// Purpose: Command to get a file from the store
+// Created: 2003/10/12
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts)
+{
+ // At least one argument?
+ // Check args
+ if(args.size() < 1 || (opts['i'] && args.size() != 2) || args.size() > 2)
+ {
+ BOX_ERROR("Incorrect usage.\n"
+ "get <remote-filename> [<local-filename>] or\n"
+ "get -i <object-id> <local-filename>");
+ return;
+ }
+
+ // Find object ID somehow
+ int64_t fileId, dirId;
+ std::string localName;
+
+#ifdef WIN32
+ for (std::vector<std::string>::iterator
+ i = args.begin(); i != args.end(); i++)
+ {
+ std::string out;
+ if(!ConvertConsoleToUtf8(i->c_str(), out))
+ {
+ BOX_ERROR("Failed to convert encoding.");
+ return;
+ }
+ *i = out;
+ }
+#endif
+
+ int16_t flagsExclude;
+
+ if(opts['i'])
+ {
+ // can retrieve anything by ID
+ flagsExclude = 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<IOStream> objectStream(mrConnection.ReceiveStream());
+
+ // Decode it
+ BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout());
+
+ // Done.
+ BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(fileId) <<
+ " fetched successfully.");
+ }
+ catch (BoxException &e)
+ {
+ BOX_ERROR("Failed to fetch file: " <<
+ e.what());
+ ::unlink(localName.c_str());
+ }
+ catch(std::exception &e)
+ {
+ BOX_ERROR("Failed to fetch file: " <<
+ e.what());
+ ::unlink(localName.c_str());
+ }
+ catch(...)
+ {
+ BOX_ERROR("Failed to fetch file: unknown error");
+ ::unlink(localName.c_str());
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareParams::CompareParams()
+// Purpose: Constructor
+// Created: 29/1/04
+//
+// --------------------------------------------------------------------------
+BackupQueries::CompareParams::CompareParams(bool QuickCompare,
+ bool IgnoreExcludes, bool IgnoreAttributes,
+ box_time_t LatestFileUploadTime)
+: BoxBackupCompareParams(QuickCompare, IgnoreExcludes, IgnoreAttributes,
+ LatestFileUploadTime),
+ mDifferences(0),
+ mDifferencesExplainedByModTime(0),
+ mUncheckedFiles(0),
+ mExcludedDirs(0),
+ mExcludedFiles(0)
+{ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandCompare(const std::vector<std::string> &, const bool *)
+// Purpose: Command to compare data on the store with local data
+// Created: 2003/10/12
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandCompare(const std::vector<std::string> &args, const bool *opts)
+{
+ box_time_t LatestFileUploadTime = GetCurrentBoxTime();
+
+ // Try and work out the time before which all files should be on the server
+ {
+ std::string syncTimeFilename(mrConfiguration.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR);
+ syncTimeFilename += "last_sync_start";
+ // Stat it to get file time
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(syncTimeFilename.c_str(), &st) == 0)
+ {
+ // Files modified after this time shouldn't be on the server, so report errors slightly differently
+ LatestFileUploadTime = FileModificationTime(st) -
+ SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge"));
+ }
+ else
+ {
+ BOX_WARNING("Failed to determine the time of the last "
+ "synchronisation -- checks not performed.");
+ }
+ }
+
+ // Parameters, including count of differences
+ BackupQueries::CompareParams params(opts['q'], // quick compare?
+ opts['E'], // ignore excludes
+ opts['A'], // ignore attributes
+ LatestFileUploadTime);
+
+ params.mQuietCompare = opts['Q'];
+
+ // Quick compare?
+ if(params.QuickCompare())
+ {
+ BOX_WARNING("Quick compare used -- file attributes are not "
+ "checked.");
+ }
+
+ if(!opts['l'] && opts['a'] && args.size() == 0)
+ {
+ // Compare all locations
+ const Configuration &rLocations(
+ mrConfiguration.GetSubConfiguration("BackupLocations"));
+ std::vector<std::string> locNames =
+ rLocations.GetSubConfigurationNames();
+ for(std::vector<std::string>::iterator
+ pLocName = locNames.begin();
+ pLocName != locNames.end();
+ pLocName++)
+ {
+ CompareLocation(*pLocName, params);
+ }
+ }
+ else if(opts['l'] && !opts['a'] && args.size() == 1)
+ {
+ // Compare one location
+ CompareLocation(args[0], params);
+ }
+ else if(!opts['l'] && !opts['a'] && args.size() == 2)
+ {
+ // Compare directory to directory
+
+ // Can't be bothered to do all the hard work to work out which location it's on, and hence which exclude list
+ if(!params.IgnoreExcludes())
+ {
+ BOX_ERROR("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes.");
+ return;
+ }
+ else
+ {
+ // Do compare
+ Compare(args[0], args[1], params);
+ }
+ }
+ else
+ {
+ BOX_ERROR("Incorrect usage.\ncompare -a\n or compare -l <location-name>\n or compare <store-dir-name> <local-dir-name>");
+ return;
+ }
+
+ if (!params.mQuietCompare)
+ {
+ BOX_INFO("[ " <<
+ params.mDifferencesExplainedByModTime << " (of " <<
+ params.mDifferences << ") differences probably "
+ "due to file modifications after the last upload ]");
+ }
+
+ BOX_INFO("Differences: " << params.mDifferences << " (" <<
+ params.mExcludedDirs << " dirs excluded, " <<
+ params.mExcludedFiles << " files excluded, " <<
+ params.mUncheckedFiles << " files not checked)");
+
+ // Set return code?
+ if(opts['c'])
+ {
+ if (params.mUncheckedFiles != 0)
+ {
+ SetReturnCode(ReturnCode::Compare_Error);
+ }
+ else if (params.mDifferences != 0)
+ {
+ SetReturnCode(ReturnCode::Compare_Different);
+ }
+ else
+ {
+ SetReturnCode(ReturnCode::Compare_Same);
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CompareLocation(const std::string &, BackupQueries::CompareParams &)
+// Purpose: Compare a location
+// Created: 2003/10/13
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CompareLocation(const std::string &rLocation,
+ BoxBackupCompareParams &rParams)
+{
+ // Find the location's sub configuration
+ const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations"));
+ if(!locations.SubConfigurationExists(rLocation.c_str()))
+ {
+ BOX_ERROR("Location " << rLocation << " does not exist.");
+ return;
+ }
+ const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str()));
+
+ #ifdef WIN32
+ {
+ std::string path = loc.GetKeyValue("Path");
+ if (path.size() > 0 && path[path.size()-1] ==
+ DIRECTORY_SEPARATOR_ASCHAR)
+ {
+ BOX_WARNING("Location '" << rLocation << "' path ends "
+ "with '" DIRECTORY_SEPARATOR "', "
+ "compare may fail!");
+ }
+ }
+ #endif
+
+ // Generate the exclude lists
+ if(!rParams.IgnoreExcludes())
+ {
+ rParams.LoadExcludeLists(loc);
+ }
+
+ // Then get it compared
+ Compare(std::string("/") + rLocation, loc.GetKeyValue("Path"), rParams);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::Compare(const std::string &,
+// const std::string &, BackupQueries::CompareParams &)
+// Purpose: Compare a store directory against a local directory
+// Created: 2003/10/13
+//
+// --------------------------------------------------------------------------
+void BackupQueries::Compare(const std::string &rStoreDir,
+ const std::string &rLocalDir, BoxBackupCompareParams &rParams)
+{
+#ifdef WIN32
+ std::string localDirEncoded;
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return;
+ if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return;
+#else
+ const std::string& localDirEncoded(rLocalDir);
+ const std::string& storeDirEncoded(rStoreDir);
+#endif
+
+ // Get the directory ID of the directory -- only use current data
+ int64_t dirID = FindDirectoryObjectID(storeDirEncoded);
+
+ // Found?
+ if(dirID == 0)
+ {
+ bool modifiedAfterLastSync = false;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(rLocalDir.c_str(), &st) == 0)
+ {
+ if(FileAttrModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+ }
+
+ rParams.NotifyRemoteFileMissing(localDirEncoded,
+ storeDirEncoded, modifiedAfterLastSync);
+ return;
+ }
+
+ // Go!
+ Compare(dirID, storeDirEncoded, localDirEncoded, rParams);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// 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<IOStream> dirstream(mrConnection.ReceiveStream());
+ dir.ReadFromStream(*dirstream, mrConnection.GetTimeout());
+
+ // Test out the attributes
+ if(!dir.HasAttributes())
+ {
+ rParams.NotifyStoreDirMissingAttributes(rLocalDir, rStoreDir);
+ }
+ else
+ {
+ // Fetch the attributes
+ const StreamableMemBlock &storeAttr(dir.GetAttributes());
+ BackupClientFileAttributes attr(storeAttr);
+
+ // Get attributes of local directory
+ BackupClientFileAttributes localAttr;
+ localAttr.ReadAttributes(rLocalDir.c_str(),
+ true /* directories have zero mod times */);
+
+ if(attr.Compare(localAttr, true, true /* ignore modification times */))
+ {
+ rParams.NotifyDirCompared(rLocalDir, rStoreDir,
+ false, false /* actually we didn't check :) */);
+ }
+ else
+ {
+ bool modifiedAfterLastSync = false;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(rLocalDir.c_str(), &st) == 0)
+ {
+ if(FileAttrModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+ }
+
+ rParams.NotifyDirCompared(rLocalDir, rStoreDir,
+ true, modifiedAfterLastSync);
+ }
+ }
+
+ // Open the local directory
+ DIR *dirhandle = ::opendir(rLocalDir.c_str());
+ if(dirhandle == 0)
+ {
+ rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir);
+ return;
+ }
+
+ try
+ {
+ // Read the files and directories into sets
+ std::set<std::string> localFiles;
+ std::set<std::string> localDirs;
+ struct dirent *localDirEn = 0;
+ while((localDirEn = readdir(dirhandle)) != 0)
+ {
+ // Not . and ..!
+ if(localDirEn->d_name[0] == '.' &&
+ (localDirEn->d_name[1] == '\0' || (localDirEn->d_name[1] == '.' && localDirEn->d_name[2] == '\0')))
+ {
+ // ignore, it's . or ..
+
+#ifdef HAVE_VALID_DIRENT_D_TYPE
+ if (localDirEn->d_type != DT_DIR)
+ {
+ BOX_ERROR("d_type does not really "
+ "work on your platform. "
+ "Reconfigure Box!");
+ return;
+ }
+#endif
+
+ continue;
+ }
+
+ std::string localDirPath(MakeFullPath(rLocalDir,
+ localDirEn->d_name));
+ std::string storeDirPath(rStoreDir + "/" +
+ localDirEn->d_name);
+
+#ifndef HAVE_VALID_DIRENT_D_TYPE
+ EMU_STRUCT_STAT st;
+ if(EMU_LSTAT(localDirPath.c_str(), &st) != 0)
+ {
+ // Check whether dir is excluded before trying
+ // to stat it, to fix problems with .gvfs
+ // directories that are not readable by root
+ // causing compare to crash:
+ // http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000013.html
+ if(rParams.IsExcludedDir(localDirPath))
+ {
+ rParams.NotifyExcludedDir(localDirPath,
+ storeDirPath);
+ continue;
+ }
+ else
+ {
+ THROW_EXCEPTION_MESSAGE(CommonException,
+ OSFileError, localDirPath);
+ }
+ }
+
+ // Entry -- file or dir?
+ if(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ {
+ // File or symbolic link
+ localFiles.insert(std::string(localDirEn->d_name));
+ }
+ else if(S_ISDIR(st.st_mode))
+ {
+ // Directory
+ localDirs.insert(std::string(localDirEn->d_name));
+ }
+#else
+ // Entry -- file or dir?
+ if(localDirEn->d_type == DT_REG || localDirEn->d_type == DT_LNK)
+ {
+ // File or symbolic link
+ localFiles.insert(std::string(localDirEn->d_name));
+ }
+ else if(localDirEn->d_type == DT_DIR)
+ {
+ // Directory
+ localDirs.insert(std::string(localDirEn->d_name));
+ }
+#endif
+ }
+ // Close directory
+ if(::closedir(dirhandle) != 0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to close local directory "
+ "'" << rLocalDir << "'");
+ }
+ dirhandle = 0;
+
+ // Do the same for the store directories
+ std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeFiles;
+ std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeDirs;
+
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *storeDirEn = 0;
+ while((storeDirEn = i.Next()) != 0)
+ {
+ // Decrypt filename
+ BackupStoreFilenameClear name(storeDirEn->GetName());
+
+ // What is it?
+ if((storeDirEn->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == BackupStoreDirectory::Entry::Flags_File)
+ {
+ // File
+ storeFiles.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn));
+ }
+ else
+ {
+ // Dir
+ storeDirs.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn));
+ }
+ }
+
+#ifdef _MSC_VER
+ typedef std::set<std::string>::iterator string_set_iter_t;
+#else
+ typedef std::set<std::string>::const_iterator string_set_iter_t;
+#endif
+
+ // Now compare files.
+ for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeFiles.begin(); i != storeFiles.end(); ++i)
+ {
+ const std::string& fileName(i->first);
+
+ std::string localPath(MakeFullPath(rLocalDir, fileName));
+ std::string storePath(rStoreDir + "/" + fileName);
+
+ rParams.NotifyFileComparing(localPath, storePath);
+
+ // Does the file exist locally?
+ string_set_iter_t local(localFiles.find(fileName));
+ if(local == localFiles.end())
+ {
+ // Not found -- report
+ rParams.NotifyLocalFileMissing(localPath,
+ storePath);
+ }
+ else
+ {
+ int64_t fileSize = 0;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(localPath.c_str(), &st) == 0)
+ {
+ fileSize = st.st_size;
+ }
+
+ try
+ {
+ // Files the same flag?
+ bool equal = true;
+
+ // File modified after last sync flag
+ bool modifiedAfterLastSync = false;
+
+ bool hasDifferentAttribs = false;
+
+ if(rParams.QuickCompare())
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetBlockIndexByID(i->second->GetObjectID());
+
+ // Stream containing block index
+ std::auto_ptr<IOStream> blockIndexStream(mrConnection.ReceiveStream());
+
+ // Compare
+ equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(localPath.c_str(), *blockIndexStream, mrConnection.GetTimeout());
+ }
+ else
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetFile(DirID, i->second->GetObjectID());
+
+ // Stream containing encoded file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+
+ // Decode it
+ std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream;
+ // Got additional attributes?
+ if(i->second->HasAttributes())
+ {
+ // Use these attributes
+ const StreamableMemBlock &storeAttr(i->second->GetAttributes());
+ BackupClientFileAttributes attr(storeAttr);
+ fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout(), &attr).release());
+ }
+ else
+ {
+ // Use attributes stored in file
+ fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout()).release());
+ }
+
+ // Should always be something in the auto_ptr, it's how the interface is defined. But be paranoid.
+ if(!fileOnServerStream.get())
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ // Compare attributes
+ BackupClientFileAttributes localAttr;
+ box_time_t fileModTime = 0;
+ localAttr.ReadAttributes(localPath.c_str(), false /* don't zero mod times */, &fileModTime);
+ modifiedAfterLastSync = (fileModTime > rParams.LatestFileUploadTime());
+ bool ignoreAttrModTime = true;
+
+ #ifdef WIN32
+ // attr mod time is really
+ // creation time, so check it
+ ignoreAttrModTime = false;
+ #endif
+
+ if(!rParams.IgnoreAttributes() &&
+ #ifdef PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE
+ !fileOnServerStream->IsSymLink() &&
+ #endif
+ !localAttr.Compare(fileOnServerStream->GetAttributes(),
+ ignoreAttrModTime,
+ fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */))
+ {
+ hasDifferentAttribs = true;
+ }
+
+ // Compare contents, if it's a regular file not a link
+ // Remember, we MUST read the entire stream from the server.
+ SelfFlushingStream flushObject(*objectStream);
+
+ if(!fileOnServerStream->IsSymLink())
+ {
+ SelfFlushingStream flushFile(*fileOnServerStream);
+ // Open the local file
+ FileStream l(localPath.c_str());
+ equal = l.CompareWith(*fileOnServerStream,
+ mrConnection.GetTimeout());
+ }
+ }
+
+ rParams.NotifyFileCompared(localPath,
+ storePath, fileSize,
+ hasDifferentAttribs, !equal,
+ modifiedAfterLastSync,
+ i->second->HasAttributes());
+ }
+ catch(BoxException &e)
+ {
+ rParams.NotifyDownloadFailed(localPath,
+ storePath, fileSize, e);
+ }
+ catch(std::exception &e)
+ {
+ rParams.NotifyDownloadFailed(localPath,
+ storePath, fileSize, e);
+ }
+ catch(...)
+ {
+ rParams.NotifyDownloadFailed(localPath,
+ storePath, fileSize);
+ }
+
+ // Remove from set so that we know it's been compared
+ localFiles.erase(local);
+ }
+ }
+
+ // Report any files which exist locally, but not on the store
+ for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i)
+ {
+ std::string localPath(MakeFullPath(rLocalDir, *i));
+ std::string storePath(rStoreDir + "/" + *i);
+
+ // Should this be ignored (ie is excluded)?
+ if(!rParams.IsExcludedFile(localPath))
+ {
+ bool modifiedAfterLastSync = false;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(localPath.c_str(), &st) == 0)
+ {
+ if(FileModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+ }
+
+ rParams.NotifyRemoteFileMissing(localPath,
+ storePath, modifiedAfterLastSync);
+ }
+ else
+ {
+ rParams.NotifyExcludedFile(localPath,
+ storePath);
+ }
+ }
+
+ // Finished with the files, clear the sets to reduce memory usage slightly
+ localFiles.clear();
+ storeFiles.clear();
+
+ // Now do the directories, recursively to check subdirectories
+ for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i)
+ {
+ std::string localPath(MakeFullPath(rLocalDir, i->first));
+ std::string storePath(rStoreDir + "/" + i->first);
+
+ // Does the directory exist locally?
+ string_set_iter_t local(localDirs.find(i->first));
+ if(local == localDirs.end() &&
+ rParams.IsExcludedDir(localPath))
+ {
+ rParams.NotifyExcludedFileNotDeleted(localPath,
+ storePath);
+ }
+ else if(local == localDirs.end())
+ {
+ // Not found -- report
+ rParams.NotifyLocalFileMissing(localPath,
+ storePath);
+ }
+ else if(rParams.IsExcludedDir(localPath))
+ {
+ // don't recurse into excluded directories
+ }
+ else
+ {
+ // Compare directory
+ Compare(i->second->GetObjectID(),
+ storePath, localPath, rParams);
+
+ // Remove from set so that we know it's been compared
+ localDirs.erase(local);
+ }
+ }
+
+ // Report any directories which exist locally, but not on the store
+ for(std::set<std::string>::const_iterator
+ i = localDirs.begin();
+ i != localDirs.end(); ++i)
+ {
+ std::string localPath(MakeFullPath(rLocalDir, *i));
+ std::string storePath(rStoreDir + "/" + *i);
+
+ // Should this be ignored (ie is excluded)?
+ if(!rParams.IsExcludedDir(localPath))
+ {
+ bool modifiedAfterLastSync = false;
+
+ // Check the dir modification time
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(localPath.c_str(), &st) == 0 &&
+ FileModificationTime(st) >
+ rParams.LatestFileUploadTime())
+ {
+ modifiedAfterLastSync = true;
+ }
+
+ rParams.NotifyRemoteFileMissing(localPath,
+ storePath, modifiedAfterLastSync);
+ }
+ else
+ {
+ rParams.NotifyExcludedDir(localPath, storePath);
+ }
+ }
+ }
+ catch(...)
+ {
+ if(dirhandle != 0)
+ {
+ ::closedir(dirhandle);
+ }
+ throw;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandRestore(const std::vector<std::string> &, const bool *)
+// Purpose: Restore a directory
+// Created: 23/11/03
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandRestore(const std::vector<std::string> &args, const bool *opts)
+{
+ // Check arguments
+ if(args.size() != 2)
+ {
+ BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> <local-name>");
+ return;
+ }
+
+ // Restoring deleted things?
+ bool restoreDeleted = opts['d'];
+
+ std::string storeDirEncoded;
+
+ // Get directory ID
+ int64_t dirID = 0;
+ if(opts['i'])
+ {
+ // Specified as ID.
+ dirID = ::strtoll(args[0].c_str(), 0, 16);
+ if(dirID == std::numeric_limits<long long>::min() || dirID == std::numeric_limits<long long>::max() || dirID == 0)
+ {
+ BOX_ERROR("Not a valid object ID (specified in hex)");
+ 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<std::string> &args)
+// Purpose: Display help on commands
+// Created: 15/2/04
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandHelp(const std::vector<std::string> &args)
+{
+ if(args.size() == 0)
+ {
+ // Display a list of all commands
+ printf("Available commands are:\n");
+ for(int c = 0; help_commands[c] != 0; ++c)
+ {
+ printf(" %s\n", help_commands[c]);
+ }
+ printf("Type \"help <command>\" for more information on a command.\n\n");
+ }
+ else
+ {
+ // Display help on a particular command
+ int c;
+ for(c = 0; help_commands[c] != 0; ++c)
+ {
+ if(::strcmp(help_commands[c], args[0].c_str()) == 0)
+ {
+ // Found the command, print help
+ printf("\n%s\n", help_text[c]);
+ break;
+ }
+ }
+ if(help_commands[c] == 0)
+ {
+ printf("No help found for command '%s'\n", args[0].c_str());
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandUsage()
+// Purpose: Display storage space used on server
+// Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandUsage(const bool *opts)
+{
+ bool MachineReadable = opts['m'];
+
+ // Request full details from the server
+ std::auto_ptr<BackupProtocolClientAccountUsage> usage(mrConnection.QueryGetAccountUsage());
+
+ // Display each entry in turn
+ int64_t hardLimit = usage->GetBlocksHardLimit();
+ int32_t blockSize = usage->GetBlockSize();
+ CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit,
+ blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(),
+ hardLimit, blockSize, MachineReadable);
+ CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize,
+ MachineReadable);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandUsageDisplayEntry(const char *,
+// int64_t, int64_t, int32_t, bool)
+// Purpose: Display an entry in the usage table
+// Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size,
+int64_t HardLimit, int32_t BlockSize, bool MachineReadable)
+{
+ std::cout << FormatUsageLineStart(Name, MachineReadable) <<
+ FormatUsageBar(Size, Size * BlockSize, HardLimit * BlockSize,
+ MachineReadable) << std::endl;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupQueries::CommandUndelete(const std::vector<std::string> &, const bool *)
+// Purpose: Undelete a directory
+// Created: 23/11/03
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const bool *opts)
+{
+ if (!mReadWrite)
+ {
+ BOX_ERROR("This command requires a read-write connection. "
+ "Please reconnect with the -w option.");
+ return;
+ }
+
+ // Check arguments
+ if(args.size() != 1)
+ {
+ BOX_ERROR("Incorrect usage. undelete <name> or undelete -i <object-id>");
+ return;
+ }
+
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return;
+#else
+ const std::string& storeDirEncoded(args[0]);
+#endif
+
+ // Find object ID somehow
+ int64_t fileId, parentId;
+ std::string fileName;
+ int16_t flagsOut;
+
+ fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
+ /* include files and directories */
+ 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<std::string> &, const bool *)
+// Purpose: Deletes a file
+// Created: 23/11/03
+//
+// --------------------------------------------------------------------------
+void BackupQueries::CommandDelete(const std::vector<std::string> &args,
+ const bool *opts)
+{
+ if (!mReadWrite)
+ {
+ BOX_ERROR("This command requires a read-write connection. "
+ "Please reconnect with the -w option.");
+ return;
+ }
+
+ // Check arguments
+ if(args.size() != 1)
+ {
+ BOX_ERROR("Incorrect usage. delete <name>");
+ return;
+ }
+
+#ifdef WIN32
+ std::string storeDirEncoded;
+ if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return;
+#else
+ const std::string& storeDirEncoded(args[0]);
+#endif
+
+ // Find object ID somehow
+ int64_t fileId, parentId;
+ std::string fileName;
+ int16_t flagsOut;
+
+ fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
+ /* include files and directories */
+ 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 <vector>
+#include <string>
+
+#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<std::string> &args, const bool *opts);
+ void CommandChangeDir(const std::vector<std::string> &args, const bool *opts);
+ void CommandChangeLocalDir(const std::vector<std::string> &args);
+ void CommandGetObject(const std::vector<std::string> &args, const bool *opts);
+ void CommandGet(std::vector<std::string> args, const bool *opts);
+ void CommandCompare(const std::vector<std::string> &args, const bool *opts);
+ void CommandRestore(const std::vector<std::string> &args, const bool *opts);
+ void CommandUndelete(const std::vector<std::string> &args, const bool *opts);
+ void CommandDelete(const std::vector<std::string> &args,
+ const bool *opts);
+ void CommandUsage(const bool *opts);
+ void CommandUsageDisplayEntry(const char *Name, int64_t Size,
+ int64_t HardLimit, int32_t BlockSize, bool MachineReadable);
+ void CommandHelp(const std::vector<std::string> &args);
+
+ // 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<std::pair<std::string, int64_t> > *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<std::pair<std::string, int64_t> > 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 <memory>
+#include <string>
+
+#include "BoxTime.h"
+#include "ExcludeList.h"
+#include "BackupClientMakeExcludeList.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BoxBackupCompareParams
+// Purpose: Parameters and notifiers for a compare operation
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+class BoxBackupCompareParams
+{
+private:
+ std::auto_ptr<const ExcludeList> mapExcludeFiles, mapExcludeDirs;
+ bool mQuickCompare;
+ bool mIgnoreExcludes;
+ bool mIgnoreAttributes;
+ box_time_t mLatestFileUploadTime;
+
+public:
+ BoxBackupCompareParams(bool QuickCompare, bool IgnoreExcludes,
+ bool IgnoreAttributes, box_time_t LatestFileUploadTime)
+ : mQuickCompare(QuickCompare),
+ mIgnoreExcludes(IgnoreExcludes),
+ mIgnoreAttributes(IgnoreAttributes),
+ mLatestFileUploadTime(LatestFileUploadTime)
+ { }
+
+ virtual ~BoxBackupCompareParams() { }
+
+ bool QuickCompare() { return mQuickCompare; }
+ bool IgnoreExcludes() { return mIgnoreExcludes; }
+ bool IgnoreAttributes() { return mIgnoreAttributes; }
+ box_time_t LatestFileUploadTime() { return mLatestFileUploadTime; }
+
+ void LoadExcludeLists(const Configuration& rLoc)
+ {
+ mapExcludeFiles.reset(BackupClientMakeExcludeList_Files(rLoc));
+ mapExcludeDirs.reset(BackupClientMakeExcludeList_Dirs(rLoc));
+ }
+ bool IsExcludedFile(const std::string& rLocalPath)
+ {
+ if (!mapExcludeFiles.get()) return false;
+ return mapExcludeFiles->IsExcluded(rLocalPath);
+ }
+ bool IsExcludedDir(const std::string& rLocalPath)
+ {
+ if (!mapExcludeDirs.get()) return false;
+ return mapExcludeDirs->IsExcluded(rLocalPath);
+ }
+
+ virtual void NotifyLocalDirMissing(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyLocalDirAccessFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyStoreDirMissingAttributes(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyRemoteFileMissing(const std::string& rLocalPath,
+ const std::string& rRemotePath,
+ bool modifiedAfterLastSync) = 0;
+ virtual void NotifyLocalFileMissing(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyExcludedFileNotDeleted(const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
+ virtual void NotifyDownloadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ BoxException& rException) = 0;
+ virtual void 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 <unistd.h>
+#endif
+
+#include <errno.h>
+#include <cstdio>
+#include <cstdlib>
+
+#ifdef HAVE_SYS_TYPES_H
+ #include <sys/types.h>
+#endif
+
+#ifdef HAVE_LIBREADLINE
+ #ifdef HAVE_READLINE_READLINE_H
+ #include <readline/readline.h>
+ #elif defined(HAVE_EDITLINE_READLINE_H)
+ #include <editline/readline.h>
+ #elif defined(HAVE_READLINE_H)
+ #include <readline.h>
+ #endif
+#endif
+#ifdef HAVE_READLINE_HISTORY
+ #ifdef HAVE_READLINE_HISTORY_H
+ #include <readline/history.h>
+ #elif defined(HAVE_HISTORY_H)
+ #include <history.h>
+ #endif
+#endif
+
+#include <cstdlib>
+
+#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<level>] [-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> 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<Configuration> 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<BackupProtocolClientVersion> 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 h