boxbackup/0000775000175000017500000000000011652362374013323 5ustar siretartsiretartboxbackup/LICENSE-DUAL.txt0000664000175000017500000000545411345265741015700 0ustar siretartsiretartBox 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] boxbackup/lib/0000775000175000017500000000000011652362374014071 5ustar siretartsiretartboxbackup/lib/win32/0000775000175000017500000000000011652362374015033 5ustar siretartsiretartboxbackup/lib/win32/emu.cpp0000664000175000017500000012131511445743753016334 0ustar siretartsiretart// Box Backup Win32 native port by Nick Knight // Need at least 0x0500 to use GetFileSizeEx on Cygwin/MinGW #define WINVER 0x0500 #include "emu.h" #ifdef WIN32 #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include // message resource definitions for syslog() #include "messages.h" DWORD winerrno; struct passwd gTempPasswd; bool EnableBackupRights() { HANDLE hToken; TOKEN_PRIVILEGES token_priv; //open current process to adjust privileges if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { ::syslog(LOG_ERR, "Failed to open process token: %s", GetErrorMessage(GetLastError()).c_str()); return false; } //let's build the token privilege struct - //first, look up the LUID for the backup privilege if (!LookupPrivilegeValue( NULL, //this system SE_BACKUP_NAME, //the name of the privilege &( token_priv.Privileges[0].Luid ))) //result { ::syslog(LOG_ERR, "Failed to lookup backup privilege: %s", GetErrorMessage(GetLastError()).c_str()); CloseHandle(hToken); return false; } token_priv.PrivilegeCount = 1; token_priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // now set the privilege // because we're going exit right after dumping the streams, there isn't // any need to save current state if (!AdjustTokenPrivileges( hToken, //our process token false, //we're not disabling everything &token_priv, //address of structure sizeof(token_priv), //size of structure NULL, NULL)) //don't save current state { //this function is a little tricky - if we were adjusting //more than one privilege, it could return success but not //adjust them all - in the general case, you need to trap this ::syslog(LOG_ERR, "Failed to enable backup privilege: %s", GetErrorMessage(GetLastError()).c_str()); CloseHandle(hToken); return false; } CloseHandle(hToken); return true; } // forward declaration char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage); // -------------------------------------------------------------------------- // // Function // Name: GetDefaultConfigFilePath(std::string name) // Purpose: Calculates the default configuration file name, // by using the directory location of the currently // executing program, and appending the provided name. // In case of fire, returns an empty string. // Created: 26th May 2007 // // -------------------------------------------------------------------------- std::string GetDefaultConfigFilePath(const std::string& rName) { WCHAR exePathWide[MAX_PATH]; GetModuleFileNameW(NULL, exePathWide, MAX_PATH-1); char* exePathUtf8 = ConvertFromWideString(exePathWide, CP_UTF8); if (exePathUtf8 == NULL) { return ""; } std::string configfile = exePathUtf8; delete [] exePathUtf8; // make the default config file name, // based on the program path configfile = configfile.substr(0, configfile.rfind('\\')); configfile += "\\"; configfile += rName; return configfile; } // -------------------------------------------------------------------------- // // Function // Name: ConvertToWideString // Purpose: Converts a string from specified codepage to // a wide string (WCHAR*). Returns a buffer which // MUST be freed by the caller with delete[]. // In case of fire, logs the error and returns NULL. // Created: 4th February 2006 // // -------------------------------------------------------------------------- WCHAR* ConvertToWideString(const char* pString, unsigned int codepage, bool logErrors) { int len = MultiByteToWideChar ( codepage, // source code page 0, // character-type options pString, // string to map -1, // number of bytes in string - auto detect NULL, // wide-character buffer 0 // size of buffer - work out // how much space we need ); if (len == 0) { winerrno = GetLastError(); if (logErrors) { ::syslog(LOG_WARNING, "Failed to convert string to wide string: " "%s", GetErrorMessage(winerrno).c_str()); } errno = EINVAL; return NULL; } WCHAR* buffer = new WCHAR[len]; if (buffer == NULL) { if (logErrors) { ::syslog(LOG_WARNING, "Failed to convert string to wide string: " "out of memory"); } winerrno = ERROR_OUTOFMEMORY; errno = ENOMEM; return NULL; } len = MultiByteToWideChar ( codepage, // source code page 0, // character-type options pString, // string to map -1, // number of bytes in string - auto detect buffer, // wide-character buffer len // size of buffer ); if (len == 0) { winerrno = GetLastError(); if (logErrors) { ::syslog(LOG_WARNING, "Failed to convert string to wide string: " "%s", GetErrorMessage(winerrno).c_str()); } errno = EACCES; delete [] buffer; return NULL; } return buffer; } // -------------------------------------------------------------------------- // // Function // Name: ConvertUtf8ToWideString // Purpose: Converts a string from UTF-8 to a wide string. // Returns a buffer which MUST be freed by the caller // with delete[]. // In case of fire, logs the error and returns NULL. // Created: 4th February 2006 // // -------------------------------------------------------------------------- WCHAR* ConvertUtf8ToWideString(const char* pString) { return ConvertToWideString(pString, CP_UTF8, true); } // -------------------------------------------------------------------------- // // Function // Name: ConvertFromWideString // Purpose: Converts a wide string to a narrow string in the // specified code page. Returns a buffer which MUST // be freed by the caller with delete[]. // In case of fire, logs the error and returns NULL. // Created: 4th February 2006 // // -------------------------------------------------------------------------- char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage) { int len = WideCharToMultiByte ( codepage, // destination code page 0, // character-type options pString, // string to map -1, // number of bytes in string - auto detect NULL, // output buffer 0, // size of buffer - work out // how much space we need NULL, // replace unknown chars with system default NULL // don't tell us when that happened ); if (len == 0) { ::syslog(LOG_WARNING, "Failed to convert wide string to narrow: " "error %d", GetLastError()); errno = EINVAL; return NULL; } char* buffer = new char[len]; if (buffer == NULL) { ::syslog(LOG_WARNING, "Failed to convert wide string to narrow: " "out of memory"); errno = ENOMEM; return NULL; } len = WideCharToMultiByte ( codepage, // source code page 0, // character-type options pString, // string to map -1, // number of bytes in string - auto detect buffer, // output buffer len, // size of buffer NULL, // replace unknown chars with system default NULL // don't tell us when that happened ); if (len == 0) { ::syslog(LOG_WARNING, "Failed to convert wide string to narrow: " "error %i", GetLastError()); errno = EACCES; delete [] buffer; return NULL; } return buffer; } // -------------------------------------------------------------------------- // // Function // Name: ConvertEncoding(const std::string&, int, // std::string&, int) // Purpose: Converts a string from one code page to another. // On success, replaces contents of rDest and returns // true. In case of fire, logs the error and returns // false. // Created: 15th October 2006 // // -------------------------------------------------------------------------- bool ConvertEncoding(const std::string& rSource, int sourceCodePage, std::string& rDest, int destCodePage) { WCHAR* pWide = ConvertToWideString(rSource.c_str(), sourceCodePage, true); if (pWide == NULL) { ::syslog(LOG_ERR, "Failed to convert string '%s' from " "current code page %d to wide string: %s", rSource.c_str(), sourceCodePage, GetErrorMessage(GetLastError()).c_str()); return false; } char* pConsole = ConvertFromWideString(pWide, destCodePage); delete [] pWide; if (!pConsole) { // Error should have been logged by ConvertFromWideString return false; } rDest = pConsole; delete [] pConsole; return true; } bool ConvertToUtf8(const std::string& rSource, std::string& rDest, int sourceCodePage) { return ConvertEncoding(rSource, sourceCodePage, rDest, CP_UTF8); } bool ConvertFromUtf8(const std::string& rSource, std::string& rDest, int destCodePage) { return ConvertEncoding(rSource, CP_UTF8, rDest, destCodePage); } bool ConvertConsoleToUtf8(const std::string& rSource, std::string& rDest) { return ConvertToUtf8(rSource, rDest, GetConsoleCP()); } bool ConvertUtf8ToConsole(const std::string& rSource, std::string& rDest) { return ConvertFromUtf8(rSource, rDest, GetConsoleOutputCP()); } // -------------------------------------------------------------------------- // // Function // Name: ConvertPathToAbsoluteUnicode // Purpose: Converts relative paths to absolute (with unicode marker) // Created: 4th February 2006 // // -------------------------------------------------------------------------- std::string ConvertPathToAbsoluteUnicode(const char *pFileName) { std::string filename; for (int i = 0; pFileName[i] != 0; i++) { if (pFileName[i] == '/') { filename += '\\'; } else { filename += pFileName[i]; } } std::string tmpStr("\\\\?\\"); // Is the path relative or absolute? // Absolute paths on Windows are always a drive letter // followed by ':' char wd[PATH_MAX]; if (::getcwd(wd, PATH_MAX) == 0) { ::syslog(LOG_WARNING, "Failed to open '%s': path too long", pFileName); errno = ENAMETOOLONG; winerrno = ERROR_INVALID_NAME; tmpStr = ""; return tmpStr; } if (filename.length() > 2 && filename[0] == '\\' && filename[1] == '\\') { tmpStr += "UNC\\"; filename.replace(0, 2, ""); // \\?\UNC\\ // see http://msdn2.microsoft.com/en-us/library/aa365247.aspx } else if (filename.length() >= 1 && filename[0] == '\\') { // root directory of current drive. tmpStr = wd; tmpStr.resize(2); // drive letter and colon } else if (filename.length() >= 2 && filename[1] != ':') { // Must be relative. We need to get the // current directory to make it absolute. tmpStr += wd; if (tmpStr[tmpStr.length()] != '\\') { tmpStr += '\\'; } } tmpStr += filename; // We are using direct filename access, which does not support .., // so we need to implement it ourselves. for (std::string::size_type i = 1; i < tmpStr.size() - 3; i++) { if (tmpStr.substr(i, 3) == "\\..") { std::string::size_type lastSlash = tmpStr.rfind('\\', i - 1); if (lastSlash == std::string::npos) { // no previous directory, ignore it, // CreateFile will fail with error 123 } else { tmpStr.replace(lastSlash, i + 3 - lastSlash, ""); } i = lastSlash; } } return tmpStr; } std::string GetErrorMessage(DWORD errorCode) { char* pMsgBuf = NULL; DWORD chars = FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char *)(&pMsgBuf), 0, NULL ); if (chars == 0 || pMsgBuf == NULL) { return std::string("failed to get error message"); } // remove embedded newline pMsgBuf[chars - 1] = 0; pMsgBuf[chars - 2] = 0; std::ostringstream line; line << pMsgBuf << " (" << errorCode << ")"; LocalFree(pMsgBuf); return line.str(); } // -------------------------------------------------------------------------- // // Function // Name: openfile // Purpose: replacement for any open calls - handles unicode // filenames - supplied in utf8 // Created: 25th October 2004 // // -------------------------------------------------------------------------- HANDLE openfile(const char *pFileName, int flags, int mode) { winerrno = ERROR_INVALID_FUNCTION; std::string AbsPathWithUnicode = ConvertPathToAbsoluteUnicode(pFileName); if (AbsPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() return INVALID_HANDLE_VALUE; } WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); // We are responsible for freeing pBuffer if (pBuffer == NULL) { // error already logged by ConvertUtf8ToWideString() return INVALID_HANDLE_VALUE; } // flags could be O_WRONLY | O_CREAT | O_RDONLY DWORD createDisposition = OPEN_EXISTING; DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; DWORD accessRights = FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_READ_EA; if (flags & O_WRONLY) { accessRights = FILE_WRITE_DATA; } else if (flags & O_RDWR) { accessRights |= FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA; } if (flags & O_CREAT) { createDisposition = OPEN_ALWAYS; } if (flags & O_TRUNC) { createDisposition = CREATE_ALWAYS; } if ((flags & O_CREAT) && (flags & O_EXCL)) { createDisposition = CREATE_NEW; } if (flags & O_LOCK) { shareMode = 0; } DWORD winFlags = FILE_FLAG_BACKUP_SEMANTICS; if (flags & O_TEMPORARY) { winFlags |= FILE_FLAG_DELETE_ON_CLOSE; } HANDLE hdir = CreateFileW(pBuffer, accessRights, shareMode, NULL, createDisposition, winFlags, NULL); delete [] pBuffer; if (hdir == INVALID_HANDLE_VALUE) { winerrno = GetLastError(); switch(winerrno) { case ERROR_SHARING_VIOLATION: errno = EBUSY; break; default: errno = EINVAL; } ::syslog(LOG_WARNING, "Failed to open file '%s': " "%s", pFileName, GetErrorMessage(GetLastError()).c_str()); return INVALID_HANDLE_VALUE; } if (flags & O_APPEND) { if (SetFilePointer(hdir, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) { winerrno = GetLastError(); errno = EINVAL; CloseHandle(hdir); return INVALID_HANDLE_VALUE; } } winerrno = NO_ERROR; return hdir; } // -------------------------------------------------------------------------- // // Function // Name: emu_fstat // Purpose: replacement for fstat. Supply a windows handle. // Returns a struct emu_stat to have room for 64-bit // file identifier in st_ino (mingw allows only 16!) // Created: 25th October 2004 // // -------------------------------------------------------------------------- int emu_fstat(HANDLE hdir, struct emu_stat * st) { if (hdir == INVALID_HANDLE_VALUE) { ::syslog(LOG_ERR, "Error: invalid file handle in emu_fstat()"); errno = EBADF; return -1; } BY_HANDLE_FILE_INFORMATION fi; if (!GetFileInformationByHandle(hdir, &fi)) { ::syslog(LOG_WARNING, "Failed to read file information: " "%s", GetErrorMessage(GetLastError()).c_str()); errno = EACCES; return -1; } if (INVALID_FILE_ATTRIBUTES == fi.dwFileAttributes) { ::syslog(LOG_WARNING, "Failed to get file attributes: " "%s", GetErrorMessage(GetLastError()).c_str()); errno = EACCES; return -1; } memset(st, 0, sizeof(*st)); // This is how we get our INODE (equivalent) information ULARGE_INTEGER conv; conv.HighPart = fi.nFileIndexHigh; conv.LowPart = fi.nFileIndexLow; st->st_ino = conv.QuadPart; // get the time information st->st_ctime = ConvertFileTimeToTime_t(&fi.ftCreationTime); st->st_atime = ConvertFileTimeToTime_t(&fi.ftLastAccessTime); st->st_mtime = ConvertFileTimeToTime_t(&fi.ftLastWriteTime); if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { st->st_size = 0; } else { conv.HighPart = fi.nFileSizeHigh; conv.LowPart = fi.nFileSizeLow; st->st_size = (_off_t)conv.QuadPart; } // at the mo st->st_uid = 0; st->st_gid = 0; st->st_nlink = 1; // the mode of the file // mode zero will make it impossible to restore on Unix // (no access to anybody, including the owner). // we'll fake a sensible mode: // all objects get user read (0400) // if it's a directory it gets user execute (0100) // if it's not read-only it gets user write (0200) st->st_mode = S_IREAD; if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { st->st_mode |= S_IFDIR | S_IEXEC; } else { st->st_mode |= S_IFREG; } if (!(fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { st->st_mode |= S_IWRITE; } // st_dev is normally zero, regardless of the drive letter, // since backup locations can't normally span drives. However, // a reparse point does allow all kinds of weird stuff to happen. // We set st_dev to 1 for a reparse point, so that Box will detect // a change of device number (from 0) and refuse to recurse down // the reparse point (which could lead to havoc). if (fi.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { st->st_dev = 1; } else { st->st_dev = 0; } return 0; } // -------------------------------------------------------------------------- // // Function // Name: OpenFileByNameUtf8 // Purpose: Converts filename to Unicode and returns // a handle to it. In case of error, sets errno, // logs the error and returns NULL. // Created: 10th December 2004 // // -------------------------------------------------------------------------- HANDLE OpenFileByNameUtf8(const char* pFileName, DWORD flags) { std::string AbsPathWithUnicode = ConvertPathToAbsoluteUnicode(pFileName); if (AbsPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() return NULL; } WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); // We are responsible for freeing pBuffer if (pBuffer == NULL) { // error already logged by ConvertUtf8ToWideString() return NULL; } HANDLE handle = CreateFileW(pBuffer, flags, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (handle == INVALID_HANDLE_VALUE) { // if our open fails we should always be able to // open in this mode - to get the inode information // at least one process must have the file open - // in this case someone else does. handle = CreateFileW(pBuffer, READ_CONTROL, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } delete [] pBuffer; if (handle == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { errno = ENOENT; } else { ::syslog(LOG_WARNING, "Failed to open '%s': " "%s", pFileName, GetErrorMessage(err).c_str()); errno = EACCES; } return NULL; } return handle; } // -------------------------------------------------------------------------- // // Function // Name: emu_stat // Purpose: replacement for the lstat and stat functions. // Works with unicode filenames supplied in utf8. // Returns a struct emu_stat to have room for 64-bit // file identifier in st_ino (mingw allows only 16!) // Created: 25th October 2004 // // -------------------------------------------------------------------------- int emu_stat(const char * pName, struct emu_stat * st) { HANDLE handle = OpenFileByNameUtf8(pName, FILE_READ_ATTRIBUTES | FILE_READ_EA); if (handle == NULL) { // errno already set and error logged by OpenFileByNameUtf8() return -1; } int retVal = emu_fstat(handle, st); if (retVal != 0) { // error logged, but without filename ::syslog(LOG_WARNING, "Failed to get file information " "for '%s'", pName); } // close the handle CloseHandle(handle); return retVal; } // -------------------------------------------------------------------------- // // Function // Name: statfs // Purpose: returns the mount point of where a file is located - // in this case the volume serial number // Created: 25th October 2004 // // -------------------------------------------------------------------------- int statfs(const char * pName, struct statfs * s) { HANDLE handle = OpenFileByNameUtf8(pName, FILE_READ_ATTRIBUTES | FILE_READ_EA); if (handle == NULL) { // errno already set and error logged by OpenFileByNameUtf8() return -1; } BY_HANDLE_FILE_INFORMATION fi; if (!GetFileInformationByHandle(handle, &fi)) { ::syslog(LOG_WARNING, "Failed to get file information " "for '%s': %s", pName, GetErrorMessage(GetLastError()).c_str()); CloseHandle(handle); errno = EACCES; return -1; } // convert volume serial number to a string _ui64toa(fi.dwVolumeSerialNumber, s->f_mntonname + 1, 16); // pseudo unix mount point s->f_mntonname[0] = '\\'; CloseHandle(handle); // close the handle return 0; } // -------------------------------------------------------------------------- // // Function // Name: emu_utimes // Purpose: replacement for the POSIX utimes() function, // works with unicode filenames supplied in utf8 format, // sets creation time instead of last access time. // Created: 25th July 2006 // // -------------------------------------------------------------------------- int emu_utimes(const char * pName, const struct timeval times[]) { FILETIME creationTime; if (!ConvertTime_tToFileTime(times[0].tv_sec, &creationTime)) { errno = EINVAL; return -1; } FILETIME modificationTime; if (!ConvertTime_tToFileTime(times[1].tv_sec, &modificationTime)) { errno = EINVAL; return -1; } HANDLE handle = OpenFileByNameUtf8(pName, FILE_WRITE_ATTRIBUTES); if (handle == NULL) { // errno already set and error logged by OpenFileByNameUtf8() return -1; } if (!SetFileTime(handle, &creationTime, NULL, &modificationTime)) { ::syslog(LOG_ERR, "Failed to set times on '%s': %s", pName, GetErrorMessage(GetLastError()).c_str()); CloseHandle(handle); return 1; } CloseHandle(handle); return 0; } // -------------------------------------------------------------------------- // // Function // Name: emu_chmod // Purpose: replacement for the POSIX chmod function, // works with unicode filenames supplied in utf8 format // Created: 26th July 2006 // // -------------------------------------------------------------------------- int emu_chmod(const char * pName, mode_t mode) { std::string AbsPathWithUnicode = ConvertPathToAbsoluteUnicode(pName); if (AbsPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() return -1; } WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); // We are responsible for freeing pBuffer if (pBuffer == NULL) { // error already logged by ConvertUtf8ToWideString() free(pBuffer); return -1; } DWORD attribs = GetFileAttributesW(pBuffer); if (attribs == INVALID_FILE_ATTRIBUTES) { ::syslog(LOG_ERR, "Failed to get file attributes of '%s': %s", pName, GetErrorMessage(GetLastError()).c_str()); errno = EACCES; free(pBuffer); return -1; } if (mode & S_IWRITE) { attribs &= ~FILE_ATTRIBUTE_READONLY; } else { attribs |= FILE_ATTRIBUTE_READONLY; } if (!SetFileAttributesW(pBuffer, attribs)) { ::syslog(LOG_ERR, "Failed to set file attributes of '%s': %s", pName, GetErrorMessage(GetLastError()).c_str()); errno = EACCES; free(pBuffer); return -1; } delete [] pBuffer; return 0; } // -------------------------------------------------------------------------- // // Function // Name: opendir // Purpose: replacement for unix function, uses win32 findfirst routines // Created: 25th October 2004 // // -------------------------------------------------------------------------- DIR *opendir(const char *name) { if (!name || !name[0]) { errno = EINVAL; return NULL; } std::string dirName(name); //append a '\' win32 findfirst is sensitive to this if ( dirName[dirName.size()] != '\\' || dirName[dirName.size()] != '/' ) { dirName += '\\'; } // what is the search string? - everything dirName += '*'; DIR *pDir = new DIR; if (pDir == NULL) { errno = ENOMEM; return NULL; } pDir->name = ConvertUtf8ToWideString(dirName.c_str()); // We are responsible for freeing dir->name with delete[] if (pDir->name == NULL) { delete pDir; return NULL; } pDir->fd = _wfindfirst((const wchar_t*)pDir->name, &(pDir->info)); if (pDir->fd == -1) { delete [] pDir->name; delete pDir; return NULL; } pDir->result.d_name = 0; return pDir; } // this kinda makes it not thread friendly! // but I don't think it needs to be. char tempbuff[MAX_PATH]; // -------------------------------------------------------------------------- // // Function // Name: readdir // Purpose: as function above // Created: 25th October 2004 // // -------------------------------------------------------------------------- struct dirent *readdir(DIR *dp) { try { struct dirent *den = NULL; if (dp && dp->fd != -1) { if (!dp->result.d_name || _wfindnext(dp->fd, &dp->info) != -1) { den = &dp->result; std::wstring input(dp->info.name); memset(tempbuff, 0, sizeof(tempbuff)); WideCharToMultiByte(CP_UTF8, 0, dp->info.name, -1, &tempbuff[0], sizeof (tempbuff), NULL, NULL); //den->d_name = (char *)dp->info.name; den->d_name = &tempbuff[0]; if (dp->info.attrib & FILE_ATTRIBUTE_DIRECTORY) { den->d_type = S_IFDIR; } else { den->d_type = S_IFREG; } } } else { errno = EBADF; } return den; } catch (...) { printf("Caught readdir"); } return NULL; } // -------------------------------------------------------------------------- // // Function // Name: closedir // Purpose: as function above // Created: 25th October 2004 // // -------------------------------------------------------------------------- int closedir(DIR *dp) { try { int finres = -1; if (dp) { if(dp->fd != -1) { finres = _findclose(dp->fd); } delete [] dp->name; delete dp; } if (finres == -1) // errors go to EBADF { errno = EBADF; } return finres; } catch (...) { printf("Caught closedir"); } return -1; } // -------------------------------------------------------------------------- // // Function // Name: poll // Purpose: a weak implimentation (just enough for box) // of the unix poll for winsock2 // Created: 25th October 2004 // // -------------------------------------------------------------------------- int poll (struct pollfd *ufds, unsigned long nfds, int timeout) { try { fd_set readfd; fd_set writefd; FD_ZERO(&readfd); FD_ZERO(&writefd); // struct pollfd *ufdsTmp = ufds; timeval timOut; timeval *tmpptr; if (timeout == INFTIM) tmpptr = NULL; else tmpptr = &timOut; timOut.tv_sec = timeout / 1000; timOut.tv_usec = timeout * 1000; for (unsigned long i = 0; i < nfds; i++) { struct pollfd* ufd = &(ufds[i]); if (ufd->events & POLLIN) { FD_SET(ufd->fd, &readfd); } if (ufd->events & POLLOUT) { FD_SET(ufd->fd, &writefd); } if (ufd->events & ~(POLLIN | POLLOUT)) { printf("Unsupported poll bits %d", ufd->events); return -1; } } int nready = select(0, &readfd, &writefd, 0, tmpptr); if (nready == SOCKET_ERROR) { // int errval = WSAGetLastError(); struct pollfd* pufd = ufds; for (unsigned long i = 0; i < nfds; i++) { pufd->revents = POLLERR; pufd++; } return (-1); } else if (nready > 0) { for (unsigned long i = 0; i < nfds; i++) { struct pollfd *ufd = &(ufds[i]); if (FD_ISSET(ufd->fd, &readfd)) { ufd->revents |= POLLIN; } if (FD_ISSET(ufd->fd, &writefd)) { ufd->revents |= POLLOUT; } } } return nready; } catch (...) { printf("Caught poll"); } return -1; } // copied from MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/eventlog/base/adding_a_source_to_the_registry.asp BOOL AddEventSource ( LPTSTR pszSrcName, // event source name DWORD dwNum // number of categories ) { // Work out the executable file name, to register ourselves // as the event source WCHAR cmd[MAX_PATH]; DWORD len = GetModuleFileNameW(NULL, cmd, MAX_PATH); if (len == 0) { ::syslog(LOG_ERR, "Failed to get the program file name: %s", GetErrorMessage(GetLastError()).c_str()); return FALSE; } // Create the event source as a subkey of the log. std::string regkey("SYSTEM\\CurrentControlSet\\Services\\EventLog\\" "Application\\"); regkey += pszSrcName; HKEY hk; DWORD dwDisp; if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, regkey.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk, &dwDisp)) { ::syslog(LOG_ERR, "Failed to create the registry key: %s", GetErrorMessage(GetLastError()).c_str()); return FALSE; } // Set the name of the message file. if (RegSetValueExW(hk, // subkey handle L"EventMessageFile", // value name 0, // must be zero REG_EXPAND_SZ, // value type (LPBYTE)cmd, // pointer to value data len*sizeof(WCHAR))) // data size { ::syslog(LOG_ERR, "Failed to set the event message file: %s", GetErrorMessage(GetLastError()).c_str()); RegCloseKey(hk); return FALSE; } // Set the supported event types. DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; if (RegSetValueEx(hk, // subkey handle "TypesSupported", // value name 0, // must be zero REG_DWORD, // value type (LPBYTE) &dwData, // pointer to value data sizeof(DWORD))) // length of value data { ::syslog(LOG_ERR, "Failed to set the supported types: %s", GetErrorMessage(GetLastError()).c_str()); RegCloseKey(hk); return FALSE; } // Set the category message file and number of categories. if (RegSetValueExW(hk, // subkey handle L"CategoryMessageFile", // value name 0, // must be zero REG_EXPAND_SZ, // value type (LPBYTE)cmd, // pointer to value data len*sizeof(WCHAR))) // data size { ::syslog(LOG_ERR, "Failed to set the category message file: " "%s", GetErrorMessage(GetLastError()).c_str()); RegCloseKey(hk); return FALSE; } if (RegSetValueEx(hk, // subkey handle "CategoryCount", // value name 0, // must be zero REG_DWORD, // value type (LPBYTE) &dwNum, // pointer to value data sizeof(DWORD))) // length of value data { ::syslog(LOG_ERR, "Failed to set the category count: %s", GetErrorMessage(GetLastError()).c_str()); RegCloseKey(hk); return FALSE; } RegCloseKey(hk); return TRUE; } static HANDLE gSyslogH = 0; static bool sHaveWarnedEventLogFull = false; void openlog(const char * daemonName, int, int) { std::string nameStr = "Box Backup ("; nameStr += daemonName; nameStr += ")"; // register a default event source, so that we can // log errors with the process of adding or registering our own. gSyslogH = RegisterEventSource( NULL, // uses local computer nameStr.c_str()); // source name if (gSyslogH == NULL) { } char* name = strdup(nameStr.c_str()); BOOL success = AddEventSource(name, 0); free(name); if (!success) { ::syslog(LOG_ERR, "Failed to add our own event source"); return; } HANDLE newSyslogH = RegisterEventSource(NULL, nameStr.c_str()); if (newSyslogH == NULL) { ::syslog(LOG_ERR, "Failed to register our own event source: " "%s", GetErrorMessage(GetLastError()).c_str()); return; } DeregisterEventSource(gSyslogH); gSyslogH = newSyslogH; } void closelog(void) { DeregisterEventSource(gSyslogH); } void syslog(int loglevel, const char *frmt, ...) { WORD errinfo; char buffer[4096]; std::string sixfour(frmt); switch (loglevel) { case LOG_INFO: errinfo = EVENTLOG_INFORMATION_TYPE; break; case LOG_ERR: errinfo = EVENTLOG_ERROR_TYPE; break; case LOG_WARNING: errinfo = EVENTLOG_WARNING_TYPE; break; default: errinfo = EVENTLOG_WARNING_TYPE; break; } // taken from MSDN int sixfourpos; while ( (sixfourpos = (int)sixfour.find("%ll")) != -1 ) { // maintain portability - change the 64 bit formater... std::string temp = sixfour.substr(0,sixfourpos); temp += "%I64"; temp += sixfour.substr(sixfourpos+3, sixfour.length()); sixfour = temp; } // printf("parsed string is:%s\r\n", sixfour.c_str()); va_list args; va_start(args, frmt); int len = vsnprintf(buffer, sizeof(buffer)-1, sixfour.c_str(), args); assert(len >= 0); if (len < 0) { printf("%s\r\n", buffer); fflush(stdout); return; } assert((size_t)len < sizeof(buffer)); buffer[sizeof(buffer)-1] = 0; va_end(args); if (gSyslogH == 0) { printf("%s\r\n", buffer); fflush(stdout); return; } WCHAR* pWide = ConvertToWideString(buffer, CP_UTF8, false); // must delete[] pWide DWORD result; if (pWide == NULL) { std::string buffer2 = buffer; buffer2 += " (failed to convert string encoding)"; LPCSTR strings[] = { buffer2.c_str(), NULL }; result = ReportEventA(gSyslogH, // event log handle errinfo, // event type 0, // category zero MSG_ERR, // event identifier - // we will call them all the same NULL, // no user security identifier 1, // one substitution string 0, // no data strings, // pointer to string array NULL); // pointer to data } else { LPCWSTR strings[] = { pWide, NULL }; result = ReportEventW(gSyslogH, // event log handle errinfo, // event type 0, // category zero MSG_ERR, // event identifier - // we will call them all the same NULL, // no user security identifier 1, // one substitution string 0, // no data strings, // pointer to string array NULL); // pointer to data delete [] pWide; } if (result == 0) { DWORD err = GetLastError(); if (err == ERROR_LOG_FILE_FULL) { if (!sHaveWarnedEventLogFull) { printf("Unable to send message to Event Log " "(Event Log is full):\r\n"); fflush(stdout); sHaveWarnedEventLogFull = TRUE; } } else { printf("Unable to send message to Event Log: %s:\r\n", GetErrorMessage(err).c_str()); fflush(stdout); } } else { sHaveWarnedEventLogFull = false; } } int emu_chdir(const char* pDirName) { /* std::string AbsPathWithUnicode = ConvertPathToAbsoluteUnicode(pDirName); if (AbsPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() return -1; } WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); */ WCHAR* pBuffer = ConvertUtf8ToWideString(pDirName); if (!pBuffer) return -1; int result = SetCurrentDirectoryW(pBuffer); delete [] pBuffer; if (result != 0) return 0; errno = EACCES; fprintf(stderr, "Failed to change directory to '%s': %s\n", pDirName, GetErrorMessage(GetLastError()).c_str()); return -1; } char* emu_getcwd(char* pBuffer, int BufSize) { DWORD len = GetCurrentDirectoryW(0, NULL); if (len == 0) { errno = EINVAL; return NULL; } if ((int)len > BufSize) { errno = ENAMETOOLONG; return NULL; } WCHAR* pWide = new WCHAR [len]; if (!pWide) { errno = ENOMEM; return NULL; } DWORD result = GetCurrentDirectoryW(len, pWide); if (result <= 0 || result >= len) { errno = EACCES; delete [] pWide; return NULL; } char* pUtf8 = ConvertFromWideString(pWide, CP_UTF8); delete [] pWide; if (!pUtf8) { return NULL; } strncpy(pBuffer, pUtf8, BufSize - 1); pBuffer[BufSize - 1] = 0; delete [] pUtf8; return pBuffer; } int emu_mkdir(const char* pPathName) { std::string AbsPathWithUnicode = ConvertPathToAbsoluteUnicode(pPathName); if (AbsPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() return -1; } WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); if (!pBuffer) { return -1; } BOOL result = CreateDirectoryW(pBuffer, NULL); delete [] pBuffer; if (!result) { errno = EACCES; return -1; } return 0; } int emu_unlink(const char* pFileName) { std::string AbsPathWithUnicode = ConvertPathToAbsoluteUnicode(pFileName); if (AbsPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() return -1; } WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); if (!pBuffer) { return -1; } BOOL result = DeleteFileW(pBuffer); DWORD err = GetLastError(); delete [] pBuffer; if (!result) { if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { errno = ENOENT; } else if (err == ERROR_SHARING_VIOLATION) { errno = EBUSY; } else if (err == ERROR_ACCESS_DENIED) { errno = EACCES; } else { ::syslog(LOG_WARNING, "Failed to delete file " "'%s': %s", pFileName, GetErrorMessage(err).c_str()); errno = ENOSYS; } return -1; } return 0; } int emu_rename(const char* pOldFileName, const char* pNewFileName) { std::string OldPathWithUnicode = ConvertPathToAbsoluteUnicode(pOldFileName); if (OldPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() return -1; } WCHAR* pOldBuffer = ConvertUtf8ToWideString(OldPathWithUnicode.c_str()); if (!pOldBuffer) { return -1; } std::string NewPathWithUnicode = ConvertPathToAbsoluteUnicode(pNewFileName); if (NewPathWithUnicode.size() == 0) { // error already logged by ConvertPathToAbsoluteUnicode() delete [] pOldBuffer; return -1; } WCHAR* pNewBuffer = ConvertUtf8ToWideString(NewPathWithUnicode.c_str()); if (!pNewBuffer) { delete [] pOldBuffer; return -1; } BOOL result = MoveFileW(pOldBuffer, pNewBuffer); DWORD err = GetLastError(); delete [] pOldBuffer; delete [] pNewBuffer; if (!result) { if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { errno = ENOENT; } else if (err == ERROR_SHARING_VIOLATION) { errno = EBUSY; } else if (err == ERROR_ACCESS_DENIED) { errno = EACCES; } else { ::syslog(LOG_WARNING, "Failed to rename file " "'%s' to '%s': %s", pOldFileName, pNewFileName, GetErrorMessage(err).c_str()); errno = ENOSYS; } return -1; } return 0; } int console_read(char* pBuffer, size_t BufferSize) { HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE); if (hConsole == INVALID_HANDLE_VALUE) { ::fprintf(stderr, "Failed to get a handle on standard input: " "%s", GetErrorMessage(GetLastError()).c_str()); return -1; } size_t WideSize = BufferSize / 5; WCHAR* pWideBuffer = new WCHAR [WideSize + 1]; if (!pWideBuffer) { ::perror("Failed to allocate wide character buffer"); return -1; } DWORD numCharsRead = 0; if (!ReadConsoleW( hConsole, pWideBuffer, WideSize, // will not be null terminated by ReadConsole &numCharsRead, NULL // reserved )) { ::fprintf(stderr, "Failed to read from console: %s\n", GetErrorMessage(GetLastError()).c_str()); return -1; } pWideBuffer[numCharsRead] = 0; char* pUtf8 = ConvertFromWideString(pWideBuffer, GetConsoleCP()); delete [] pWideBuffer; strncpy(pBuffer, pUtf8, BufferSize); delete [] pUtf8; return strlen(pBuffer); } int readv (int filedes, const struct iovec *vector, size_t count) { int bytes = 0; for (size_t i = 0; i < count; i++) { int result = read(filedes, vector[i].iov_base, vector[i].iov_len); if (result < 0) { return result; } bytes += result; } return bytes; } int writev(int filedes, const struct iovec *vector, size_t count) { int bytes = 0; for (size_t i = 0; i < count; i++) { int result = write(filedes, vector[i].iov_base, vector[i].iov_len); if (result < 0) { return result; } bytes += result; } return bytes; } // need this for conversions time_t ConvertFileTimeToTime_t(FILETIME *fileTime) { SYSTEMTIME stUTC; struct tm timeinfo; // Convert the last-write time to local time. FileTimeToSystemTime(fileTime, &stUTC); memset(&timeinfo, 0, sizeof(timeinfo)); timeinfo.tm_sec = stUTC.wSecond; timeinfo.tm_min = stUTC.wMinute; timeinfo.tm_hour = stUTC.wHour; timeinfo.tm_mday = stUTC.wDay; timeinfo.tm_wday = stUTC.wDayOfWeek; timeinfo.tm_mon = stUTC.wMonth - 1; // timeinfo.tm_yday = ...; timeinfo.tm_year = stUTC.wYear - 1900; time_t retVal = mktime(&timeinfo) - _timezone; return retVal; } bool ConvertTime_tToFileTime(const time_t from, FILETIME *pTo) { time_t adjusted = from + _timezone; struct tm *time_breakdown = gmtime(&adjusted); if (time_breakdown == NULL) { ::syslog(LOG_ERR, "Error: failed to convert time format: " "%d is not a valid time\n", adjusted); return false; } SYSTEMTIME stUTC; stUTC.wSecond = time_breakdown->tm_sec; stUTC.wMinute = time_breakdown->tm_min; stUTC.wHour = time_breakdown->tm_hour; stUTC.wDay = time_breakdown->tm_mday; stUTC.wDayOfWeek = time_breakdown->tm_wday; stUTC.wMonth = time_breakdown->tm_mon + 1; stUTC.wYear = time_breakdown->tm_year + 1900; stUTC.wMilliseconds = 0; // Convert the last-write time to local time. if (!SystemTimeToFileTime(&stUTC, pTo)) { syslog(LOG_ERR, "Failed to convert between time formats: %s", GetErrorMessage(GetLastError()).c_str()); return false; } return true; } #endif // WIN32 boxbackup/lib/win32/MSG00001.bin0000775000175000017500000000004010601334724016520 0ustar siretartsiretart@@%1 boxbackup/lib/win32/getopt.h0000775000175000017500000000734710476267307016527 0ustar siretartsiretart/* $OpenBSD: getopt.h,v 1.1 2002/12/03 20:24:29 millert Exp $ */ /* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _GETOPT_H_ #define _GETOPT_H_ // copied from: http://www.la.utexas.edu/lab/software/devtool/gnu/libtool/C_header_files.html /* __BEGIN_DECLS should be used at the beginning of your declarations, so that C++ compilers don't mangle their names. Use __END_DECLS at the end of C declarations. */ #undef __BEGIN_DECLS #undef __END_DECLS #ifdef __cplusplus # define __BEGIN_DECLS extern "C" { # define __END_DECLS } #else # define __BEGIN_DECLS /* empty */ # define __END_DECLS /* empty */ #endif /* * GNU-like getopt_long() and 4.4BSD getsubopt()/optreset extensions */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 struct option { /* name of long option */ const char *name; /* * one of no_argument, required_argument, and optional_argument: * whether option takes an argument */ int has_arg; /* if not NULL, set *flag to val when option found */ int *flag; /* if flag not NULL, value to set *flag to; else return value */ int val; }; __BEGIN_DECLS int getopt_long(int, char * const *, const char *, const struct option *, int *); int getopt_long_only(int, char * const *, const char *, const struct option *, int *); #ifndef _GETOPT_DEFINED_ #define _GETOPT_DEFINED_ int getopt(int, char * const *, const char *); int getsubopt(char **, char * const *, char **); extern char *optarg; /* getopt(3) external variables */ extern int opterr; extern int optind; extern int optopt; extern int optreset; extern char *suboptarg; /* getsubopt(3) external variable */ #endif __END_DECLS #endif /* !_GETOPT_H_ */ boxbackup/lib/win32/messages.rc0000775000175000017500000000004510476266770017200 0ustar siretartsiretartLANGUAGE 0x9,0x1 1 11 MSG00001.bin boxbackup/lib/win32/getopt_long.cpp0000775000175000017500000003744111175154614020070 0ustar siretartsiretart/* $OpenBSD: getopt_long.c,v 1.20 2005/10/25 15:49:37 jmc Exp $ */ /* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ // Adapted for Box Backup by Chris Wilson /* * Copyright (c) 2002 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F39502-99-1-0512. */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ // #include "Box.h" #include #include #include #include #include #include "getopt.h" #if defined _MSC_VER || defined __MINGW32__ #define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ #ifdef REPLACE_GETOPT int opterr = 1; /* if error message should be printed */ int optind = 1; /* index into parent argv vector */ int optopt = '?'; /* character checked for validity */ int optreset; /* reset getopt */ char *optarg; /* argument associated with option */ #endif #define PRINT_ERROR ((opterr) && (*options != ':')) #define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ #define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ #define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ /* return values */ #define BADCH (int)'?' #define BADARG ((*options == ':') ? (int)':' : (int)'?') #define INORDER (int)1 #define EMSG "" static void warnx(const char* fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); } static int getopt_internal(int, char * const *, const char *, const struct option *, int *, int); static int parse_long_options(char * const *, const char *, const struct option *, int *, int); static int gcd(int, int); static void permute_args(int, int, int, char * const *); static char *place = EMSG; /* option letter processing */ /* XXX: set optreset to 1 rather than these two */ static int nonopt_start = -1; /* first non option argument (for permute) */ static int nonopt_end = -1; /* first option after non options (for permute) */ /* Error messages */ static const char recargchar[] = "option requires an argument -- %c"; static const char recargstring[] = "option requires an argument -- %s"; static const char ambig[] = "ambiguous option -- %.*s"; static const char noarg[] = "option doesn't take an argument -- %.*s"; static const char illoptchar[] = "unknown option -- %c"; static const char illoptstring[] = "unknown option -- %s"; /* * Compute the greatest common divisor of a and b. */ static int gcd(int a, int b) { int c; c = a % b; while (c != 0) { a = b; b = c; c = a % b; } return (b); } /* * Exchange the block from nonopt_start to nonopt_end with the block * from nonopt_end to opt_end (keeping the same order of arguments * in each block). */ static void permute_args(int panonopt_start, int panonopt_end, int opt_end, char * const *nargv) { int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; char *swap; /* * compute lengths of blocks and number and size of cycles */ nnonopts = panonopt_end - panonopt_start; nopts = opt_end - panonopt_end; ncycle = gcd(nnonopts, nopts); cyclelen = (opt_end - panonopt_start) / ncycle; for (i = 0; i < ncycle; i++) { cstart = panonopt_end+i; pos = cstart; for (j = 0; j < cyclelen; j++) { if (pos >= panonopt_end) pos -= nnonopts; else pos += nopts; swap = nargv[pos]; /* LINTED const cast */ ((char **) nargv)[pos] = nargv[cstart]; /* LINTED const cast */ ((char **)nargv)[cstart] = swap; } } } /* * parse_long_options -- * Parse long options in argc/argv argument vector. * Returns -1 if short_too is set and the option does not match long_options. */ static int parse_long_options(char * const *nargv, const char *options, const struct option *long_options, int *idx, int short_too) { char *current_argv, *has_equal; size_t current_argv_len; int i, match; current_argv = place; match = -1; optind++; if ((has_equal = strchr(current_argv, '=')) != NULL) { /* argument found (--option=arg) */ current_argv_len = has_equal - current_argv; has_equal++; } else current_argv_len = strlen(current_argv); for (i = 0; long_options[i].name; i++) { /* find matching long option */ if (strncmp(current_argv, long_options[i].name, current_argv_len)) continue; if (strlen(long_options[i].name) == current_argv_len) { /* exact match */ match = i; break; } /* * If this is a known short option, don't allow * a partial match of a single character. */ if (short_too && current_argv_len == 1) continue; if (match == -1) /* partial match */ match = i; else { /* ambiguous abbreviation */ if (PRINT_ERROR) warnx(ambig, (int)current_argv_len, current_argv); optopt = 0; return (BADCH); } } if (match != -1) { /* option found */ if (long_options[match].has_arg == no_argument && has_equal) { if (PRINT_ERROR) warnx(noarg, (int)current_argv_len, current_argv); /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) optopt = long_options[match].val; else optopt = 0; return (BADARG); } if (long_options[match].has_arg == required_argument || long_options[match].has_arg == optional_argument) { if (has_equal) optarg = has_equal; else if (long_options[match].has_arg == required_argument) { /* * optional argument doesn't use next nargv */ optarg = nargv[optind++]; } } if ((long_options[match].has_arg == required_argument) && (optarg == NULL)) { /* * Missing argument; leading ':' indicates no error * should be generated. */ if (PRINT_ERROR) warnx(recargstring, current_argv); /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) optopt = long_options[match].val; else optopt = 0; --optind; return (BADARG); } } else { /* unknown option */ if (short_too) { --optind; return (-1); } if (PRINT_ERROR) warnx(illoptstring, current_argv); optopt = 0; return (BADCH); } if (idx) *idx = match; if (long_options[match].flag) { *long_options[match].flag = long_options[match].val; return (0); } else return (long_options[match].val); } /* * getopt_internal -- * Parse argc/argv argument vector. Called by user level routines. */ static int getopt_internal(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx, int flags) { const char * oli; /* option letter list index */ int optchar, short_too; static int posixly_correct = -1; if (options == NULL) return (-1); /* * Disable GNU extensions if POSIXLY_CORRECT is set or options * string begins with a '+'. */ if (posixly_correct == -1) posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); if (posixly_correct || *options == '+') flags &= ~FLAG_PERMUTE; else if (*options == '-') flags |= FLAG_ALLARGS; if (*options == '+' || *options == '-') options++; /* * XXX Some GNU programs (like cvs) set optind to 0 instead of * XXX using optreset. Work around this braindamage. */ if (optind == 0) optind = optreset = 1; optarg = NULL; if (optreset) nonopt_start = nonopt_end = -1; start: if (optreset || !*place) { /* update scanning pointer */ optreset = 0; if (optind >= nargc) { /* end of argument vector */ place = EMSG; if (nonopt_end != -1) { /* do permutation, if we have to */ permute_args(nonopt_start, nonopt_end, optind, nargv); optind -= nonopt_end - nonopt_start; } else if (nonopt_start != -1) { /* * If we skipped non-options, set optind * to the first of them. */ optind = nonopt_start; } nonopt_start = nonopt_end = -1; return (-1); } if (*(place = nargv[optind]) != '-' || (place[1] == '\0' && strchr(options, '-') == NULL)) { place = EMSG; /* found non-option */ if (flags & FLAG_ALLARGS) { /* * GNU extension: * return non-option as argument to option 1 */ optarg = nargv[optind++]; return (INORDER); } if (!(flags & FLAG_PERMUTE)) { /* * If no permutation wanted, stop parsing * at first non-option. */ return (-1); } /* do permutation */ if (nonopt_start == -1) nonopt_start = optind; else if (nonopt_end != -1) { permute_args(nonopt_start, nonopt_end, optind, nargv); nonopt_start = optind - (nonopt_end - nonopt_start); nonopt_end = -1; } optind++; /* process next argument */ goto start; } if (nonopt_start != -1 && nonopt_end == -1) nonopt_end = optind; /* * If we have "-" do nothing, if "--" we are done. */ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { optind++; place = EMSG; /* * We found an option (--), so if we skipped * non-options, we have to permute. */ if (nonopt_end != -1) { permute_args(nonopt_start, nonopt_end, optind, nargv); optind -= nonopt_end - nonopt_start; } nonopt_start = nonopt_end = -1; return (-1); } } /* * Check long options if: * 1) we were passed some * 2) the arg is not just "-" * 3) either the arg starts with -- we are getopt_long_only() */ if (long_options != NULL && place != nargv[optind] && (*place == '-' || (flags & FLAG_LONGONLY))) { short_too = 0; if (*place == '-') place++; /* --foo long option */ else if (*place != ':' && strchr(options, *place) != NULL) short_too = 1; /* could be short option too */ optchar = parse_long_options(nargv, options, long_options, idx, short_too); if (optchar != -1) { place = EMSG; return (optchar); } } if ((optchar = (int)*place++) == (int)':' || optchar == (int)'-' && *place != '\0' || (oli = strchr(options, optchar)) == NULL) { /* * If the user specified "-" and '-' isn't listed in * options, return -1 (non-option) as per POSIX. * Otherwise, it is an unknown option character (or ':'). */ if (optchar == (int)'-' && *place == '\0') return (-1); if (!*place) ++optind; if (PRINT_ERROR) warnx(illoptchar, optchar); optopt = optchar; return (BADCH); } if (long_options != NULL && optchar == 'W' && oli[1] == ';') { /* -W long-option */ if (*place) /* no space */ /* NOTHING */; else if (++optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) warnx(recargchar, optchar); optopt = optchar; return (BADARG); } else /* white space */ place = nargv[optind]; optchar = parse_long_options(nargv, options, long_options, idx, 0); place = EMSG; return (optchar); } if (*++oli != ':') { /* doesn't take argument */ if (!*place) ++optind; } else { /* takes (optional) argument */ optarg = NULL; if (*place) /* no white space */ optarg = place; /* XXX: disable test for :: if PC? (GNU doesn't) */ else if (oli[1] != ':') { /* arg not optional */ if (++optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) warnx(recargchar, optchar); optopt = optchar; return (BADARG); } else optarg = nargv[optind]; } else if (!(flags & FLAG_PERMUTE)) { /* * If permutation is disabled, we can accept an * optional arg separated by whitespace so long * as it does not start with a dash (-). */ if (optind + 1 < nargc && *nargv[optind + 1] != '-') optarg = nargv[++optind]; } place = EMSG; ++optind; } /* dump back option letter */ return (optchar); } #ifdef REPLACE_GETOPT /* * getopt -- * Parse argc/argv argument vector. * * [eventually this will replace the BSD getopt] */ int getopt(int nargc, char * const *nargv, const char *options) { /* * We don't pass FLAG_PERMUTE to getopt_internal() since * the BSD getopt(3) (unlike GNU) has never done this. * * Furthermore, since many privileged programs call getopt() * before dropping privileges it makes sense to keep things * as simple (and bug-free) as possible. */ return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); } #endif /* REPLACE_GETOPT */ /* * getopt_long -- * Parse argc/argv argument vector. */ int getopt_long(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx) { return (getopt_internal(nargc, nargv, options, long_options, idx, FLAG_PERMUTE)); } /* * getopt_long_only -- * Parse argc/argv argument vector. */ int getopt_long_only(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx) { return (getopt_internal(nargc, nargv, options, long_options, idx, FLAG_PERMUTE|FLAG_LONGONLY)); } #endif // defined _MSC_VER || defined __MINGW32__ boxbackup/lib/win32/emu.h0000664000175000017500000002414611175071121015764 0ustar siretartsiretart// emulates unix syscalls to win32 functions #ifdef WIN32 #define EMU_STRUCT_STAT struct emu_stat #define EMU_STAT emu_stat #define EMU_FSTAT emu_fstat #define EMU_LSTAT emu_stat #else #define EMU_STRUCT_STAT struct stat #define EMU_STAT ::stat #define EMU_FSTAT ::fstat #define EMU_LSTAT ::lstat #endif #if ! defined EMU_INCLUDE && defined WIN32 #define EMU_INCLUDE // basic types, may be required by other headers since we // don't include sys/types.h #ifdef __MINGW32__ #include #else // MSVC typedef unsigned __int64 u_int64_t; typedef unsigned __int64 uint64_t; typedef __int64 int64_t; typedef unsigned __int32 uint32_t; typedef unsigned __int32 u_int32_t; typedef __int32 int32_t; typedef unsigned __int16 uint16_t; typedef __int16 int16_t; typedef unsigned __int8 uint8_t; typedef __int8 int8_t; #endif // emulated types, present on MinGW but not MSVC or vice versa #ifdef __MINGW32__ typedef uint32_t u_int32_t; #else typedef unsigned int mode_t; typedef unsigned int pid_t; #endif // set up to include the necessary parts of Windows headers #define WIN32_LEAN_AND_MEAN #ifndef __MSVCRT_VERSION__ #define __MSVCRT_VERSION__ 0x0601 #endif // Windows headers #include #include #include #include #include #include #include #include #include #include #include #include // emulated functions #define gmtime_r( _clock, _result ) \ ( *(_result) = *gmtime( (_clock) ), \ (_result) ) #define ITIMER_REAL 0 #ifdef _MSC_VER // Microsoft decided to deprecate the standard POSIX functions. Great! #define open(file,flags,mode) _open(file,flags,mode) #define close(fd) _close(fd) #define dup(fd) _dup(fd) #define read(fd,buf,count) _read(fd,buf,count) #define write(fd,buf,count) _write(fd,buf,count) #define lseek(fd,off,whence) _lseek(fd,off,whence) #define fileno(struct_file) _fileno(struct_file) #endif struct passwd { char *pw_name; char *pw_passwd; int pw_uid; int pw_gid; time_t pw_change; char *pw_class; char *pw_gecos; char *pw_dir; char *pw_shell; time_t pw_expire; }; extern passwd gTempPasswd; inline struct passwd * getpwnam(const char * name) { //for the mo pretend to be root gTempPasswd.pw_uid = 0; gTempPasswd.pw_gid = 0; return &gTempPasswd; } #define S_IRWXG 1 #define S_IRWXO 2 #define S_ISUID 4 #define S_ISGID 8 #define S_ISVTX 16 #ifndef __MINGW32__ //not sure if these are correct //S_IWRITE - writing permitted //_S_IREAD - reading permitted //_S_IREAD | _S_IWRITE - #define S_IRUSR S_IWRITE #define S_IWUSR S_IREAD #define S_IRWXU (S_IREAD|S_IWRITE|S_IEXEC) #define S_ISREG(x) (S_IFREG & x) #define S_ISDIR(x) (S_IFDIR & x) #endif inline int chown(const char * Filename, u_int32_t uid, u_int32_t gid) { //important - this needs implementing //If a large restore is required then //it needs to restore files AND permissions //reference AdjustTokenPrivileges //GetAccountSid //InitializeSecurityDescriptor //SetSecurityDescriptorOwner //The next function looks like the guy to use... //SetFileSecurity //indicate success return 0; } // Windows and Unix owners and groups are pretty fundamentally different. // Ben prefers that we kludge here rather than litter the code with #ifdefs. // Pretend to be root, and pretend that set...() operations succeed. inline int setegid(int) { return true; } inline int seteuid(int) { return true; } inline int setgid(int) { return true; } inline int setuid(int) { return true; } inline int getgid(void) { return 0; } inline int getuid(void) { return 0; } inline int geteuid(void) { return 0; } #ifndef PATH_MAX #define PATH_MAX MAX_PATH #endif // MinGW provides a getopt implementation #ifndef __MINGW32__ #include "getopt.h" #endif // !__MINGW32__ #define timespec timeval //win32 deals in usec not nsec - so need to ensure this follows through #define tv_nsec tv_usec #ifndef __MINGW32__ typedef int socklen_t; #endif #define S_IRGRP S_IWRITE #define S_IWGRP S_IREAD #define S_IROTH S_IWRITE | S_IREAD #define S_IWOTH S_IREAD | S_IREAD //again need to verify these #define S_IFLNK 1 #define S_IFSOCK 0 #define S_ISLNK(x) ( false ) #define vsnprintf _vsnprintf #ifndef __MINGW32__ inline int strcasecmp(const char *s1, const char *s2) { return _stricmp(s1,s2); } #endif #ifdef _DIRENT_H_ #error You must not include the MinGW dirent.h! #endif struct dirent { char *d_name; unsigned long d_type; }; struct DIR { intptr_t fd; // filedescriptor // struct _finddata_t info; struct _wfinddata_t info; // struct _finddata_t info; struct dirent result; // d_name (first time null) wchar_t *name; // null-terminated byte string }; DIR *opendir(const char *name); struct dirent *readdir(DIR *dp); int closedir(DIR *dp); // local constant to open file exclusively without shared access #define O_LOCK 0x10000 extern DWORD winerrno; /* used to report errors from openfile() */ HANDLE openfile(const char *filename, int flags, int mode); inline int closefile(HANDLE handle) { if (CloseHandle(handle) != TRUE) { errno = EINVAL; return -1; } return 0; } #define LOG_DEBUG LOG_INFO #define LOG_INFO 6 #define LOG_NOTICE LOG_INFO #define LOG_WARNING 4 #define LOG_ERR 3 #define LOG_CRIT LOG_ERR #define LOG_PID 0 #define LOG_LOCAL5 0 #define LOG_LOCAL6 0 void openlog (const char * daemonName, int, int); void closelog(void); void syslog (int loglevel, const char *fmt, ...); #define LOG_LOCAL0 0 #define LOG_LOCAL1 0 #define LOG_LOCAL2 0 #define LOG_LOCAL3 0 #define LOG_LOCAL4 0 #define LOG_LOCAL5 0 #define LOG_LOCAL6 0 #define LOG_DAEMON 0 #ifndef __MINGW32__ #define strtoll _strtoi64 #endif inline unsigned int sleep(unsigned int secs) { Sleep(secs*1000); return(ERROR_SUCCESS); } #define INFTIM -1 #define POLLIN 0x1 #define POLLERR 0x8 #define POLLOUT 0x4 #define SHUT_RDWR SD_BOTH #define SHUT_RD SD_RECEIVE #define SHUT_WR SD_SEND struct pollfd { SOCKET fd; short int events; short int revents; }; inline int ioctl(SOCKET sock, int flag, int * something) { //indicate success return 0; } extern "C" inline int getpid() { return (int)GetCurrentProcessId(); } inline int waitpid(pid_t pid, int *status, int) { return 0; } //this shouldn't be needed. struct statfs { TCHAR f_mntonname[MAX_PATH]; }; struct emu_stat { int st_dev; uint64_t st_ino; DWORD st_mode; short st_nlink; short st_uid; short st_gid; //_dev_t st_rdev; uint64_t st_size; time_t st_atime; time_t st_mtime; time_t st_ctime; }; // need this for conversions time_t ConvertFileTimeToTime_t(FILETIME *fileTime); bool ConvertTime_tToFileTime(const time_t from, FILETIME *pTo); int emu_chdir (const char* pDirName); int emu_mkdir (const char* pPathName); int emu_unlink (const char* pFileName); int emu_fstat (HANDLE file, struct emu_stat* st); int emu_stat (const char* pName, struct emu_stat* st); int emu_utimes (const char* pName, const struct timeval[]); int emu_chmod (const char* pName, mode_t mode); char* emu_getcwd (char* pBuffer, int BufSize); int emu_rename (const char* pOldName, const char* pNewName); #define chdir(directory) emu_chdir (directory) #define mkdir(path, mode) emu_mkdir (path) #define unlink(file) emu_unlink (file) #define utimes(buffer, times) emu_utimes (buffer, times) #define chmod(file, mode) emu_chmod (file, mode) #define getcwd(buffer, size) emu_getcwd (buffer, size) #define rename(oldname, newname) emu_rename (oldname, newname) // Not safe to replace stat/fstat/lstat on mingw at least, as struct stat // has a 16-bit st_ino and we need a 64-bit one. // // #define stat(filename, struct) emu_stat (filename, struct) // #define lstat(filename, struct) emu_stat (filename, struct) // #define fstat(handle, struct) emu_fstat (handle, struct) // // But lstat doesn't exist on Windows, so we have to provide something: #define lstat(filename, struct) stat(filename, struct) int statfs(const char * name, struct statfs * s); int poll(struct pollfd *ufds, unsigned long nfds, int timeout); struct iovec { void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes */ }; int readv (int filedes, const struct iovec *vector, size_t count); int writev(int filedes, const struct iovec *vector, size_t count); // The following functions are not emulations, but utilities for other // parts of the code where Windows API is used or windows-specific stuff // is needed, like codepage conversion. bool EnableBackupRights( void ); bool ConvertEncoding (const std::string& rSource, int sourceCodePage, std::string& rDest, int destCodePage); bool ConvertToUtf8 (const std::string& rSource, std::string& rDest, int sourceCodePage); bool ConvertFromUtf8 (const std::string& rSource, std::string& rDest, int destCodePage); bool ConvertUtf8ToConsole(const std::string& rSource, std::string& rDest); bool ConvertConsoleToUtf8(const std::string& rSource, std::string& rDest); // Utility function which returns a default config file name, // based on the path of the current executable. std::string GetDefaultConfigFilePath(const std::string& rName); // GetErrorMessage() returns a system error message, like strerror() // but for Windows error codes. std::string GetErrorMessage(DWORD errorCode); // console_read() is a replacement for _cgetws which requires a // relatively recent C runtime lib int console_read(char* pBuffer, size_t BufferSize); #ifdef _MSC_VER /* disable certain compiler warnings to be able to actually see the show-stopper ones */ #pragma warning(disable:4101) // unreferenced local variable #pragma warning(disable:4244) // conversion, possible loss of data #pragma warning(disable:4267) // conversion, possible loss of data #pragma warning(disable:4311) // pointer truncation #pragma warning(disable:4700) // uninitialized local variable used (hmmmmm...) #pragma warning(disable:4805) // unsafe mix of type and type 'bool' in operation #pragma warning(disable:4800) // forcing value to bool 'true' or 'false' (performance warning) #pragma warning(disable:4996) // POSIX name for this item is deprecated #endif // _MSC_VER #endif // !EMU_INCLUDE && WIN32 boxbackup/lib/win32/messages.mc0000664000175000017500000000134410476266770017173 0ustar siretartsiretart; // Message source file, to be compiled to a resource file with ; // Microsoft Message Compiler (MC), to an object file with a Resource ; // Compiler, and linked into the application. ; ; // The main reason for this file is to work around Windows' stupid ; // messages in the Event Log, which say: ; ; // The description for Event ID ( 4 ) in Source ( Box Backup (bbackupd) ) ; // cannot be found. The local computer may not have the necessary ; // registry information or message DLL files to display messages from a ; // remote computer. The following information is part of the event: MessageIdTypedef = DWORD ; // Message definitions follow MessageId = 0x1 Severity = Informational SymbolicName = MSG_ERR Language = English %1 . boxbackup/lib/win32/messages.h0000775000175000017500000000304610476266770017027 0ustar siretartsiretart // Message source file, to be compiled to a resource file with // Microsoft Message Compiler (MC), to an object file with a Resource // Compiler, and linked into the application. // The main reason for this file is to work around Windows' stupid // messages in the Event Log, which say: // The description for Event ID ( 4 ) in Source ( Box Backup (bbackupd) ) // cannot be found. The local computer may not have the necessary // registry information or message DLL files to display messages from a // remote computer. The following information is part of the event: // Message definitions follow // // Values are 32 bit values layed out as follows: // // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +---+-+-+-----------------------+-------------------------------+ // |Sev|C|R| Facility | Code | // +---+-+-+-----------------------+-------------------------------+ // // where // // Sev - is the severity code // // 00 - Success // 01 - Informational // 10 - Warning // 11 - Error // // C - is the Customer code flag // // R - is a reserved bit // // Facility - is the facility code // // Code - is the facility's status code // // // Define the facility codes // // // Define the severity codes // // // MessageId: MSG_ERR // // MessageText: // // %1 // #define MSG_ERR ((DWORD)0x40000001L) boxbackup/lib/httpserver/0000775000175000017500000000000011652362374016277 5ustar siretartsiretartboxbackup/lib/httpserver/S3Client.cpp0000664000175000017500000001521311131634762020424 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: S3Client.cpp // Purpose: Amazon S3 client helper implementation class // Created: 09/01/2009 // // -------------------------------------------------------------------------- #include "Box.h" #include // #include // #include #include #include "HTTPRequest.h" #include "HTTPResponse.h" #include "HTTPServer.h" #include "autogen_HTTPException.h" #include "IOStream.h" #include "Logging.h" #include "S3Client.h" #include "decode.h" #include "encode.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: S3Client::GetObject(const std::string& rObjectURI) // Purpose: Retrieve the object with the specified URI (key) // from your S3 bucket. // Created: 09/01/09 // // -------------------------------------------------------------------------- HTTPResponse S3Client::GetObject(const std::string& rObjectURI) { return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI); } // -------------------------------------------------------------------------- // // Function // Name: S3Client::PutObject(const std::string& rObjectURI, // IOStream& rStreamToSend, const char* pContentType) // Purpose: Upload the stream to S3, creating or overwriting the // object with the specified URI (key) in your S3 // bucket. // Created: 09/01/09 // // -------------------------------------------------------------------------- HTTPResponse S3Client::PutObject(const std::string& rObjectURI, IOStream& rStreamToSend, const char* pContentType) { return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI, &rStreamToSend, pContentType); } // -------------------------------------------------------------------------- // // Function // Name: S3Client::FinishAndSendRequest( // HTTPRequest::Method Method, // const std::string& rRequestURI, // IOStream* pStreamToSend, // const char* pStreamContentType) // Purpose: Internal method which creates an HTTP request to S3, // populates the date and authorization header fields, // and sends it to S3 (or the simulator), attaching // the specified stream if any to the request. Opens a // connection to the server if necessary, which may // throw a ConnectionException. Returns the HTTP // response returned by S3, which may be a 500 error. // Created: 09/01/09 // // -------------------------------------------------------------------------- HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method, const std::string& rRequestURI, IOStream* pStreamToSend, const char* pStreamContentType) { HTTPRequest request(Method, rRequestURI); request.SetHostName(mHostName); std::ostringstream date; time_t tt = time(NULL); struct tm *tp = gmtime(&tt); if (!tp) { BOX_ERROR("Failed to get current time"); THROW_EXCEPTION(HTTPException, Internal); } const char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; date << dow[tp->tm_wday] << ", "; const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec"}; date << std::internal << std::setfill('0') << std::setw(2) << tp->tm_mday << " " << month[tp->tm_mon] << " " << (tp->tm_year + 1900) << " "; date << std::setw(2) << tp->tm_hour << ":" << std::setw(2) << tp->tm_min << ":" << std::setw(2) << tp->tm_sec << " GMT"; request.AddHeader("Date", date.str()); if (pStreamContentType) { request.AddHeader("Content-Type", pStreamContentType); } std::string s3suffix = ".s3.amazonaws.com"; std::string bucket; if (mHostName.size() > s3suffix.size()) { std::string suffix = mHostName.substr(mHostName.size() - s3suffix.size(), s3suffix.size()); if (suffix == s3suffix) { bucket = mHostName.substr(0, mHostName.size() - s3suffix.size()); } } std::ostringstream data; data << request.GetVerb() << "\n"; data << "\n"; /* Content-MD5 */ data << request.GetContentType() << "\n"; data << date.str() << "\n"; if (! bucket.empty()) { data << "/" << bucket; } data << request.GetRequestURI(); std::string data_string = data.str(); unsigned char digest_buffer[EVP_MAX_MD_SIZE]; unsigned int digest_size = sizeof(digest_buffer); /* unsigned char* mac = */ HMAC(EVP_sha1(), mSecretKey.c_str(), mSecretKey.size(), (const unsigned char*)data_string.c_str(), data_string.size(), digest_buffer, &digest_size); std::string digest((const char *)digest_buffer, digest_size); base64::encoder encoder; std::string auth_code = "AWS " + mAccessKey + ":" + encoder.encode(digest); if (auth_code[auth_code.size() - 1] == '\n') { auth_code = auth_code.substr(0, auth_code.size() - 1); } request.AddHeader("Authorization", auth_code); if (mpSimulator) { if (pStreamToSend) { pStreamToSend->CopyStreamTo(request); } request.SetForReading(); CollectInBufferStream response_buffer; HTTPResponse response(&response_buffer); mpSimulator->Handle(request, response); return response; } else { try { if (!mapClientSocket.get()) { mapClientSocket.reset(new SocketStream()); mapClientSocket->Open(Socket::TypeINET, mHostName, mPort); } return SendRequest(request, pStreamToSend, pStreamContentType); } catch (ConnectionException &ce) { if (ce.GetType() == ConnectionException::SocketWriteError) { // server may have disconnected us, // try to reconnect, just once mapClientSocket->Open(Socket::TypeINET, mHostName, mPort); return SendRequest(request, pStreamToSend, pStreamContentType); } else { throw; } } } } // -------------------------------------------------------------------------- // // Function // Name: S3Client::SendRequest(HTTPRequest& rRequest, // IOStream* pStreamToSend, // const char* pStreamContentType) // Purpose: Internal method which sends a pre-existing HTTP // request to S3. Attaches the specified stream if any // to the request. Opens a connection to the server if // necessary, which may throw a ConnectionException. // Returns the HTTP response returned by S3, which may // be a 500 error. // Created: 09/01/09 // // -------------------------------------------------------------------------- HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest, IOStream* pStreamToSend, const char* pStreamContentType) { HTTPResponse response; if (pStreamToSend) { rRequest.SendWithStream(*mapClientSocket, 30000 /* milliseconds */, pStreamToSend, response); } else { rRequest.Send(*mapClientSocket, 30000 /* milliseconds */); response.Receive(*mapClientSocket, 30000 /* milliseconds */); } return response; } boxbackup/lib/httpserver/decode.h0000664000175000017500000000325611130655017017667 0ustar siretartsiretart// :mode=c++: /* decode.h - c++ wrapper for a base64 decoding algorithm This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 */ #ifndef BASE64_DECODE_H #define BASE64_DECODE_H #include namespace base64 { extern "C" { #include "cdecode.h" } struct decoder { base64_decodestate _state; int _buffersize; decoder(int buffersize_in = 4096) : _buffersize(buffersize_in) {} int decode(char value_in) { return base64_decode_value(value_in); } int decode(const char* code_in, const int length_in, char* plaintext_out) { return base64_decode_block(code_in, length_in, plaintext_out, &_state); } std::string decode(const std::string& input) { base64_init_decodestate(&_state); char* output = new char[2*input.size()]; int outlength = decode(input.c_str(), input.size(), output); std::string output_string(output, outlength); base64_init_decodestate(&_state); delete [] output; return output_string; } void decode(std::istream& istream_in, std::ostream& ostream_in) { base64_init_decodestate(&_state); // const int N = _buffersize; char* code = new char[N]; char* plaintext = new char[N]; int codelength; int plainlength; do { istream_in.read((char*)code, N); codelength = istream_in.gcount(); plainlength = decode(code, codelength, plaintext); ostream_in.write((const char*)plaintext, plainlength); } while (istream_in.good() && codelength > 0); // base64_init_decodestate(&_state); delete [] code; delete [] plaintext; } }; } // namespace base64 #endif // BASE64_DECODE_H boxbackup/lib/httpserver/HTTPQueryDecoder.h0000664000175000017500000000220310476004573021535 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPQueryDecoder.h // Purpose: Utility class to decode HTTP query strings // Created: 26/3/04 // // -------------------------------------------------------------------------- #ifndef HTTPQUERYDECODER__H #define HTTPQUERYDECODER__H #include "HTTPRequest.h" // -------------------------------------------------------------------------- // // Class // Name: HTTPQueryDecoder // Purpose: Utility class to decode HTTP query strings // Created: 26/3/04 // // -------------------------------------------------------------------------- class HTTPQueryDecoder { public: HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto); ~HTTPQueryDecoder(); private: // no copying HTTPQueryDecoder(const HTTPQueryDecoder &); HTTPQueryDecoder &operator=(const HTTPQueryDecoder &); public: void DecodeChunk(const char *pQueryString, int QueryStringSize); void Finish(); private: HTTPRequest::Query_t &mrDecodeInto; std::string mCurrentKey; std::string mCurrentValue; bool mInKey; char mEscaped[4]; int mEscapedState; }; #endif // HTTPQUERYDECODER__H boxbackup/lib/httpserver/HTTPResponse.h0000664000175000017500000001225611131622237020741 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPResponse.h // Purpose: Response object for HTTP connections // Created: 26/3/04 // // -------------------------------------------------------------------------- #ifndef HTTPRESPONSE__H #define HTTPRESPONSE__H #include #include #include "CollectInBufferStream.h" class IOStreamGetLine; // -------------------------------------------------------------------------- // // Class // Name: HTTPResponse // Purpose: Response object for HTTP connections // Created: 26/3/04 // // -------------------------------------------------------------------------- class HTTPResponse : public CollectInBufferStream { public: HTTPResponse(IOStream* pStreamToSendTo); HTTPResponse(); ~HTTPResponse(); // allow copying, but be very careful with the response stream, // you can only read it once! (this class doesn't police it). HTTPResponse(const HTTPResponse& rOther) : mResponseCode(rOther.mResponseCode), mResponseIsDynamicContent(rOther.mResponseIsDynamicContent), mKeepAlive(rOther.mKeepAlive), mContentType(rOther.mContentType), mExtraHeaders(rOther.mExtraHeaders), mContentLength(rOther.mContentLength), mpStreamToSendTo(rOther.mpStreamToSendTo) { Write(rOther.GetBuffer(), rOther.GetSize()); } HTTPResponse &operator=(const HTTPResponse &rOther) { Reset(); Write(rOther.GetBuffer(), rOther.GetSize()); mResponseCode = rOther.mResponseCode; mResponseIsDynamicContent = rOther.mResponseIsDynamicContent; mKeepAlive = rOther.mKeepAlive; mContentType = rOther.mContentType; mExtraHeaders = rOther.mExtraHeaders; mContentLength = rOther.mContentLength; mpStreamToSendTo = rOther.mpStreamToSendTo; return *this; } typedef std::pair Header; void SetResponseCode(int Code); int GetResponseCode() { return mResponseCode; } void SetContentType(const char *ContentType); const std::string& GetContentType() { return mContentType; } void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true); void SetAsNotFound(const char *URI); void Send(bool OmitContent = false); void SendContinue(); void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite); // void AddHeader(const char *EntireHeaderLine); // void AddHeader(const std::string &rEntireHeaderLine); void AddHeader(const char *Header, const char *Value); void AddHeader(const char *Header, const std::string &rValue); void AddHeader(const std::string &rHeader, const std::string &rValue); bool GetHeader(const std::string& rName, std::string* pValueOut) const { for (std::vector
::const_iterator i = mExtraHeaders.begin(); i != mExtraHeaders.end(); i++) { if (i->first == rName) { *pValueOut = i->second; return true; } } return false; } std::string GetHeaderValue(const std::string& rName) { std::string value; if (!GetHeader(rName, &value)) { THROW_EXCEPTION(CommonException, ConfigNoKey); } return value; } // Set dynamic content flag, default is content is dynamic void SetResponseIsDynamicContent(bool IsDynamic) {mResponseIsDynamicContent = IsDynamic;} // Set keep alive control, default is to mark as to be closed void SetKeepAlive(bool KeepAlive) {mKeepAlive = KeepAlive;} void SetCookie(const char *Name, const char *Value, const char *Path = "/", int ExpiresAt = 0); enum { Code_OK = 200, Code_NoContent = 204, Code_MovedPermanently = 301, Code_Found = 302, // redirection Code_NotModified = 304, Code_TemporaryRedirect = 307, Code_MethodNotAllowed = 400, Code_Unauthorized = 401, Code_Forbidden = 403, Code_NotFound = 404, Code_InternalServerError = 500, Code_NotImplemented = 501 }; static const char *ResponseCodeToString(int ResponseCode); void WriteStringDefang(const char *String, unsigned int StringLen); void WriteStringDefang(const std::string &rString) {WriteStringDefang(rString.c_str(), rString.size());} // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::WriteString(const std::string &) // Purpose: Write a string to the response (simple sugar function) // Created: 9/4/04 // // -------------------------------------------------------------------------- void WriteString(const std::string &rString) { Write(rString.c_str(), rString.size()); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::SetDefaultURIPrefix(const std::string &) // Purpose: Set default prefix used to local redirections // Created: 26/3/04 // // -------------------------------------------------------------------------- static void SetDefaultURIPrefix(const std::string &rPrefix) { msDefaultURIPrefix = rPrefix; } private: int mResponseCode; bool mResponseIsDynamicContent; bool mKeepAlive; std::string mContentType; std::vector
mExtraHeaders; int mContentLength; // only used when reading response from stream IOStream* mpStreamToSendTo; // nonzero only when constructed with a stream static std::string msDefaultURIPrefix; void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); }; #endif // HTTPRESPONSE__H boxbackup/lib/httpserver/HTTPResponse.cpp0000664000175000017500000004215111130253447021273 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPResponse.cpp // Purpose: Response object for HTTP connections // Created: 26/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "HTTPResponse.h" #include "IOStreamGetLine.h" #include "autogen_HTTPException.h" #include "MemLeakFindOn.h" // Static variables std::string HTTPResponse::msDefaultURIPrefix; // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::HTTPResponse(IOStream*) // Purpose: Constructor for response to be sent to a stream // Created: 04/01/09 // // -------------------------------------------------------------------------- HTTPResponse::HTTPResponse(IOStream* pStreamToSendTo) : mResponseCode(HTTPResponse::Code_NoContent), mResponseIsDynamicContent(true), mKeepAlive(false), mContentLength(-1), mpStreamToSendTo(pStreamToSendTo) { } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::HTTPResponse() // Purpose: Constructor // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPResponse::HTTPResponse() : mResponseCode(HTTPResponse::Code_NoContent), mResponseIsDynamicContent(true), mKeepAlive(false), mContentLength(-1), mpStreamToSendTo(NULL) { } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::~HTTPResponse() // Purpose: Destructor // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPResponse::~HTTPResponse() { } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::ResponseCodeToString(int) // Purpose: Return string equivalent of the response code, // suitable for Status: headers // Created: 26/3/04 // // -------------------------------------------------------------------------- const char *HTTPResponse::ResponseCodeToString(int ResponseCode) { switch(ResponseCode) { case Code_OK: return "200 OK"; break; case Code_NoContent: return "204 No Content"; break; case Code_MovedPermanently: return "301 Moved Permanently"; break; case Code_Found: return "302 Found"; break; case Code_NotModified: return "304 Not Modified"; break; case Code_TemporaryRedirect: return "307 Temporary Redirect"; break; case Code_MethodNotAllowed: return "400 Method Not Allowed"; break; case Code_Unauthorized: return "401 Unauthorized"; break; case Code_Forbidden: return "403 Forbidden"; break; case Code_NotFound: return "404 Not Found"; break; case Code_InternalServerError: return "500 Internal Server Error"; break; case Code_NotImplemented: return "501 Not Implemented"; break; default: { THROW_EXCEPTION(HTTPException, UnknownResponseCodeUsed) } } return "500 Internal Server Error"; } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::SetResponseCode(int) // Purpose: Set the response code to be returned // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::SetResponseCode(int Code) { mResponseCode = Code; } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::SetContentType(const char *) // Purpose: Set content type // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::SetContentType(const char *ContentType) { mContentType = ContentType; } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::Send(IOStream &, bool) // Purpose: Build the response, and send via the stream. // Optionally omitting the content. // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::Send(bool OmitContent) { if (!mpStreamToSendTo) { THROW_EXCEPTION(HTTPException, NoStreamConfigured); } if (GetSize() != 0 && mContentType.empty()) { THROW_EXCEPTION(HTTPException, NoContentTypeSet); } // Build and send header { std::string header("HTTP/1.1 "); header += ResponseCodeToString(mResponseCode); header += "\r\nContent-Type: "; header += mContentType; header += "\r\nContent-Length: "; { char len[32]; ::sprintf(len, "%d", OmitContent?(0):(GetSize())); header += len; } // Extra headers... for(std::vector >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i) { header += "\r\n"; header += i->first + ": " + i->second; } // NOTE: a line ending must be included here in all cases // Control whether the response is cached if(mResponseIsDynamicContent) { // dynamic is private and can't be cached header += "\r\nCache-Control: no-cache, private"; } else { // static is allowed to be cached for a day header += "\r\nCache-Control: max-age=86400"; } if(mKeepAlive) { header += "\r\nConnection: keep-alive\r\n\r\n"; } else { header += "\r\nConnection: close\r\n\r\n"; } // NOTE: header ends with blank line in all cases // Write to stream mpStreamToSendTo->Write(header.c_str(), header.size()); } // Send content if(!OmitContent) { mpStreamToSendTo->Write(GetBuffer(), GetSize()); } } void HTTPResponse::SendContinue() { mpStreamToSendTo->Write("HTTP/1.1 100 Continue\r\n"); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::ParseHeaders(IOStreamGetLine &, int) // Purpose: Private. Parse the headers of the response // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) { std::string header; bool haveHeader = false; while(true) { if(rGetLine.IsEOF()) { // Header terminates unexpectedly THROW_EXCEPTION(HTTPException, BadRequest) } std::string currentLine; if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout)) { // Timeout THROW_EXCEPTION(HTTPException, RequestReadFailed) } // Is this a continuation of the previous line? bool processHeader = haveHeader; if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t')) { // A continuation, don't process anything yet processHeader = false; } //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str()); // Parse the header -- this will actually process the header // from the previous run around the loop. if(processHeader) { // Find where the : is in the line const char *h = header.c_str(); int p = 0; while(h[p] != '\0' && h[p] != ':') { ++p; } // Skip white space int dataStart = p + 1; while(h[dataStart] == ' ' || h[dataStart] == '\t') { ++dataStart; } if(p == sizeof("Content-Length")-1 && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0) { // Decode number long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK if(len < 0) len = 0; // Store mContentLength = len; } else if(p == sizeof("Content-Type")-1 && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0) { // Store rest of string as content type mContentType = h + dataStart; } else if(p == sizeof("Cookie")-1 && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0) { THROW_EXCEPTION(HTTPException, NotImplemented); /* // Parse cookies ParseCookies(header, dataStart); */ } else if(p == sizeof("Connection")-1 && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0) { // Connection header, what is required? const char *v = h + dataStart; if(::strcasecmp(v, "close") == 0) { mKeepAlive = false; } else if(::strcasecmp(v, "keep-alive") == 0) { mKeepAlive = true; } // else don't understand, just assume default for protocol version } else { std::string headerName = header.substr(0, p); AddHeader(headerName, h + dataStart); } // Unset have header flag, as it's now been processed haveHeader = false; } // Store the chunk of header the for next time round if(haveHeader) { header += currentLine; } else { header = currentLine; haveHeader = true; } // End of headers? if(currentLine.empty()) { // All done! break; } } } void HTTPResponse::Receive(IOStream& rStream, int Timeout) { IOStreamGetLine rGetLine(rStream); if(rGetLine.IsEOF()) { // Connection terminated unexpectedly THROW_EXCEPTION(HTTPException, BadResponse) } std::string statusLine; if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout)) { // Timeout THROW_EXCEPTION(HTTPException, ResponseReadFailed) } if (statusLine.substr(0, 7) != "HTTP/1." || statusLine[8] != ' ') { // Status line terminated unexpectedly BOX_ERROR("Bad response status line: " << statusLine); THROW_EXCEPTION(HTTPException, BadResponse) } if (statusLine[5] == '1' && statusLine[7] == '1') { // HTTP/1.1 default is to keep alive mKeepAlive = true; } // Decode the status code long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10); // returns zero in error case, this is OK if (status < 0) status = 0; // Store mResponseCode = status; // 100 Continue responses have no headers, terminating newline, or body if (status == 100) { return; } ParseHeaders(rGetLine, Timeout); // push back whatever bytes we have left // rGetLine.DetachFile(); if (mContentLength > 0) { if (mContentLength < rGetLine.GetSizeOfBufferedData()) { // very small response, not good! THROW_EXCEPTION(HTTPException, NotImplemented); } mContentLength -= rGetLine.GetSizeOfBufferedData(); Write(rGetLine.GetBufferedData(), rGetLine.GetSizeOfBufferedData()); } while (mContentLength != 0) // could be -1 as well { char buffer[4096]; int readSize = sizeof(buffer); if (mContentLength > 0 && mContentLength < readSize) { readSize = mContentLength; } readSize = rStream.Read(buffer, readSize, Timeout); if (readSize == 0) { break; } mContentLength -= readSize; Write(buffer, readSize); } SetForReading(); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::AddHeader(const char *) // Purpose: Add header, given entire line // Created: 26/3/04 // // -------------------------------------------------------------------------- /* void HTTPResponse::AddHeader(const char *EntireHeaderLine) { mExtraHeaders.push_back(std::string(EntireHeaderLine)); } */ // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::AddHeader(const std::string &) // Purpose: Add header, given entire line // Created: 26/3/04 // // -------------------------------------------------------------------------- /* void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine) { mExtraHeaders.push_back(rEntireHeaderLine); } */ // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::AddHeader(const char *, const char *) // Purpose: Add header, given header name and it's value // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::AddHeader(const char *pHeader, const char *pValue) { mExtraHeaders.push_back(Header(pHeader, pValue)); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::AddHeader(const char *, const std::string &) // Purpose: Add header, given header name and it's value // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue) { mExtraHeaders.push_back(Header(pHeader, rValue)); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::AddHeader(const std::string &, const std::string &) // Purpose: Add header, given header name and it's value // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue) { mExtraHeaders.push_back(Header(rHeader, rValue)); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::SetCookie(const char *, const char *, const char *, int) // Purpose: Sets a cookie, using name, value, path and expiry time. // Created: 20/8/04 // // -------------------------------------------------------------------------- void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Path, int ExpiresAt) { if(ExpiresAt != 0) { THROW_EXCEPTION(HTTPException, NotImplemented) } // Appears you shouldn't use quotes when you generate set-cookie headers. // Oh well. It was fun finding that out. /* std::string h("Set-Cookie: "); h += Name; h += "=\""; h += Value; h += "\"; Version=\"1\"; Path=\""; h += Path; h += "\""; */ std::string h; h += Name; h += "="; h += Value; h += "; Version=1; Path="; h += Path; mExtraHeaders.push_back(Header("Set-Cookie", h)); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::SetAsRedirect(const char *, bool) // Purpose: Sets the response objects to be a redirect to another page. // If IsLocalURL == true, the default prefix will be added. // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI) { if(mResponseCode != HTTPResponse::Code_NoContent || !mContentType.empty() || GetSize() != 0) { THROW_EXCEPTION(HTTPException, CannotSetRedirectIfReponseHasData) } // Set response code mResponseCode = Code_Found; // Set location to redirect to std::string header; if(IsLocalURI) header += msDefaultURIPrefix; header += RedirectTo; mExtraHeaders.push_back(Header("Location", header)); // Set up some default content mContentType = "text/html"; #define REDIRECT_HTML_1 "Redirection\n

Redirect to content

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

404 Not Found

\n

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

\n" Write(NOT_FOUND_HTML_1, sizeof(NOT_FOUND_HTML_1) - 1); WriteStringDefang(std::string(URI)); Write(NOT_FOUND_HTML_2, sizeof(NOT_FOUND_HTML_2) - 1); } // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::WriteStringDefang(const char *, unsigned int) // Purpose: Writes a string 'defanged', ie has HTML special characters escaped // so that people can't output arbitary HTML by playing with // URLs and form parameters, and it's safe to write strings into // HTML element attribute values. // Created: 9/4/04 // // -------------------------------------------------------------------------- void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen) { while(StringLen > 0) { unsigned int toWrite = 0; while(toWrite < StringLen && String[toWrite] != '<' && String[toWrite] != '>' && String[toWrite] != '&' && String[toWrite] != '"') { ++toWrite; } if(toWrite > 0) { Write(String, toWrite); StringLen -= toWrite; String += toWrite; } // Is it a bad character next? while(StringLen > 0) { bool notSpecial = false; switch(*String) { case '<': Write("<", 4); break; case '>': Write(">", 4); break; case '&': Write("&", 5); break; case '"': Write(""", 6); break; default: // Stop this loop notSpecial = true; break; } if(notSpecial) break; ++String; --StringLen; } } } boxbackup/lib/httpserver/encode.h0000664000175000017500000000361311130655017017676 0ustar siretartsiretart// :mode=c++: /* encode.h - c++ wrapper for a base64 encoding algorithm This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 */ #ifndef BASE64_ENCODE_H #define BASE64_ENCODE_H #include namespace base64 { extern "C" { #include "cencode.h" } struct encoder { base64_encodestate _state; int _buffersize; encoder(int buffersize_in = 4096) : _buffersize(buffersize_in) {} int encode(char value_in) { return base64_encode_value(value_in); } int encode(const char* code_in, const int length_in, char* plaintext_out) { return base64_encode_block(code_in, length_in, plaintext_out, &_state); } int encode_end(char* plaintext_out) { return base64_encode_blockend(plaintext_out, &_state); } std::string encode(const std::string& input) { base64_init_encodestate(&_state); char* output = new char[2*input.size()]; int outlength = encode(input.c_str(), input.size(), output); outlength += encode_end(output + outlength); std::string output_string(output, outlength); base64_init_encodestate(&_state); delete [] output; return output_string; } void encode(std::istream& istream_in, std::ostream& ostream_in) { base64_init_encodestate(&_state); // const int N = _buffersize; char* plaintext = new char[N]; char* code = new char[2*N]; int plainlength; int codelength; do { istream_in.read(plaintext, N); plainlength = istream_in.gcount(); // codelength = encode(plaintext, plainlength, code); ostream_in.write(code, codelength); } while (istream_in.good() && plainlength > 0); codelength = encode_end(code); ostream_in.write(code, codelength); // base64_init_encodestate(&_state); delete [] code; delete [] plaintext; } }; } // namespace base64 #endif // BASE64_ENCODE_H boxbackup/lib/httpserver/S3Simulator.h0000664000175000017500000000210211170703462020620 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: S3Simulator.h // Purpose: Amazon S3 simulation HTTP server for S3 testing // Created: 09/01/2009 // // -------------------------------------------------------------------------- #ifndef S3SIMULATOR__H #define S3SIMULATOR__H #include "HTTPServer.h" class ConfigurationVerify; class HTTPRequest; class HTTPResponse; // -------------------------------------------------------------------------- // // Class // Name: S3Simulator // Purpose: Amazon S3 simulation HTTP server for S3 testing // Created: 09/01/2009 // // -------------------------------------------------------------------------- class S3Simulator : public HTTPServer { public: S3Simulator() { } ~S3Simulator() { } const ConfigurationVerify* GetConfigVerify() const; virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); virtual void HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse); virtual void HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse); }; #endif // S3SIMULATOR__H boxbackup/lib/httpserver/HTTPRequest.cpp0000664000175000017500000004351311170703462021131 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPRequest.cpp // Purpose: Request object for HTTP connections // Created: 26/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include "HTTPRequest.h" #include "HTTPResponse.h" #include "HTTPQueryDecoder.h" #include "autogen_HTTPException.h" #include "IOStream.h" #include "IOStreamGetLine.h" #include "Logging.h" #include "MemLeakFindOn.h" #define MAX_CONTENT_SIZE (128*1024) #define ENSURE_COOKIE_JAR_ALLOCATED \ if(mpCookies == 0) {mpCookies = new CookieJar_t;} // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::HTTPRequest() // Purpose: Constructor // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPRequest::HTTPRequest() : mMethod(Method_UNINITIALISED), mHostPort(80), // default if not specified mHTTPVersion(0), mContentLength(-1), mpCookies(0), mClientKeepAliveRequested(false), mExpectContinue(false), mpStreamToReadFrom(NULL) { } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::HTTPRequest(enum Method, // const std::string&) // Purpose: Alternate constructor for hand-crafted requests // Created: 03/01/09 // // -------------------------------------------------------------------------- HTTPRequest::HTTPRequest(enum Method method, const std::string& rURI) : mMethod(method), mRequestURI(rURI), mHostPort(80), // default if not specified mHTTPVersion(HTTPVersion_1_1), mContentLength(-1), mpCookies(0), mClientKeepAliveRequested(false), mExpectContinue(false), mpStreamToReadFrom(NULL) { } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::~HTTPRequest() // Purpose: Destructor // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPRequest::~HTTPRequest() { // Clean up any cookies if(mpCookies != 0) { delete mpCookies; mpCookies = 0; } } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::Receive(IOStreamGetLine &, int) // Purpose: Read the request from an IOStreamGetLine (and // attached stream). // Returns false if there was no valid request, // probably due to a kept-alive connection closing. // Created: 26/3/04 // // -------------------------------------------------------------------------- bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout) { // Check caller's logic if(mMethod != Method_UNINITIALISED) { THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead) } // Read the first line, which is of a different format to the rest of the lines std::string requestLine; if(!rGetLine.GetLine(requestLine, false /* no preprocessing */, Timeout)) { // Didn't get the request line, probably end of connection which had been kept alive return false; } BOX_TRACE("Request line: " << requestLine); // Check the method size_t p = 0; // current position in string p = requestLine.find(' '); // end of first word if (p == std::string::npos) { // No terminating space, looks bad p = requestLine.size(); } else { mHttpVerb = requestLine.substr(0, p); if (mHttpVerb == "GET") { mMethod = Method_GET; } else if (mHttpVerb == "HEAD") { mMethod = Method_HEAD; } else if (mHttpVerb == "POST") { mMethod = Method_POST; } else if (mHttpVerb == "PUT") { mMethod = Method_PUT; } else { mMethod = Method_UNKNOWN; } } // Skip spaces to find URI const char *requestLinePtr = requestLine.c_str(); while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ') { ++p; } // Check there's a URI following... if(requestLinePtr[p] == '\0') { // Didn't get the request line, probably end of connection which had been kept alive return false; } // Read the URI, unescaping any %XX hex codes while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0') { // End of URI, on to query string? if(requestLinePtr[p] == '?') { // Put the rest into the query string, without escaping anything ++p; while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0') { mQueryString += requestLinePtr[p]; ++p; } break; } // Needs unescaping? else if(requestLinePtr[p] == '+') { mRequestURI += ' '; } else if(requestLinePtr[p] == '%') { // Be tolerant about this... bad things are silently accepted, // rather than throwing an error. char code[4] = {0,0,0,0}; code[0] = requestLinePtr[++p]; if(code[0] != '\0') { code[1] = requestLinePtr[++p]; } // Convert into a char code long c = ::strtol(code, NULL, 16); // Accept it? if(c > 0 && c <= 255) { mRequestURI += (char)c; } } else { // Simple copy of character mRequestURI += requestLinePtr[p]; } ++p; } // End of URL? if(requestLinePtr[p] == '\0') { // Assume HTTP 0.9 mHTTPVersion = HTTPVersion_0_9; } else { // Skip any more spaces while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ') { ++p; } // Check to see if there's the right string next... if(::strncmp(requestLinePtr + p, "HTTP/", 5) == 0) { // Find the version numbers int major, minor; if(::sscanf(requestLinePtr + p + 5, "%d.%d", &major, &minor) != 2) { THROW_EXCEPTION(HTTPException, BadRequest) } // Store version mHTTPVersion = (major * HTTPVersion__MajorMultiplier) + minor; } else { // Not good -- wrong string found THROW_EXCEPTION(HTTPException, BadRequest) } } BOX_TRACE("HTTPRequest: method=" << mMethod << ", uri=" << mRequestURI << ", version=" << mHTTPVersion); // If HTTP 1.1 or greater, assume keep-alive if(mHTTPVersion >= HTTPVersion_1_1) { mClientKeepAliveRequested = true; } // Decode query string? if((mMethod == Method_GET || mMethod == Method_HEAD) && !mQueryString.empty()) { HTTPQueryDecoder decoder(mQuery); decoder.DecodeChunk(mQueryString.c_str(), mQueryString.size()); decoder.Finish(); } // Now parse the headers ParseHeaders(rGetLine, Timeout); std::string expected; if (GetHeader("Expect", &expected)) { if (expected == "100-continue") { mExpectContinue = true; } } // Parse form data? if(mMethod == Method_POST && mContentLength >= 0) { // Too long? Don't allow people to be nasty by sending lots of data if(mContentLength > MAX_CONTENT_SIZE) { THROW_EXCEPTION(HTTPException, POSTContentTooLong) } // Some data in the request to follow, parsing it bit by bit HTTPQueryDecoder decoder(mQuery); // Don't forget any data left in the GetLine object int fromBuffer = rGetLine.GetSizeOfBufferedData(); if(fromBuffer > mContentLength) fromBuffer = mContentLength; if(fromBuffer > 0) { BOX_TRACE("Decoding " << fromBuffer << " bytes of " "data from getline buffer"); decoder.DecodeChunk((const char *)rGetLine.GetBufferedData(), fromBuffer); // And tell the getline object to ignore the data we just used rGetLine.IgnoreBufferedData(fromBuffer); } // Then read any more data, as required int bytesToGo = mContentLength - fromBuffer; while(bytesToGo > 0) { char buf[4096]; int toRead = sizeof(buf); if(toRead > bytesToGo) toRead = bytesToGo; IOStream &rstream(rGetLine.GetUnderlyingStream()); int r = rstream.Read(buf, toRead, Timeout); if(r == 0) { // Timeout, just error THROW_EXCEPTION(HTTPException, RequestReadFailed) } decoder.DecodeChunk(buf, r); bytesToGo -= r; } // Finish off decoder.Finish(); } else if (mContentLength > 0) { IOStream::pos_type bytesToCopy = rGetLine.GetSizeOfBufferedData(); if (bytesToCopy > mContentLength) { bytesToCopy = mContentLength; } Write(rGetLine.GetBufferedData(), bytesToCopy); SetForReading(); mpStreamToReadFrom = &(rGetLine.GetUnderlyingStream()); } return true; } void HTTPRequest::ReadContent(IOStream& rStreamToWriteTo) { Seek(0, SeekType_Absolute); CopyStreamTo(rStreamToWriteTo); IOStream::pos_type bytesCopied = GetSize(); while (bytesCopied < mContentLength) { char buffer[1024]; IOStream::pos_type bytesToCopy = sizeof(buffer); if (bytesToCopy > mContentLength - bytesCopied) { bytesToCopy = mContentLength - bytesCopied; } bytesToCopy = mpStreamToReadFrom->Read(buffer, bytesToCopy); rStreamToWriteTo.Write(buffer, bytesToCopy); bytesCopied += bytesToCopy; } } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::Send(IOStream &, int) // Purpose: Write the request to an IOStream using HTTP. // Created: 03/01/09 // // -------------------------------------------------------------------------- bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue) { switch (mMethod) { case Method_UNINITIALISED: THROW_EXCEPTION(HTTPException, RequestNotInitialised); break; case Method_UNKNOWN: THROW_EXCEPTION(HTTPException, BadRequest); break; case Method_GET: rStream.Write("GET"); break; case Method_HEAD: rStream.Write("HEAD"); break; case Method_POST: rStream.Write("POST"); break; case Method_PUT: rStream.Write("PUT"); break; } rStream.Write(" "); rStream.Write(mRequestURI.c_str()); rStream.Write(" "); switch (mHTTPVersion) { case HTTPVersion_0_9: rStream.Write("HTTP/0.9"); break; case HTTPVersion_1_0: rStream.Write("HTTP/1.0"); break; case HTTPVersion_1_1: rStream.Write("HTTP/1.1"); break; default: THROW_EXCEPTION(HTTPException, NotImplemented); } rStream.Write("\n"); std::ostringstream oss; if (mContentLength != -1) { oss << "Content-Length: " << mContentLength << "\n"; } if (mContentType != "") { oss << "Content-Type: " << mContentType << "\n"; } if (mHostName != "") { if (mHostPort != 80) { oss << "Host: " << mHostName << ":" << mHostPort << "\n"; } else { oss << "Host: " << mHostName << "\n"; } } if (mpCookies) { THROW_EXCEPTION(HTTPException, NotImplemented); } if (mClientKeepAliveRequested) { oss << "Connection: keep-alive\n"; } else { oss << "Connection: close\n"; } for (std::vector
::iterator i = mExtraHeaders.begin(); i != mExtraHeaders.end(); i++) { oss << i->first << ": " << i->second << "\n"; } if (ExpectContinue) { oss << "Expect: 100-continue\n"; } rStream.Write(oss.str().c_str()); rStream.Write("\n"); return true; } void HTTPRequest::SendWithStream(IOStream &rStreamToSendTo, int Timeout, IOStream* pStreamToSend, HTTPResponse& rResponse) { IOStream::pos_type size = pStreamToSend->BytesLeftToRead(); if (size != IOStream::SizeOfStreamUnknown) { mContentLength = size; } Send(rStreamToSendTo, Timeout, true); rResponse.Receive(rStreamToSendTo, Timeout); if (rResponse.GetResponseCode() != 100) { // bad response, abort now return; } pStreamToSend->CopyStreamTo(rStreamToSendTo, Timeout); // receive the final response rResponse.Receive(rStreamToSendTo, Timeout); } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::ParseHeaders(IOStreamGetLine &, int) // Purpose: Private. Parse the headers of the request // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) { std::string header; bool haveHeader = false; while(true) { if(rGetLine.IsEOF()) { // Header terminates unexpectedly THROW_EXCEPTION(HTTPException, BadRequest) } std::string currentLine; if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout)) { // Timeout THROW_EXCEPTION(HTTPException, RequestReadFailed) } // Is this a continuation of the previous line? bool processHeader = haveHeader; if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t')) { // A continuation, don't process anything yet processHeader = false; } //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str()); // Parse the header -- this will actually process the header // from the previous run around the loop. if(processHeader) { // Find where the : is in the line const char *h = header.c_str(); int p = 0; while(h[p] != '\0' && h[p] != ':') { ++p; } // Skip white space int dataStart = p + 1; while(h[dataStart] == ' ' || h[dataStart] == '\t') { ++dataStart; } std::string header_name(ToLowerCase(std::string(h, p))); if (header_name == "content-length") { // Decode number long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK if(len < 0) len = 0; // Store mContentLength = len; } else if (header_name == "content-type") { // Store rest of string as content type mContentType = h + dataStart; } else if (header_name == "host") { // Store host header mHostName = h + dataStart; // Is there a port number to split off? std::string::size_type colon = mHostName.find_first_of(':'); if(colon != std::string::npos) { // There's a port in the string... attempt to turn it into an int mHostPort = ::strtol(mHostName.c_str() + colon + 1, 0, 10); // Truncate the string to just the hostname mHostName = mHostName.substr(0, colon); BOX_TRACE("Host: header, hostname = " << "'" << mHostName << "', host " "port = " << mHostPort); } } else if (header_name == "cookie") { // Parse cookies ParseCookies(header, dataStart); } else if (header_name == "connection") { // Connection header, what is required? const char *v = h + dataStart; if(::strcasecmp(v, "close") == 0) { mClientKeepAliveRequested = false; } else if(::strcasecmp(v, "keep-alive") == 0) { mClientKeepAliveRequested = true; } // else don't understand, just assume default for protocol version } else { mExtraHeaders.push_back(Header(header_name, h + dataStart)); } // Unset have header flag, as it's now been processed haveHeader = false; } // Store the chunk of header the for next time round if(haveHeader) { header += currentLine; } else { header = currentLine; haveHeader = true; } // End of headers? if(currentLine.empty()) { // All done! break; } } } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::ParseCookies(const std::string &, int) // Purpose: Parse the cookie header // Created: 20/8/04 // // -------------------------------------------------------------------------- void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts) { const char *data = rHeader.c_str() + DataStarts; const char *pos = data; const char *itemStart = pos; std::string name; enum { s_NAME, s_VALUE, s_VALUE_QUOTED, s_FIND_NEXT_NAME } state = s_NAME; do { switch(state) { case s_NAME: { if(*pos == '=') { // Found the name. Store name.assign(itemStart, pos - itemStart); // Looking at values now state = s_VALUE; if((*(pos + 1)) == '"') { // Actually it's a quoted value, skip over that ++pos; state = s_VALUE_QUOTED; } // Record starting point for this item itemStart = pos + 1; } } break; case s_VALUE: { if(*pos == ';' || *pos == ',' || *pos == '\0') { // Name ends ENSURE_COOKIE_JAR_ALLOCATED std::string value(itemStart, pos - itemStart); (*mpCookies)[name] = value; // And move to the waiting stage state = s_FIND_NEXT_NAME; } } break; case s_VALUE_QUOTED: { if(*pos == '"') { // That'll do nicely, save it ENSURE_COOKIE_JAR_ALLOCATED std::string value(itemStart, pos - itemStart); (*mpCookies)[name] = value; // And move to the waiting stage state = s_FIND_NEXT_NAME; } } break; case s_FIND_NEXT_NAME: { // Skip over terminators and white space to get to the next name if(*pos != ';' && *pos != ',' && *pos != ' ' && *pos != '\t') { // Name starts here itemStart = pos; state = s_NAME; } } break; default: // Ooops THROW_EXCEPTION(HTTPException, Internal) break; } } while(*(pos++) != 0); } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::GetCookie(const char *, std::string &) const // Purpose: Fetch a cookie's value. If cookie not present, returns false // and string is unaltered. // Created: 20/8/04 // // -------------------------------------------------------------------------- bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) const { // Got any cookies? if(mpCookies == 0) { return false; } // See if it's there CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName))); if(v != mpCookies->end()) { // Return the value rValueOut = v->second; return true; } return false; } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::GetCookie(const char *) // Purpose: Return a string for the given cookie, or the null string if the // cookie has not been recieved. // Created: 22/8/04 // // -------------------------------------------------------------------------- const std::string &HTTPRequest::GetCookie(const char *CookieName) const { static const std::string noCookie; // Got any cookies? if(mpCookies == 0) { return noCookie; } // See if it's there CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName))); if(v != mpCookies->end()) { // Return the value return v->second; } return noCookie; } boxbackup/lib/httpserver/cencode.cpp0000664000175000017500000000501111130655017020366 0ustar siretartsiretart/* cencoder.c - c source to a base64 encoding algorithm implementation This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 */ extern "C" { #include "cencode.h" const int CHARS_PER_LINE = 72; void base64_init_encodestate(base64_encodestate* state_in) { state_in->step = step_A; state_in->result = 0; state_in->stepcount = 0; } char base64_encode_value(char value_in) { static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if (value_in > 63) return '='; return encoding[(int)value_in]; } int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) { const char* plainchar = plaintext_in; const char* const plaintextend = plaintext_in + length_in; char* codechar = code_out; char result; char fragment; result = state_in->result; switch (state_in->step) { while (1) { case step_A: if (plainchar == plaintextend) { state_in->result = result; state_in->step = step_A; return codechar - code_out; } fragment = *plainchar++; result = (fragment & 0x0fc) >> 2; *codechar++ = base64_encode_value(result); result = (fragment & 0x003) << 4; case step_B: if (plainchar == plaintextend) { state_in->result = result; state_in->step = step_B; return codechar - code_out; } fragment = *plainchar++; result |= (fragment & 0x0f0) >> 4; *codechar++ = base64_encode_value(result); result = (fragment & 0x00f) << 2; case step_C: if (plainchar == plaintextend) { state_in->result = result; state_in->step = step_C; return codechar - code_out; } fragment = *plainchar++; result |= (fragment & 0x0c0) >> 6; *codechar++ = base64_encode_value(result); result = (fragment & 0x03f) >> 0; *codechar++ = base64_encode_value(result); ++(state_in->stepcount); if (state_in->stepcount == CHARS_PER_LINE/4) { *codechar++ = '\n'; state_in->stepcount = 0; } } } /* control should not reach here */ return codechar - code_out; } int base64_encode_blockend(char* code_out, base64_encodestate* state_in) { char* codechar = code_out; switch (state_in->step) { case step_B: *codechar++ = base64_encode_value(state_in->result); *codechar++ = '='; *codechar++ = '='; break; case step_C: *codechar++ = base64_encode_value(state_in->result); *codechar++ = '='; break; case step_A: break; } *codechar++ = '\n'; return codechar - code_out; } } boxbackup/lib/httpserver/S3Client.h0000664000175000017500000000353411131634762020074 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: S3Client.h // Purpose: Amazon S3 client helper implementation class // Created: 09/01/2009 // // -------------------------------------------------------------------------- #ifndef S3CLIENT__H #define S3CLIENT__H #include #include #include "HTTPRequest.h" #include "SocketStream.h" class HTTPResponse; class HTTPServer; class IOStream; // -------------------------------------------------------------------------- // // Class // Name: S3Client // Purpose: Amazon S3 client helper implementation class // Created: 09/01/2009 // // -------------------------------------------------------------------------- class S3Client { public: S3Client(HTTPServer* pSimulator, const std::string& rHostName, const std::string& rAccessKey, const std::string& rSecretKey) : mpSimulator(pSimulator), mHostName(rHostName), mAccessKey(rAccessKey), mSecretKey(rSecretKey) { } S3Client(std::string HostName, int Port, const std::string& rAccessKey, const std::string& rSecretKey) : mpSimulator(NULL), mHostName(HostName), mPort(Port), mAccessKey(rAccessKey), mSecretKey(rSecretKey) { } HTTPResponse GetObject(const std::string& rObjectURI); HTTPResponse PutObject(const std::string& rObjectURI, IOStream& rStreamToSend, const char* pContentType = NULL); private: HTTPServer* mpSimulator; std::string mHostName; int mPort; std::auto_ptr mapClientSocket; std::string mAccessKey, mSecretKey; HTTPResponse FinishAndSendRequest(HTTPRequest::Method Method, const std::string& rRequestURI, IOStream* pStreamToSend = NULL, const char* pStreamContentType = NULL); HTTPResponse SendRequest(HTTPRequest& rRequest, IOStream* pStreamToSend = NULL, const char* pStreamContentType = NULL); }; #endif // S3CLIENT__H boxbackup/lib/httpserver/Makefile.extra0000664000175000017500000000031211345266370021053 0ustar siretartsiretart MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_HTTPException.h autogen_HTTPException.cpp: $(MAKEEXCEPTION) HTTPException.txt $(_PERL) $(MAKEEXCEPTION) HTTPException.txt boxbackup/lib/httpserver/HTTPServer.cpp0000664000175000017500000001411211170703462020740 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPServer.cpp // Purpose: HTTP server class // Created: 26/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "HTTPServer.h" #include "HTTPRequest.h" #include "HTTPResponse.h" #include "IOStreamGetLine.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::HTTPServer() // Purpose: Constructor // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPServer::HTTPServer() : mTimeout(20000) // default timeout leaves a little while for clients to get the second request in. { } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::~HTTPServer() // Purpose: Destructor // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPServer::~HTTPServer() { } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::DaemonName() // Purpose: As interface, generic name for daemon // Created: 26/3/04 // // -------------------------------------------------------------------------- const char *HTTPServer::DaemonName() const { return "generic-httpserver"; } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::GetConfigVerify() // Purpose: As interface -- return most basic config so it's only necessary to // provide this if you want to add extra directives. // Created: 26/3/04 // // -------------------------------------------------------------------------- const ConfigurationVerify *HTTPServer::GetConfigVerify() const { static ConfigurationVerifyKey verifyserverkeys[] = { HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses }; static ConfigurationVerify verifyserver[] = { { "Server", 0, verifyserverkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; static ConfigurationVerifyKey verifyrootkeys[] = { HTTPSERVER_VERIFY_ROOT_KEYS }; static ConfigurationVerify verify = { "root", verifyserver, verifyrootkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; return &verify; } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::Run() // Purpose: As interface. // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPServer::Run() { // Do some configuration stuff const Configuration &conf(GetConfiguration()); HTTPResponse::SetDefaultURIPrefix(conf.GetKeyValue("AddressPrefix")); // Let the base class do the work ServerStream::Run(); } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::Connection(SocketStream &) // Purpose: As interface, handle connection // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPServer::Connection(SocketStream &rStream) { // Create a get line object to use IOStreamGetLine getLine(rStream); // Notify dervived claases HTTPConnectionOpening(); bool handleRequests = true; while(handleRequests) { // Parse the request HTTPRequest request; if(!request.Receive(getLine, mTimeout)) { // Didn't get request, connection probably closed. break; } // Generate a response HTTPResponse response(&rStream); try { Handle(request, response); } catch(BoxException &e) { char exceptionCode[256]; ::sprintf(exceptionCode, "%s (%d/%d)", e.what(), e.GetType(), e.GetSubType()); SendInternalErrorResponse(exceptionCode, response); } catch(...) { SendInternalErrorResponse("unknown", response); } // Keep alive? if(request.GetClientKeepAliveRequested()) { // Mark the response to the client as supporting keepalive response.SetKeepAlive(true); } else { // Stop now handleRequests = false; } // Send the response (omit any content if this is a HEAD method request) response.Send(request.GetMethod() == HTTPRequest::Method_HEAD); } // Notify derived classes HTTPConnectionClosing(); } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::SendInternalErrorResponse(const char*, // HTTPResponse&) // Purpose: Generates an error message in the provided response // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPServer::SendInternalErrorResponse(const std::string& rErrorMsg, HTTPResponse& rResponse) { #define ERROR_HTML_1 "Internal Server Error\n" \ "

Internal Server Error

\n" \ "

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

" \ "

Please try again later.

" \ "\n\n" // Generate the error page // rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); rResponse.SetContentType("text/html"); rResponse.Write(ERROR_HTML_1, sizeof(ERROR_HTML_1) - 1); rResponse.IOStream::Write(rErrorMsg.c_str()); rResponse.Write(ERROR_HTML_2, sizeof(ERROR_HTML_2) - 1); } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::HTTPConnectionOpening() // Purpose: Override to get notifications of connections opening // Created: 22/12/04 // // -------------------------------------------------------------------------- void HTTPServer::HTTPConnectionOpening() { } // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::HTTPConnectionClosing() // Purpose: Override to get notifications of connections closing // Created: 22/12/04 // // -------------------------------------------------------------------------- void HTTPServer::HTTPConnectionClosing() { } boxbackup/lib/httpserver/cdecode.h0000664000175000017500000000121011130655017020016 0ustar siretartsiretart/* cdecode.h - c header for a base64 decoding algorithm This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 */ #ifndef BASE64_CDECODE_H #define BASE64_CDECODE_H typedef enum { step_a, step_b, step_c, step_d } base64_decodestep; typedef struct { base64_decodestep step; char plainchar; } base64_decodestate; void base64_init_decodestate(base64_decodestate* state_in); int base64_decode_value(char value_in); int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); #endif /* BASE64_CDECODE_H */ boxbackup/lib/httpserver/HTTPServer.h0000664000175000017500000000426411170703462020414 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPServer.h // Purpose: HTTP server class // Created: 26/3/04 // // -------------------------------------------------------------------------- #ifndef HTTPSERVER__H #define HTTPSERVER__H #include "ServerStream.h" #include "SocketStream.h" class HTTPRequest; class HTTPResponse; // -------------------------------------------------------------------------- // // Class // Name: HTTPServer // Purpose: HTTP server // Created: 26/3/04 // // -------------------------------------------------------------------------- class HTTPServer : public ServerStream { public: HTTPServer(); ~HTTPServer(); private: // no copying HTTPServer(const HTTPServer &); HTTPServer &operator=(const HTTPServer &); public: int GetTimeout() const {return mTimeout;} // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::Handle(const HTTPRequest &, HTTPResponse &) // Purpose: Response to a request, filling in the response object for sending // at some point in the future. // Created: 26/3/04 // // -------------------------------------------------------------------------- virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) = 0; // For notifications to derived classes virtual void HTTPConnectionOpening(); virtual void HTTPConnectionClosing(); protected: void SendInternalErrorResponse(const std::string& rErrorMsg, HTTPResponse& rResponse); int GetTimeout() { return mTimeout; } private: int mTimeout; // Timeout for read operations const char *DaemonName() const; const ConfigurationVerify *GetConfigVerify() const; void Run(); void Connection(SocketStream &rStream); }; // Root level #define HTTPSERVER_VERIFY_ROOT_KEYS \ ConfigurationVerifyKey("AddressPrefix", \ ConfigTest_Exists | ConfigTest_LastEntry) // AddressPrefix is, for example, http://localhost:1080 -- ie the beginning of the URI // This is used for handling redirections. // Server level #define HTTPSERVER_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) #endif // HTTPSERVER__H boxbackup/lib/httpserver/HTTPQueryDecoder.cpp0000664000175000017500000000733311127624403022073 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPQueryDecoder.cpp // Purpose: Utility class to decode HTTP query strings // Created: 26/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "HTTPQueryDecoder.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: HTTPQueryDecoder::HTTPQueryDecoder( // HTTPRequest::Query_t &) // Purpose: Constructor. Pass in the query contents you want // to decode the query string into. // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto) : mrDecodeInto(rDecodeInto), mInKey(true), mEscapedState(0) { // Insert the terminator for escaped characters mEscaped[2] = '\0'; } // -------------------------------------------------------------------------- // // Function // Name: HTTPQueryDecoder::~HTTPQueryDecoder() // Purpose: Destructor. // Created: 26/3/04 // // -------------------------------------------------------------------------- HTTPQueryDecoder::~HTTPQueryDecoder() { } // -------------------------------------------------------------------------- // // Function // Name: HTTPQueryDecoder::Decode(const char *, int) // Purpose: Decode a chunk of query string -- call several times with // the bits as they are received, and then call Finish() // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPQueryDecoder::DecodeChunk(const char *pQueryString, int QueryStringSize) { for(int l = 0; l < QueryStringSize; ++l) { char c = pQueryString[l]; // BEFORE unescaping, check to see if we need to flip key / value if(mEscapedState == 0) { if(mInKey && c == '=') { // Set to store characters in the value mInKey = false; continue; } else if(!mInKey && c == '&') { // Need to store the current key/value pair mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue)); // Blank the strings mCurrentKey.erase(); mCurrentValue.erase(); // Set to store characters in the key mInKey = true; continue; } } // Decode an escaped value? if(mEscapedState == 1) { // Waiting for char one of the escaped hex value mEscaped[0] = c; mEscapedState = 2; continue; } else if(mEscapedState == 2) { // Escaped value, decode it mEscaped[1] = c; // str terminated in constructor mEscapedState = 0; // stop being in escaped mode long ch = ::strtol(mEscaped, NULL, 16); if(ch <= 0 || ch > 255) { // Bad character, just ignore continue; } // Use this instead c = (char)ch; } else if(c == '+') { c = ' '; } else if(c == '%') { mEscapedState = 1; continue; } // Store decoded value into the appropriate string if(mInKey) { mCurrentKey += c; } else { mCurrentValue += c; } } // Don't do anything here with left over values, DecodeChunk might be called // again. Let Finish() clean up. } // -------------------------------------------------------------------------- // // Function // Name: HTTPQueryDecoder::Finish() // Purpose: Finish the decoding. Necessary to get the last item! // Created: 26/3/04 // // -------------------------------------------------------------------------- void HTTPQueryDecoder::Finish() { // Insert any remaining value. if(!mCurrentKey.empty()) { mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue)); // Blank values, just in case mCurrentKey.erase(); mCurrentValue.erase(); } } boxbackup/lib/httpserver/HTTPRequest.h0000664000175000017500000001217011436174347020602 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HTTPRequest.h // Purpose: Request object for HTTP connections // Created: 26/3/04 // // -------------------------------------------------------------------------- #ifndef HTTPREQUEST__H #define HTTPREQUEST__H #include #include #include "CollectInBufferStream.h" class HTTPResponse; class IOStream; class IOStreamGetLine; // -------------------------------------------------------------------------- // // Class // Name: HTTPRequest // Purpose: Request object for HTTP connections // Created: 26/3/04 // // -------------------------------------------------------------------------- class HTTPRequest : public CollectInBufferStream { public: enum Method { Method_UNINITIALISED = -1, Method_UNKNOWN = 0, Method_GET = 1, Method_HEAD = 2, Method_POST = 3, Method_PUT = 4 }; HTTPRequest(); HTTPRequest(enum Method method, const std::string& rURI); ~HTTPRequest(); private: // no copying HTTPRequest(const HTTPRequest &); HTTPRequest &operator=(const HTTPRequest &); public: typedef std::multimap Query_t; typedef Query_t::value_type QueryEn_t; typedef std::pair Header; enum { HTTPVersion__MajorMultiplier = 1000, HTTPVersion_0_9 = 9, HTTPVersion_1_0 = 1000, HTTPVersion_1_1 = 1001 }; bool Receive(IOStreamGetLine &rGetLine, int Timeout); bool Send(IOStream &rStream, int Timeout, bool ExpectContinue = false); void SendWithStream(IOStream &rStreamToSendTo, int Timeout, IOStream* pStreamToSend, HTTPResponse& rResponse); void ReadContent(IOStream& rStreamToWriteTo); typedef std::map CookieJar_t; // -------------------------------------------------------------------------- // // Function // Name: HTTPResponse::Get*() // Purpose: Various Get accessors // Created: 26/3/04 // // -------------------------------------------------------------------------- enum Method GetMethod() const {return mMethod;} const std::string &GetRequestURI() const {return mRequestURI;} // Note: the HTTPRequest generates and parses the Host: header // Do not attempt to set one yourself with AddHeader(). const std::string &GetHostName() const {return mHostName;} void SetHostName(const std::string& rHostName) { mHostName = rHostName; } const int GetHostPort() const {return mHostPort;} const std::string &GetQueryString() const {return mQueryString;} int GetHTTPVersion() const {return mHTTPVersion;} const Query_t &GetQuery() const {return mQuery;} int GetContentLength() const {return mContentLength;} const std::string &GetContentType() const {return mContentType;} const CookieJar_t *GetCookies() const {return mpCookies;} // WARNING: May return NULL bool GetCookie(const char *CookieName, std::string &rValueOut) const; const std::string &GetCookie(const char *CookieName) const; bool GetHeader(const std::string& rName, std::string* pValueOut) const { std::string header = ToLowerCase(rName); for (std::vector
::const_iterator i = mExtraHeaders.begin(); i != mExtraHeaders.end(); i++) { if (i->first == header) { *pValueOut = i->second; return true; } } return false; } std::vector
GetHeaders() { return mExtraHeaders; } // -------------------------------------------------------------------------- // // Function // Name: HTTPRequest::GetClientKeepAliveRequested() // Purpose: Returns true if the client requested that the connection // should be kept open for further requests. // Created: 22/12/04 // // -------------------------------------------------------------------------- bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;} void SetClientKeepAliveRequested(bool keepAlive) { mClientKeepAliveRequested = keepAlive; } void AddHeader(const std::string& rName, const std::string& rValue) { mExtraHeaders.push_back(Header(ToLowerCase(rName), rValue)); } bool IsExpectingContinue() const { return mExpectContinue; } const char* GetVerb() const { if (!mHttpVerb.empty()) { return mHttpVerb.c_str(); } switch (mMethod) { case Method_UNINITIALISED: return "Uninitialized"; case Method_UNKNOWN: return "Unknown"; case Method_GET: return "GET"; case Method_HEAD: return "HEAD"; case Method_POST: return "POST"; case Method_PUT: return "PUT"; } return "Bad"; } private: void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); void ParseCookies(const std::string &rHeader, int DataStarts); enum Method mMethod; std::string mRequestURI; std::string mHostName; int mHostPort; std::string mQueryString; int mHTTPVersion; Query_t mQuery; int mContentLength; std::string mContentType; CookieJar_t *mpCookies; bool mClientKeepAliveRequested; std::vector
mExtraHeaders; bool mExpectContinue; IOStream* mpStreamToReadFrom; std::string mHttpVerb; std::string ToLowerCase(const std::string& rInput) const { std::string output = rInput; for (std::string::iterator c = output.begin(); c != output.end(); c++) { *c = tolower(*c); } return output; } }; #endif // HTTPREQUEST__H boxbackup/lib/httpserver/S3Simulator.cpp0000664000175000017500000002103711173735542021172 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: S3Client.cpp // Purpose: Amazon S3 client helper implementation class // Created: 09/01/2009 // // -------------------------------------------------------------------------- #include "Box.h" #include #include // #include // #include #include #include "HTTPRequest.h" #include "HTTPResponse.h" #include "autogen_HTTPException.h" #include "IOStream.h" #include "Logging.h" #include "S3Simulator.h" #include "decode.h" #include "encode.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: HTTPServer::GetConfigVerify() // Purpose: Returns additional configuration options for the // S3 simulator. Currently the access key, secret key // and store directory can be configured. // Created: 09/01/09 // // -------------------------------------------------------------------------- const ConfigurationVerify* S3Simulator::GetConfigVerify() const { static ConfigurationVerifyKey verifyserverkeys[] = { HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses }; static ConfigurationVerify verifyserver[] = { { "Server", 0, verifyserverkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; static ConfigurationVerifyKey verifyrootkeys[] = { ConfigurationVerifyKey("AccessKey", ConfigTest_Exists), ConfigurationVerifyKey("SecretKey", ConfigTest_Exists), ConfigurationVerifyKey("StoreDirectory", ConfigTest_Exists), HTTPSERVER_VERIFY_ROOT_KEYS }; static ConfigurationVerify verify = { "root", verifyserver, verifyrootkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; return &verify; } // -------------------------------------------------------------------------- // // Function // Name: S3Simulator::Handle(HTTPRequest &rRequest, // HTTPResponse &rResponse) // Purpose: Handles any incoming S3 request, by checking // authorization and then dispatching to one of the // private Handle* methods. // Created: 09/01/09 // // -------------------------------------------------------------------------- void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) { // if anything goes wrong, return a 500 error rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); rResponse.SetContentType("text/plain"); try { const Configuration& rConfig(GetConfiguration()); std::string access_key = rConfig.GetKeyValue("AccessKey"); std::string secret_key = rConfig.GetKeyValue("SecretKey"); std::string md5, date, bucket; rRequest.GetHeader("content-md5", &md5); rRequest.GetHeader("date", &date); std::string host = rRequest.GetHostName(); std::string s3suffix = ".s3.amazonaws.com"; if (host.size() > s3suffix.size()) { std::string suffix = host.substr(host.size() - s3suffix.size(), s3suffix.size()); if (suffix == s3suffix) { bucket = host.substr(0, host.size() - s3suffix.size()); } } std::ostringstream data; data << rRequest.GetVerb() << "\n"; data << md5 << "\n"; data << rRequest.GetContentType() << "\n"; data << date << "\n"; // header names are already in lower case, i.e. canonical form std::vector headers = rRequest.GetHeaders(); std::sort(headers.begin(), headers.end()); for (std::vector::iterator i = headers.begin(); i != headers.end(); i++) { if (i->first.substr(0, 5) == "x-amz") { data << i->first << ":" << i->second << "\n"; } } if (! bucket.empty()) { data << "/" << bucket; } data << rRequest.GetRequestURI(); std::string data_string = data.str(); unsigned char digest_buffer[EVP_MAX_MD_SIZE]; unsigned int digest_size = sizeof(digest_buffer); /* unsigned char* mac = */ HMAC(EVP_sha1(), secret_key.c_str(), secret_key.size(), (const unsigned char*)data_string.c_str(), data_string.size(), digest_buffer, &digest_size); std::string digest((const char *)digest_buffer, digest_size); base64::encoder encoder; std::string expectedAuth = "AWS " + access_key + ":" + encoder.encode(digest); if (expectedAuth[expectedAuth.size() - 1] == '\n') { expectedAuth = expectedAuth.substr(0, expectedAuth.size() - 1); } std::string actualAuth; if (!rRequest.GetHeader("authorization", &actualAuth) || actualAuth != expectedAuth) { rResponse.SetResponseCode(HTTPResponse::Code_Unauthorized); SendInternalErrorResponse("Authentication Failed", rResponse); } else if (rRequest.GetMethod() == HTTPRequest::Method_GET) { HandleGet(rRequest, rResponse); } else if (rRequest.GetMethod() == HTTPRequest::Method_PUT) { HandlePut(rRequest, rResponse); } else { rResponse.SetResponseCode(HTTPResponse::Code_MethodNotAllowed); SendInternalErrorResponse("Unsupported Method", rResponse); } } catch (CommonException &ce) { SendInternalErrorResponse(ce.what(), rResponse); } catch (std::exception &e) { SendInternalErrorResponse(e.what(), rResponse); } catch (...) { SendInternalErrorResponse("Unknown exception", rResponse); } if (rResponse.GetResponseCode() != 200 && rResponse.GetSize() == 0) { // no error message written, provide a default std::ostringstream s; s << rResponse.GetResponseCode(); SendInternalErrorResponse(s.str().c_str(), rResponse); } return; } // -------------------------------------------------------------------------- // // Function // Name: S3Simulator::HandleGet(HTTPRequest &rRequest, // HTTPResponse &rResponse) // Purpose: Handles an S3 GET request, i.e. downloading an // existing object. // Created: 09/01/09 // // -------------------------------------------------------------------------- void S3Simulator::HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse) { std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); path += rRequest.GetRequestURI(); std::auto_ptr apFile; try { apFile.reset(new FileStream(path)); } catch (CommonException &ce) { if (ce.GetSubType() == CommonException::OSFileOpenError) { rResponse.SetResponseCode(HTTPResponse::Code_NotFound); } else if (ce.GetSubType() == CommonException::AccessDenied) { rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); } throw; } // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingRESTOperations.html apFile->CopyStreamTo(rResponse); rResponse.AddHeader("x-amz-id-2", "qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY"); rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); rResponse.AddHeader("Server", "AmazonS3"); rResponse.SetResponseCode(HTTPResponse::Code_OK); } // -------------------------------------------------------------------------- // // Function // Name: S3Simulator::HandlePut(HTTPRequest &rRequest, // HTTPResponse &rResponse) // Purpose: Handles an S3 PUT request, i.e. uploading data to // create or replace an object. // Created: 09/01/09 // // -------------------------------------------------------------------------- void S3Simulator::HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse) { std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); path += rRequest.GetRequestURI(); std::auto_ptr apFile; try { apFile.reset(new FileStream(path, O_CREAT | O_WRONLY)); } catch (CommonException &ce) { if (ce.GetSubType() == CommonException::OSFileOpenError) { rResponse.SetResponseCode(HTTPResponse::Code_NotFound); } else if (ce.GetSubType() == CommonException::AccessDenied) { rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); } throw; } if (rRequest.IsExpectingContinue()) { rResponse.SendContinue(); } rRequest.ReadContent(*apFile); // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectPUT.html rResponse.AddHeader("x-amz-id-2", "LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7"); rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); rResponse.SetContentType(""); rResponse.AddHeader("Server", "AmazonS3"); rResponse.SetResponseCode(HTTPResponse::Code_OK); } boxbackup/lib/httpserver/cencode.h0000664000175000017500000000132411130655017020036 0ustar siretartsiretart/* cencode.h - c header for a base64 encoding algorithm This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 */ #ifndef BASE64_CENCODE_H #define BASE64_CENCODE_H typedef enum { step_A, step_B, step_C } base64_encodestep; typedef struct { base64_encodestep step; char result; int stepcount; } base64_encodestate; void base64_init_encodestate(base64_encodestate* state_in); char base64_encode_value(char value_in); int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); int base64_encode_blockend(char* code_out, base64_encodestate* state_in); #endif /* BASE64_CENCODE_H */ boxbackup/lib/httpserver/cdecode.cpp0000664000175000017500000000472211130655017020364 0ustar siretartsiretart/* cdecoder.c - c source to a base64 decoding algorithm implementation This is part of the libb64 project, and has been placed in the public domain. For details, see http://sourceforge.net/projects/libb64 */ extern "C" { #include "cdecode.h" int base64_decode_value(char value_in) { static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; static const char decoding_size = sizeof(decoding); value_in -= 43; if (value_in < 0 || value_in > decoding_size) return -1; return decoding[(int)value_in]; } void base64_init_decodestate(base64_decodestate* state_in) { state_in->step = step_a; state_in->plainchar = 0; } int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) { const char* codechar = code_in; char* plainchar = plaintext_out; char fragment; *plainchar = state_in->plainchar; switch (state_in->step) { while (1) { case step_a: do { if (codechar == code_in+length_in) { state_in->step = step_a; state_in->plainchar = *plainchar; return plainchar - plaintext_out; } fragment = (char)base64_decode_value(*codechar++); } while (fragment < 0); *plainchar = (fragment & 0x03f) << 2; case step_b: do { if (codechar == code_in+length_in) { state_in->step = step_b; state_in->plainchar = *plainchar; return plainchar - plaintext_out; } fragment = (char)base64_decode_value(*codechar++); } while (fragment < 0); *plainchar++ |= (fragment & 0x030) >> 4; *plainchar = (fragment & 0x00f) << 4; case step_c: do { if (codechar == code_in+length_in) { state_in->step = step_c; state_in->plainchar = *plainchar; return plainchar - plaintext_out; } fragment = (char)base64_decode_value(*codechar++); } while (fragment < 0); *plainchar++ |= (fragment & 0x03c) >> 2; *plainchar = (fragment & 0x003) << 6; case step_d: do { if (codechar == code_in+length_in) { state_in->step = step_d; state_in->plainchar = *plainchar; return plainchar - plaintext_out; } fragment = (char)base64_decode_value(*codechar++); } while (fragment < 0); *plainchar++ |= (fragment & 0x03f); } } /* control should not reach here */ return plainchar - plaintext_out; } } boxbackup/lib/httpserver/HTTPException.txt0000664000175000017500000000061511130254233021461 0ustar siretartsiretartEXCEPTION HTTP 10 Internal 0 RequestReadFailed 1 RequestAlreadyBeenRead 2 BadRequest 3 UnknownResponseCodeUsed 4 NoContentTypeSet 5 POSTContentTooLong 6 CannotSetRedirectIfReponseHasData 7 CannotSetNotFoundIfReponseHasData 8 NotImplemented 9 RequestNotInitialised 10 BadResponse 11 ResponseReadFailed 12 NoStreamConfigured 13 boxbackup/lib/compress/0000775000175000017500000000000011652362374015724 5ustar siretartsiretartboxbackup/lib/compress/CompressStream.cpp0000664000175000017500000002403111126433077021372 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CompressStream.h // Purpose: Compressing stream // Created: 27/5/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "CompressStream.h" #include "Compress.h" #include "autogen_CompressException.h" #include "MemLeakFindOn.h" // How big a buffer to use #ifndef BOX_RELEASE_BUILD // debug! #define BUFFER_SIZE 256 #else #define BUFFER_SIZE (32*1024) #endif #define USE_READ_COMPRESSOR \ CheckRead(); \ Compress *pDecompress = (Compress *)mpReadCompressor; #define USE_WRITE_COMPRESSOR \ CheckWrite(); \ Compress *pCompress = (Compress *)mpWriteCompressor; // -------------------------------------------------------------------------- // // Function // Name: CompressStream::CompressStream(IOStream *, bool, bool, bool, bool) // Purpose: Constructor // Created: 27/5/04 // // -------------------------------------------------------------------------- CompressStream::CompressStream(IOStream *pStream, bool TakeOwnership, bool DecompressRead, bool CompressWrite, bool PassThroughWhenNotCompressed) : mpStream(pStream), mHaveOwnership(TakeOwnership), mDecompressRead(DecompressRead), mCompressWrite(CompressWrite), mPassThroughWhenNotCompressed(PassThroughWhenNotCompressed), mpReadCompressor(0), mpWriteCompressor(0), mpBuffer(0), mIsClosed(false) { if(mpStream == 0) { THROW_EXCEPTION(CompressException, NullPointerPassedToCompressStream) } } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::~CompressStream() // Purpose: Destructor // Created: 27/5/04 // // -------------------------------------------------------------------------- CompressStream::~CompressStream() { // Clean up compressors if(mpReadCompressor) { delete ((Compress*)mpReadCompressor); mpReadCompressor = 0; } if(mpWriteCompressor) { delete ((Compress*)mpWriteCompressor); mpWriteCompressor = 0; } // Delete the stream, if we have ownership if(mHaveOwnership) { delete mpStream; mpStream = 0; } // Free any buffer if(mpBuffer != 0) { ::free(mpBuffer); mpBuffer = 0; } } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::CompressStream(const CompressStream &) // Purpose: Copy constructor, will exception // Created: 27/5/04 // // -------------------------------------------------------------------------- CompressStream::CompressStream(const CompressStream &) { THROW_EXCEPTION(CompressException, CopyCompressStreamNotAllowed) } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::operator=(const CompressStream &) // Purpose: Assignment operator, will exception // Created: 27/5/04 // // -------------------------------------------------------------------------- CompressStream &CompressStream::operator=(const CompressStream &) { THROW_EXCEPTION(CompressException, CopyCompressStreamNotAllowed) } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::Read(void *, int, int) // Purpose: As interface // Created: 27/5/04 // // -------------------------------------------------------------------------- int CompressStream::Read(void *pBuffer, int NBytes, int Timeout) { USE_READ_COMPRESSOR if(pDecompress == 0) { return mpStream->Read(pBuffer, NBytes, Timeout); } // Where is the buffer? (note if writing as well, read buffer is second in a block of two buffer sizes) void *pbuf = (mDecompressRead && mCompressWrite)?(((uint8_t*)mpBuffer) + BUFFER_SIZE):mpBuffer; // Any data left to go? if(!pDecompress->InputRequired()) { // Output some data from the existing data read return pDecompress->Output(pBuffer, NBytes, true /* write as much as possible */); } // Read data into the buffer -- read as much as possible in one go int s = mpStream->Read(pbuf, BUFFER_SIZE, Timeout); if(s == 0) { return 0; } // Give input to the compressor pDecompress->Input(pbuf, s); // Output as much as possible return pDecompress->Output(pBuffer, NBytes, true /* write as much as possible */); } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::Write(const void *, int) // Purpose: As interface // Created: 27/5/04 // // -------------------------------------------------------------------------- void CompressStream::Write(const void *pBuffer, int NBytes) { USE_WRITE_COMPRESSOR if(pCompress == 0) { mpStream->Write(pBuffer, NBytes); return; } if(mIsClosed) { THROW_EXCEPTION(CompressException, CannotWriteToClosedCompressStream) } // Give the data to the compressor pCompress->Input(pBuffer, NBytes); // Write the data to the stream WriteCompressedData(); } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::WriteAllBuffered() // Purpose: As interface // Created: 27/5/04 // // -------------------------------------------------------------------------- void CompressStream::WriteAllBuffered() { if(mIsClosed) { THROW_EXCEPTION(CompressException, CannotWriteToClosedCompressStream) } // Just ask compressed data to be written out, but with the sync flag set WriteCompressedData(true); } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::Close() // Purpose: As interface // Created: 27/5/04 // // -------------------------------------------------------------------------- void CompressStream::Close() { if(mCompressWrite) { USE_WRITE_COMPRESSOR if(pCompress != 0) { // Flush anything from the write buffer pCompress->FinishInput(); WriteCompressedData(); // Mark as definately closed mIsClosed = true; } } // Close mpStream->Close(); } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::WriteCompressedData(bool) // Purpose: Private. Writes the output of the compressor to the stream, // optionally doing a sync flush. // Created: 28/5/04 // // -------------------------------------------------------------------------- void CompressStream::WriteCompressedData(bool SyncFlush) { USE_WRITE_COMPRESSOR if(pCompress == 0) {THROW_EXCEPTION(CompressException, Internal)} int s = 0; do { s = pCompress->Output(mpBuffer, BUFFER_SIZE, SyncFlush); if(s > 0) { mpStream->Write(mpBuffer, s); } } while(s > 0); // Check assumption -- all input has been consumed if(!pCompress->InputRequired()) {THROW_EXCEPTION(CompressException, Internal)} } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::StreamDataLeft() // Purpose: As interface // Created: 27/5/04 // // -------------------------------------------------------------------------- bool CompressStream::StreamDataLeft() { USE_READ_COMPRESSOR if(pDecompress == 0) { return mpStream->StreamDataLeft(); } // Any bytes left in our buffer? if(!pDecompress->InputRequired()) { // Still buffered data to decompress return true; } // Otherwise ask the stream return mpStream->StreamDataLeft(); } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::StreamClosed() // Purpose: As interface // Created: 27/5/04 // // -------------------------------------------------------------------------- bool CompressStream::StreamClosed() { if(!mIsClosed && mpStream->StreamClosed()) { mIsClosed = true; } return mIsClosed; } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::CheckRead() // Purpose: Checks that everything is set up to read // Created: 27/5/04 // // -------------------------------------------------------------------------- void CompressStream::CheckRead() { // Has the read compressor already been created? if(mpReadCompressor != 0) { return; } // Need to create one? if(mDecompressRead) { mpReadCompressor = new Compress(); // And make sure there's a buffer CheckBuffer(); } else { // Not decompressing. Should be passing through data? if(!mPassThroughWhenNotCompressed) { THROW_EXCEPTION(CompressException, CompressStreamReadSupportNotRequested) } } } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::CheckWrite() // Purpose: Checks that everything is set up to write // Created: 27/5/04 // // -------------------------------------------------------------------------- void CompressStream::CheckWrite() { // Has the read compressor already been created? if(mpWriteCompressor != 0) { return; } // Need to create one? if(mCompressWrite) { mpWriteCompressor = new Compress(); // And make sure there's a buffer CheckBuffer(); } else { // Not decompressing. Should be passing through data? if(!mPassThroughWhenNotCompressed) { THROW_EXCEPTION(CompressException, CompressStreamWriteSupportNotRequested) } } } // -------------------------------------------------------------------------- // // Function // Name: CompressStream::CheckBuffer() // Purpose: Allocates the buffer for (de)compression operations // Created: 28/5/04 // // -------------------------------------------------------------------------- void CompressStream::CheckBuffer() { // Already done if(mpBuffer != 0) { return; } // Allocate the buffer -- which may actually be two buffers in one // The temporary use buffer is first (used by write only, so only present if writing) // and the read buffer follows. int size = BUFFER_SIZE; if(mDecompressRead && mCompressWrite) { size *= 2; } BOX_TRACE("Allocating CompressStream buffer, size " << size); mpBuffer = ::malloc(size); if(mpBuffer == 0) { throw std::bad_alloc(); } } boxbackup/lib/compress/CompressStream.h0000664000175000017500000000314110347400657021040 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CompressStream.h // Purpose: Compressing stream // Created: 27/5/04 // // -------------------------------------------------------------------------- #ifndef COMPRESSSTREAM__H #define COMPRESSSTREAM__H #include "IOStream.h" // -------------------------------------------------------------------------- // // Class // Name: CompressStream // Purpose: Compressing stream // Created: 27/5/04 // // -------------------------------------------------------------------------- class CompressStream : public IOStream { public: CompressStream(IOStream *pStream, bool TakeOwnership, bool DecompressRead, bool CompressWrite, bool PassThroughWhenNotCompressed = false); ~CompressStream(); private: // No copying (have implementations which exception) CompressStream(const CompressStream &); CompressStream &operator=(const CompressStream &); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual void Write(const void *pBuffer, int NBytes); virtual void WriteAllBuffered(); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); protected: void CheckRead(); void CheckWrite(); void CheckBuffer(); void WriteCompressedData(bool SyncFlush = false); private: IOStream *mpStream; bool mHaveOwnership; bool mDecompressRead; bool mCompressWrite; bool mPassThroughWhenNotCompressed; // Avoid having to include Compress.h void *mpReadCompressor; void *mpWriteCompressor; void *mpBuffer; bool mIsClosed; }; #endif // COMPRESSSTREAM__H boxbackup/lib/compress/CompressException.txt0000664000175000017500000000057010347400657022136 0ustar siretartsiretartEXCEPTION Compress 6 Internal 0 InitFailed 1 EndFailed 2 BadUsageInputNotRequired 3 TransformFailed 4 CopyCompressStreamNotAllowed 5 NullPointerPassedToCompressStream 6 CompressStreamReadSupportNotRequested 7 Specify read in the constructor CompressStreamWriteSupportNotRequested 8 Specify write in the constructor CannotWriteToClosedCompressStream 9 boxbackup/lib/compress/Compress.h0000664000175000017500000001121010775523641017664 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Compress.h // Purpose: Interface to zlib compression // Created: 5/12/03 // // -------------------------------------------------------------------------- #ifndef COMPRESSCONTEXT__H #define COMPRESSCONTEXT__H #include #include "CompressException.h" // -------------------------------------------------------------------------- // // Class // Name: Compress // Purpose: Interface to zlib compression, only very slight wrapper. // (Use CompressStream for a more friendly interface.) // Created: 5/12/03 // // -------------------------------------------------------------------------- template class Compress { public: Compress() : mFinished(false), mFlush(Z_NO_FLUSH) { // initialise stream mStream.zalloc = Z_NULL; mStream.zfree = Z_NULL; mStream.opaque = Z_NULL; mStream.data_type = Z_BINARY; if((Compressing)?(deflateInit(&mStream, Z_DEFAULT_COMPRESSION)) :(inflateInit(&mStream)) != Z_OK) { THROW_EXCEPTION(CompressException, InitFailed) } mStream.avail_in = 0; } ~Compress() { int r = 0; if((r = ((Compressing)?(deflateEnd(&mStream)) :(inflateEnd(&mStream)))) != Z_OK) { BOX_WARNING("zlib error code = " << r); if(r == Z_DATA_ERROR) { BOX_WARNING("End of compress/decompress " "without all input being consumed, " "possible corruption?"); } else { THROW_EXCEPTION(CompressException, EndFailed) } } } // -------------------------------------------------------------------------- // // Function // Name: Compress::InputRequired() // Purpose: Input required yet? // Created: 5/12/03 // // -------------------------------------------------------------------------- bool InputRequired() { return mStream.avail_in <= 0; } // -------------------------------------------------------------------------- // // Function // Name: Compress::Input(const void *, int) // Purpose: Set the input buffer ready for next output call. // Created: 5/12/03 // // -------------------------------------------------------------------------- void Input(const void *pInBuffer, int InLength) { // Check usage if(mStream.avail_in != 0) { THROW_EXCEPTION(CompressException, BadUsageInputNotRequired) } // Store info mStream.next_in = (unsigned char *)pInBuffer; mStream.avail_in = InLength; } // -------------------------------------------------------------------------- // // Function // Name: Compress::FinishInput() // Purpose: When compressing, no more input will be given. // Created: 5/12/03 // // -------------------------------------------------------------------------- void FinishInput() { mFlush = Z_FINISH; } // -------------------------------------------------------------------------- // // Function // Name: Compress::Output(void *, int) // Purpose: Get some output data // Created: 5/12/03 // // -------------------------------------------------------------------------- int Output(void *pOutBuffer, int OutLength, bool SyncFlush = false) { // need more input? if(mStream.avail_in == 0 && mFlush != Z_FINISH && !SyncFlush) { return 0; } // Buffers mStream.next_out = (unsigned char *)pOutBuffer; mStream.avail_out = OutLength; // Call one of the functions int flush = mFlush; if(SyncFlush && mFlush != Z_FINISH) { flush = Z_SYNC_FLUSH; } int ret = (Compressing)?(deflate(&mStream, flush)):(inflate(&mStream, flush)); if(SyncFlush && ret == Z_BUF_ERROR) { // No progress possible. Just return 0. return 0; } // Check errors if(ret < 0) { BOX_WARNING("zlib error code = " << ret); THROW_EXCEPTION(CompressException, TransformFailed) } // Parse result if(ret == Z_STREAM_END) { mFinished = true; } // Return how much data was output return OutLength - mStream.avail_out; } // -------------------------------------------------------------------------- // // Function // Name: Compress::OutputHasFinished() // Purpose: No more output to be recieved // Created: 5/12/03 // // -------------------------------------------------------------------------- bool OutputHasFinished() { return mFinished; } private: z_stream mStream; bool mFinished; int mFlush; }; template Integer Compress_MaxSizeForCompressedData(Integer InLen) { // Conservative rendition of the info found here: http://www.gzip.org/zlib/zlib_tech.html int blocks = (InLen + 32*1024 - 1) / (32*1024); return InLen + (blocks * 6) + 8; } #endif // COMPRESSCONTEXT__H boxbackup/lib/compress/CompressException.h0000664000175000017500000000061510347400657021546 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherException.h // Purpose: Exception // Created: 2003/07/08 // // -------------------------------------------------------------------------- #ifndef COMPRESSEXCEPTION__H #define COMPRESSEXCEPTION__H // Compatibility #include "autogen_CompressException.h" #endif // COMPRESSEXCEPTION__H boxbackup/lib/compress/Makefile.extra0000664000175000017500000000033211345266370020502 0ustar siretartsiretart MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_CompressException.h autogen_CompressException.cpp: $(MAKEEXCEPTION) CompressException.txt $(_PERL) $(MAKEEXCEPTION) CompressException.txt boxbackup/lib/intercept/0000775000175000017500000000000011652362374016066 5ustar siretartsiretartboxbackup/lib/intercept/intercept.cpp0000664000175000017500000003330611074227107020564 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: intercept.cpp // Purpose: Syscall interception code for the raidfile test // Created: 2003/07/22 // // -------------------------------------------------------------------------- #include "Box.h" #include "intercept.h" #ifdef HAVE_SYS_SYSCALL_H #include #endif #include #include #ifdef HAVE_SYS_UIO_H #include #endif #include #include #ifdef HAVE_DLFCN_H #include #endif #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE #if !defined(HAVE_SYSCALL) && !defined(HAVE___SYSCALL) && !defined(HAVE___SYSCALL_NEED_DEFN) #define PLATFORM_NO_SYSCALL #endif #ifdef PLATFORM_NO_SYSCALL // For some reason, syscall just doesn't work on Darwin // so instead, we build functions using assembler in a varient // of the technique used in the Darwin Libc extern "C" int TEST_open(const char *path, int flags, mode_t mode); extern "C" int TEST_close(int d); extern "C" ssize_t TEST_write(int d, const void *buf, size_t nbytes); extern "C" ssize_t TEST_read(int d, void *buf, size_t nbytes); extern "C" ssize_t TEST_readv(int d, const struct iovec *iov, int iovcnt); extern "C" off_t TEST_lseek(int fildes, off_t offset, int whence); #else // if we have __syscall, we should use it for everything // (on FreeBSD 7 this is required for 64-bit alignment of off_t). // if not, we should continue to use the old syscall(). #ifdef HAVE___SYSCALL_NEED_DEFN // Need this, not declared in syscall.h nor unistd.h extern "C" off_t __syscall(quad_t number, ...); #endif #ifdef HAVE___SYSCALL #undef syscall #define syscall __syscall #endif #endif #include #include #include "MemLeakFindOn.h" int intercept_count = 0; const char *intercept_filename = 0; int intercept_filedes = -1; off_t intercept_errorafter = 0; int intercept_errno = 0; int intercept_syscall = 0; off_t intercept_filepos = 0; int intercept_delay_ms = 0; static opendir_t* opendir_real = NULL; static readdir_t* readdir_real = NULL; static readdir_t* readdir_hook = NULL; static closedir_t* closedir_real = NULL; static lstat_t* lstat_real = NULL; static lstat_t* lstat_hook = NULL; static const char* lstat_file = NULL; static lstat_t* stat_real = NULL; static lstat_t* stat_hook = NULL; static const char* stat_file = NULL; static lstat_post_hook_t* lstat_post_hook = NULL; static lstat_post_hook_t* stat_post_hook = NULL; #define SIZE_ALWAYS_ERROR -773 void intercept_clear_setup() { intercept_count = 0; intercept_filename = 0; intercept_filedes = -1; intercept_errorafter = 0; intercept_syscall = 0; intercept_filepos = 0; intercept_delay_ms = 0; readdir_hook = NULL; stat_hook = NULL; lstat_hook = NULL; stat_post_hook = NULL; lstat_post_hook = NULL; } bool intercept_triggered() { return intercept_count == 0; } void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror) { BOX_TRACE("Setup for error: " << filename << ", after " << errorafter << ", err " << errortoreturn << ", syscall " << syscalltoerror); intercept_count = 1; intercept_filename = filename; intercept_filedes = -1; intercept_errorafter = errorafter; intercept_syscall = syscalltoerror; intercept_errno = errortoreturn; intercept_filepos = 0; intercept_delay_ms = 0; } void intercept_setup_delay(const char *filename, unsigned int delay_after, int delay_ms, int syscall_to_delay, int num_delays) { BOX_TRACE("Setup for delay: " << filename << ", after " << delay_after << ", wait " << delay_ms << " ms" << ", times " << num_delays << ", syscall " << syscall_to_delay); intercept_count = num_delays; intercept_filename = filename; intercept_filedes = -1; intercept_errorafter = delay_after; intercept_syscall = syscall_to_delay; intercept_errno = 0; intercept_filepos = 0; intercept_delay_ms = delay_ms; } bool intercept_errornow(int d, int size, int syscallnum) { ASSERT(intercept_count > 0) if (intercept_filedes == -1) { return false; // no error please! } if (d != intercept_filedes) { return false; // no error please! } if (syscallnum != intercept_syscall) { return false; // no error please! } bool ret = false; // no error unless one of the conditions matches //printf("Checking for err, %d, %d, %d\n", d, size, syscallnum); if (intercept_delay_ms != 0) { BOX_TRACE("Delaying " << intercept_delay_ms << " ms " << " for syscall " << syscallnum << " at " << intercept_filepos); struct timespec tm; tm.tv_sec = intercept_delay_ms / 1000; tm.tv_nsec = (intercept_delay_ms % 1000) * 1000000; while (nanosleep(&tm, &tm) != 0 && errno == EINTR) { } } if (size == SIZE_ALWAYS_ERROR) { // Looks good for an error! BOX_TRACE("Returning error " << intercept_errno << " for syscall " << syscallnum); ret = true; } else if (intercept_filepos + size < intercept_errorafter) { return false; // no error please } else if (intercept_errno != 0) { BOX_TRACE("Returning error " << intercept_errno << " for syscall " << syscallnum << " at " << intercept_filepos); ret = true; } intercept_count--; if (intercept_count == 0) { intercept_clear_setup(); } return ret; } int intercept_reterr() { int err = intercept_errno; intercept_clear_setup(); return err; } #define CHECK_FOR_FAKE_ERROR_COND(D, S, CALL, FAILRES) \ if(intercept_count > 0) \ { \ if(intercept_errornow(D, S, CALL)) \ { \ errno = intercept_reterr(); \ return FAILRES; \ } \ } #if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64 #define DEFINE_ONLY_OPEN64 #endif extern "C" int #ifdef DEFINE_ONLY_OPEN64 open64(const char *path, int flags, ...) #else open(const char *path, int flags, ...) #endif // DEFINE_ONLY_OPEN64 { if(intercept_count > 0) { if(intercept_filename != NULL && intercept_syscall == SYS_open && strcmp(path, intercept_filename) == 0) { errno = intercept_reterr(); return -1; } } mode_t mode = 0; if (flags & O_CREAT) { va_list ap; va_start(ap, flags); mode = va_arg(ap, int); va_end(ap); } #ifdef PLATFORM_NO_SYSCALL int r = TEST_open(path, flags, mode); #else int r = syscall(SYS_open, path, flags, mode); #endif if(intercept_filename != NULL && intercept_count > 0 && intercept_filedes == -1) { // Right file? if(strcmp(intercept_filename, path) == 0) { intercept_filedes = r; //printf("Found file to intercept, h = %d\n", r); } } return r; } #ifndef DEFINE_ONLY_OPEN64 extern "C" int // open64(const char *path, int flags, mode_t mode) // open64(const char *path, int flags, ...) open64 (__const char *path, int flags, ...) { mode_t mode = 0; if (flags & O_CREAT) { va_list ap; va_start(ap, flags); mode = va_arg(ap, int); va_end(ap); } // With _FILE_OFFSET_BITS set to 64 this should really use (flags | // O_LARGEFILE) here, but not actually necessary for the tests and not // worth the trouble finding O_LARGEFILE return open(path, flags, mode); } #endif // !DEFINE_ONLY_OPEN64 extern "C" int close(int d) { CHECK_FOR_FAKE_ERROR_COND(d, SIZE_ALWAYS_ERROR, SYS_close, -1); #ifdef PLATFORM_NO_SYSCALL int r = TEST_close(d); #else int r = syscall(SYS_close, d); #endif if(r == 0) { if(d == intercept_filedes) { intercept_filedes = -1; } } return r; } extern "C" ssize_t write(int d, const void *buf, size_t nbytes) { CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_write, -1); #ifdef PLATFORM_NO_SYSCALL int r = TEST_write(d, buf, nbytes); #else int r = syscall(SYS_write, d, buf, nbytes); #endif if(r != -1) { intercept_filepos += r; } return r; } extern "C" ssize_t read(int d, void *buf, size_t nbytes) { CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_read, -1); #ifdef PLATFORM_NO_SYSCALL int r = TEST_read(d, buf, nbytes); #else int r = syscall(SYS_read, d, buf, nbytes); #endif if(r != -1) { intercept_filepos += r; } return r; } extern "C" ssize_t readv(int d, const struct iovec *iov, int iovcnt) { // how many bytes? int nbytes = 0; for(int b = 0; b < iovcnt; ++b) { nbytes += iov[b].iov_len; } CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_readv, -1); #ifdef PLATFORM_NO_SYSCALL int r = TEST_readv(d, iov, iovcnt); #else int r = syscall(SYS_readv, d, iov, iovcnt); #endif if(r != -1) { intercept_filepos += r; } return r; } extern "C" off_t lseek(int fildes, off_t offset, int whence) { // random magic for lseek syscall, see /usr/src/lib/libc/sys/lseek.c CHECK_FOR_FAKE_ERROR_COND(fildes, 0, SYS_lseek, -1); #ifdef PLATFORM_NO_SYSCALL int r = TEST_lseek(fildes, offset, whence); #else #ifdef HAVE_LSEEK_DUMMY_PARAM off_t r = syscall(SYS_lseek, fildes, 0 /* extra 0 required here! */, offset, whence); #elif defined(_FILE_OFFSET_BITS) // Don't bother trying to call SYS__llseek on 32 bit since it is // fiddly and not needed for the tests off_t r = syscall(SYS_lseek, fildes, (uint32_t)offset, whence); #else off_t r = syscall(SYS_lseek, fildes, offset, whence); #endif #endif if(r != -1) { intercept_filepos = r; } return r; } void intercept_setup_readdir_hook(const char *dirname, readdir_t hookfn) { if (hookfn != NULL && dirname == NULL) { dirname = intercept_filename; ASSERT(dirname != NULL); } if (hookfn != NULL) { BOX_TRACE("readdir hooked to " << hookfn << " for " << dirname); } else if (intercept_filename != NULL) { BOX_TRACE("readdir unhooked from " << readdir_hook << " for " << intercept_filename); } intercept_filename = dirname; readdir_hook = hookfn; } void intercept_setup_lstat_hook(const char *filename, lstat_t hookfn) { /* if (hookfn != NULL) { BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); } else { BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << lstat_file); } */ lstat_file = filename; lstat_hook = hookfn; } void intercept_setup_lstat_post_hook(lstat_post_hook_t hookfn) { /* if (hookfn != NULL) { BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); } else { BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << lstat_file); } */ lstat_post_hook = hookfn; } void intercept_setup_stat_post_hook(lstat_post_hook_t hookfn) { /* if (hookfn != NULL) { BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); } else { BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << lstat_file); } */ stat_post_hook = hookfn; } static void * find_function(const char *pName) { dlerror(); void *result = NULL; #ifdef HAVE_LARGE_FILE_SUPPORT { // search for the 64-bit version first std::string name64(pName); name64 += "64"; result = dlsym(RTLD_NEXT, name64.c_str()); if (dlerror() == NULL && result != NULL) { return result; } } #endif result = dlsym(RTLD_NEXT, pName); const char *errmsg = (const char *)dlerror(); if (errmsg == NULL) { return result; } BOX_ERROR("Failed to find real " << pName << " function: " << errmsg); return NULL; } extern "C" DIR *opendir(const char *dirname) { if (opendir_real == NULL) { opendir_real = (opendir_t*)find_function("opendir"); } if (opendir_real == NULL) { perror("cannot find real opendir"); return NULL; } DIR* r = opendir_real(dirname); if (readdir_hook != NULL && intercept_filename != NULL && intercept_filedes == -1 && strcmp(intercept_filename, dirname) == 0) { intercept_filedes = dirfd(r); //printf("Found file to intercept, h = %d\n", r); } return r; } extern "C" struct dirent *readdir(DIR *dir) { if (readdir_hook != NULL && dirfd(dir) == intercept_filedes) { return readdir_hook(dir); } if (readdir_real == NULL) { readdir_real = (readdir_t*)find_function("readdir"); } if (readdir_real == NULL) { perror("cannot find real readdir"); return NULL; } return readdir_real(dir); } extern "C" int closedir(DIR *dir) { if (dirfd(dir) == intercept_filedes) { intercept_filedes = -1; } if (closedir_real == NULL) { closedir_real = (closedir_t*)find_function("closedir"); } if (closedir_real == NULL) { perror("cannot find real closedir"); errno = ENOSYS; return -1; } return closedir_real(dir); } extern "C" int #ifdef LINUX_WEIRD_LSTAT __lxstat(int ver, const char *file_name, STAT_STRUCT *buf) #else lstat(const char *file_name, STAT_STRUCT *buf) #endif { if (lstat_real == NULL) { #ifdef LINUX_WEIRD_LSTAT lstat_real = (lstat_t*)find_function("__lxstat"); #else lstat_real = (lstat_t*)find_function("lstat"); #endif } if (lstat_real == NULL) { perror("cannot find real lstat"); errno = ENOSYS; return -1; } if (lstat_hook == NULL || strcmp(file_name, lstat_file) != 0) { #ifdef LINUX_WEIRD_LSTAT int ret = lstat_real(ver, file_name, buf); #else int ret = lstat_real(file_name, buf); #endif if (lstat_post_hook != NULL) { ret = lstat_post_hook(ret, file_name, buf); } return ret; } #ifdef LINUX_WEIRD_LSTAT return lstat_hook(ver, file_name, buf); #else return lstat_hook(file_name, buf); #endif } extern "C" int #ifdef LINUX_WEIRD_LSTAT __xstat(int ver, const char *file_name, STAT_STRUCT *buf) #else stat(const char *file_name, STAT_STRUCT *buf) #endif { if (stat_real == NULL) { #ifdef LINUX_WEIRD_LSTAT stat_real = (lstat_t*)find_function("__xstat"); #else stat_real = (lstat_t*)find_function("stat"); #endif } if (stat_real == NULL) { perror("cannot find real stat"); errno = ENOSYS; return -1; } if (stat_hook == NULL || strcmp(file_name, stat_file) != 0) { #ifdef LINUX_WEIRD_LSTAT int ret = stat_real(ver, file_name, buf); #else int ret = stat_real(file_name, buf); #endif if (stat_post_hook != NULL) { ret = stat_post_hook(ret, file_name, buf); } return ret; } #ifdef LINUX_WEIRD_LSTAT return stat_hook(ver, file_name, buf); #else return stat_hook(file_name, buf); #endif } #endif // n PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE boxbackup/lib/intercept/intercept.h0000664000175000017500000000331211074227107020223 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: intercept.h // Purpose: Syscall interception code for unit tests // Created: 2006/11/29 // // -------------------------------------------------------------------------- #ifndef INTERCEPT_H #define INTERCEPT_H #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE #include #include #include extern "C" { typedef DIR *(opendir_t) (const char *name); typedef struct dirent *(readdir_t) (DIR *dir); typedef struct dirent *(readdir_t) (DIR *dir); typedef int (closedir_t)(DIR *dir); #if defined __GNUC__ && __GNUC__ >= 2 #define LINUX_WEIRD_LSTAT #define STAT_STRUCT struct stat /* should be stat64 */ typedef int (lstat_t) (int ver, const char *file_name, STAT_STRUCT *buf); #else #define STAT_STRUCT struct stat typedef int (lstat_t) (const char *file_name, STAT_STRUCT *buf); #endif } typedef int (lstat_post_hook_t) (int old_ret, const char *file_name, struct stat *buf); void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror); void intercept_setup_delay(const char *filename, unsigned int delay_after, int delay_ms, int syscall_to_delay, int num_delays); bool intercept_triggered(); void intercept_setup_readdir_hook(const char *dirname, readdir_t hookfn); void intercept_setup_lstat_hook (const char *filename, lstat_t hookfn); void intercept_setup_lstat_post_hook(lstat_post_hook_t hookfn); void intercept_setup_stat_post_hook (lstat_post_hook_t hookfn); void intercept_clear_setup(); #endif // !PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE #endif // !INTERCEPT_H boxbackup/lib/crypto/0000775000175000017500000000000011652362374015411 5ustar siretartsiretartboxbackup/lib/crypto/CipherException.txt0000664000175000017500000000071110347400657021237 0ustar siretartsiretartEXCEPTION Cipher 5 Internal 0 UnknownCipherMode 1 AlreadyInitialised 2 BadArguments 3 EVPInitFailure 4 EVPUpdateFailure 5 EVPFinalFailure 6 NotInitialised 7 OutputBufferTooSmall 8 EVPBadKeyLength 9 BeginNotCalled 10 IVSizeImplementationLimitExceeded 11 PseudoRandNotAvailable 12 EVPSetPaddingFailure 13 RandomInitFailed 14 Failed to read from random device LengthRequestedTooLongForRandomHex 15 boxbackup/lib/crypto/CipherException.h0000664000175000017500000000060510347400657020651 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherException.h // Purpose: Exception // Created: 2003/07/08 // // -------------------------------------------------------------------------- #ifndef CIPHEREXCEPTION__H #define CIPHEREXCEPTION__H // Compatibility #include "autogen_CipherException.h" #endif // CIPHEREXCEPTION__H boxbackup/lib/crypto/CipherAES.h0000664000175000017500000000243210347400657017323 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherAES.h // Purpose: AES cipher description // Created: 27/4/04 // // -------------------------------------------------------------------------- #ifndef CIPHERAES__H #define CIPHERAES__H // Only available in new versions of openssl #ifndef HAVE_OLD_SSL #include "CipherDescription.h" // -------------------------------------------------------------------------- // // Class // Name: CipherAES // Purpose: AES cipher description // Created: 27/4/04 // // -------------------------------------------------------------------------- class CipherAES : public CipherDescription { public: CipherAES(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector = 0); CipherAES(const CipherAES &rToCopy); virtual ~CipherAES(); CipherAES &operator=(const CipherAES &rToCopy); // Return OpenSSL cipher object virtual const EVP_CIPHER *GetCipher() const; // Setup any other parameters virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const; private: CipherDescription::CipherMode mMode; const void *mpKey; unsigned int mKeyLength; const void *mpInitialisationVector; }; #endif // n HAVE_OLD_SSL #endif // CIPHERAES__H boxbackup/lib/crypto/CipherDescription.h0000664000175000017500000000305610347400657021201 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherDescription.h // Purpose: Pure virtual base class for describing ciphers // Created: 1/12/03 // // -------------------------------------------------------------------------- #ifndef CIPHERDESCRIPTION__H #define CIPHERDESCRIPTION__H #ifndef BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_FALSE class EVP_CIPHER; class EVP_CIPHER_CTX; #endif // -------------------------------------------------------------------------- // // Class // Name: CipherDescription // Purpose: Describes a cipher // Created: 1/12/03 // // -------------------------------------------------------------------------- class CipherDescription { public: CipherDescription(); CipherDescription(const CipherDescription &rToCopy); virtual ~CipherDescription(); CipherDescription &operator=(const CipherDescription &rToCopy); // Return OpenSSL cipher object virtual const EVP_CIPHER *GetCipher() const = 0; // Setup any other parameters virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const = 0; // Mode parameter for cipher -- used in derived classes typedef enum { Mode_ECB = 0, Mode_CBC = 1, Mode_CFB = 2, Mode_OFB = 3 } CipherMode; #ifdef HAVE_OLD_SSL // For the old version of OpenSSL, we need to be able to store cipher descriptions. virtual CipherDescription *Clone() const = 0; // And to be able to store new IVs virtual void SetIV(const void *pIV) = 0; #endif }; #endif // CIPHERDESCRIPTION__H boxbackup/lib/crypto/Random.h0000664000175000017500000000074510347400657017005 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Random.h // Purpose: Random numbers // Created: 31/12/03 // // -------------------------------------------------------------------------- #ifndef RANDOM__H #define RANDOM__H #include namespace Random { void Initialise(); void Generate(void *pOutput, int Length); std::string GenerateHex(int Length); uint32_t RandomInt(uint32_t MaxValue); }; #endif // RANDOM__H boxbackup/lib/crypto/CipherAES.cpp0000664000175000017500000001012110347400657017650 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherAES.cpp // Purpose: AES cipher description // Created: 27/4/04 // // -------------------------------------------------------------------------- #include "Box.h" // Only available in new versions of openssl #ifndef HAVE_OLD_SSL #include #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE #include "CipherAES.h" #include "CipherException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: CipherAES::CipherAES(CipherDescription::CipherMode, const void *, unsigned int, const void *) // Purpose: Constructor -- note key material and IV are not copied. KeyLength in bytes. // Created: 27/4/04 // // -------------------------------------------------------------------------- CipherAES::CipherAES(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector) : CipherDescription(), mMode(Mode), mpKey(pKey), mKeyLength(KeyLength), mpInitialisationVector(pInitialisationVector) { } // -------------------------------------------------------------------------- // // Function // Name: CipherAES::CipherAES(const CipherAES &) // Purpose: Copy constructor // Created: 27/4/04 // // -------------------------------------------------------------------------- CipherAES::CipherAES(const CipherAES &rToCopy) : CipherDescription(rToCopy), mMode(rToCopy.mMode), mpKey(rToCopy.mpKey), mKeyLength(rToCopy.mKeyLength), mpInitialisationVector(rToCopy.mpInitialisationVector) { } // -------------------------------------------------------------------------- // // Function // Name: ~CipherAES::CipherAES() // Purpose: Destructor // Created: 27/4/04 // // -------------------------------------------------------------------------- CipherAES::~CipherAES() { } // -------------------------------------------------------------------------- // // Function // Name: CipherAES::operator=(const CipherAES &) // Purpose: Assignment operator // Created: 27/4/04 // // -------------------------------------------------------------------------- CipherAES &CipherAES::operator=(const CipherAES &rToCopy) { CipherDescription::operator=(rToCopy); mMode = rToCopy.mMode; mpKey = rToCopy.mpKey; mKeyLength = rToCopy.mKeyLength; mpInitialisationVector = rToCopy.mpInitialisationVector; return *this; } // -------------------------------------------------------------------------- // // Function // Name: CipherAES::GetCipher() // Purpose: Returns cipher object // Created: 27/4/04 // // -------------------------------------------------------------------------- const EVP_CIPHER *CipherAES::GetCipher() const { switch(mMode) { case CipherDescription::Mode_ECB: switch(mKeyLength) { case (128/8): return EVP_aes_128_ecb(); break; case (192/8): return EVP_aes_192_ecb(); break; case (256/8): return EVP_aes_256_ecb(); break; default: THROW_EXCEPTION(CipherException, EVPBadKeyLength) break; } break; case CipherDescription::Mode_CBC: switch(mKeyLength) { case (128/8): return EVP_aes_128_cbc(); break; case (192/8): return EVP_aes_192_cbc(); break; case (256/8): return EVP_aes_256_cbc(); break; default: THROW_EXCEPTION(CipherException, EVPBadKeyLength) break; } break; default: break; } // Unknown! THROW_EXCEPTION(CipherException, UnknownCipherMode) } // -------------------------------------------------------------------------- // // Function // Name: CipherAES::SetupParameters(EVP_CIPHER_CTX *) // Purpose: Set up various parameters for cipher // Created: 27/4/04 // // -------------------------------------------------------------------------- void CipherAES::SetupParameters(EVP_CIPHER_CTX *pCipherContext) const { ASSERT(pCipherContext != 0); // Set key (key length is implied) if(EVP_CipherInit_ex(pCipherContext, NULL, NULL, (unsigned char*)mpKey, (unsigned char*)mpInitialisationVector, -1) != 1) { THROW_EXCEPTION(CipherException, EVPInitFailure) } } #endif // n HAVE_OLD_SSL boxbackup/lib/crypto/CipherContext.cpp0000664000175000017500000004011410777227545020703 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherContext.cpp // Purpose: Context for symmetric encryption / descryption // Created: 1/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE #include "CipherContext.h" #include "CipherDescription.h" #include "CipherException.h" #include "Random.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: CipherContext::CipherContext() // Purpose: Constructor // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherContext::CipherContext() : mInitialised(false), mWithinTransform(false), mPaddingOn(true) #ifdef HAVE_OLD_SSL , mFunction(Decrypt), mpDescription(0) #endif { } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::~CipherContext() // Purpose: Destructor // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherContext::~CipherContext() { if(mInitialised) { // Clean up EVP_CIPHER_CTX_cleanup(&ctx); mInitialised = false; } #ifdef HAVE_OLD_SSL if(mpDescription != 0) { delete mpDescription; mpDescription = 0; } #endif } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::Init(CipherContext::CipherFunction, const CipherDescription &) // Purpose: Initialises the context, specifying the direction for the encryption, and a // description of the cipher to use, it's keys, etc // Created: 1/12/03 // // -------------------------------------------------------------------------- void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription) { // Check for bad usage if(mInitialised) { THROW_EXCEPTION(CipherException, AlreadyInitialised) } if(Function != Decrypt && Function != Encrypt) { THROW_EXCEPTION(CipherException, BadArguments) } // Initialise the cipher #ifndef HAVE_OLD_SSL EVP_CIPHER_CTX_init(&ctx); // no error return code, even though the docs says it does if(EVP_CipherInit_ex(&ctx, rDescription.GetCipher(), NULL, NULL, NULL, Function) != 1) #else // Store function for later mFunction = Function; // Use old version of init call if(EVP_CipherInit(&ctx, rDescription.GetCipher(), NULL, NULL, Function) != 1) #endif { THROW_EXCEPTION(CipherException, EVPInitFailure) } try { #ifndef HAVE_OLD_SSL // Let the description set up everything else rDescription.SetupParameters(&ctx); #else // With the old version, a copy needs to be taken first. mpDescription = rDescription.Clone(); // Mark it as not a leak, otherwise static cipher contexts // cause spurious memory leaks to be reported MEMLEAKFINDER_NOT_A_LEAK(mpDescription); mpDescription->SetupParameters(&ctx); #endif } catch(...) { EVP_CIPHER_CTX_cleanup(&ctx); throw; } // mark as initialised mInitialised = true; } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::Reset() // Purpose: Reset the context, so it can be initialised again with a different key // Created: 1/12/03 // // -------------------------------------------------------------------------- void CipherContext::Reset() { if(mInitialised) { // Clean up EVP_CIPHER_CTX_cleanup(&ctx); mInitialised = false; } #ifdef HAVE_OLD_SSL if(mpDescription != 0) { delete mpDescription; mpDescription = 0; } #endif mWithinTransform = false; } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::Begin() // Purpose: Begin a transformation // Created: 1/12/03 // // -------------------------------------------------------------------------- void CipherContext::Begin() { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } // Warn if in a transformation (not an error, because a context might not have been finalised if an exception occured) if(mWithinTransform) { BOX_WARNING("CipherContext::Begin called when context " "flagged as within a transform"); } // Initialise the cipher context again if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1) { THROW_EXCEPTION(CipherException, EVPInitFailure) } // Mark as being within a transform mWithinTransform = true; } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::Transform(void *, int, const void *, int) // Purpose: Transforms the data in the in buffer to the out buffer. If pInBuffer == 0 && InLength == 0 // then Final() is called instead. // Returns the number of bytes placed in the out buffer. // There must be room in the out buffer for all the data in the in buffer. // Created: 1/12/03 // // -------------------------------------------------------------------------- int CipherContext::Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength) { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } if(!mWithinTransform) { THROW_EXCEPTION(CipherException, BeginNotCalled) } // Check parameters if(pOutBuffer == 0 || OutLength < 0 || (pInBuffer != 0 && InLength <= 0) || (pInBuffer == 0 && InLength != 0)) { THROW_EXCEPTION(CipherException, BadArguments) } // Is this the final call? if(pInBuffer == 0) { return Final(pOutBuffer, OutLength); } // Check output buffer size if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx))) { THROW_EXCEPTION(CipherException, OutputBufferTooSmall); } // Do the transform int outLength = OutLength; if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1) { THROW_EXCEPTION(CipherException, EVPUpdateFailure) } return outLength; } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::Final(void *, int) // Purpose: Transforms the data as per Transform, and returns the final data in the out buffer. // Returns the number of bytes written in the out buffer. // Two main causes of exceptions being thrown: 1) Data is corrupt, and so the end isn't // padded properly. 2) Padding is off, and the data to be encrypted isn't a multiple // of a block long. // Created: 1/12/03 // // -------------------------------------------------------------------------- int CipherContext::Final(void *pOutBuffer, int OutLength) { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } if(!mWithinTransform) { THROW_EXCEPTION(CipherException, BeginNotCalled) } // Check parameters if(pOutBuffer == 0 || OutLength < 0) { THROW_EXCEPTION(CipherException, BadArguments) } // Check output buffer size if(OutLength < (2 * EVP_CIPHER_CTX_block_size(&ctx))) { THROW_EXCEPTION(CipherException, OutputBufferTooSmall); } // Do the transform int outLength = OutLength; #ifndef HAVE_OLD_SSL if(EVP_CipherFinal_ex(&ctx, (unsigned char*)pOutBuffer, &outLength) != 1) { THROW_EXCEPTION(CipherException, EVPFinalFailure) } #else OldOpenSSLFinal((unsigned char*)pOutBuffer, outLength); #endif mWithinTransform = false; return outLength; } #ifdef HAVE_OLD_SSL // -------------------------------------------------------------------------- // // Function // Name: CipherContext::OldOpenSSLFinal(unsigned char *, int &) // Purpose: The old version of OpenSSL needs more work doing to finalise the cipher, // and reset it so that it's ready for another go. // Created: 27/3/04 // // -------------------------------------------------------------------------- void CipherContext::OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut) { // Old version needs to use a different form, and then set up the cipher again for next time around int outLength = rOutLengthOut; // Have to emulate padding off... int blockSize = EVP_CIPHER_CTX_block_size(&ctx); if(mPaddingOn) { // Just use normal final call if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1) { THROW_EXCEPTION(CipherException, EVPFinalFailure) } } else { // Padding is off. OpenSSL < 0.9.7 doesn't support this, so it has to be // bodged in there. Which isn't nice. if(mFunction == Decrypt) { // NASTY -- fiddling around with internals like this is bad. // But only way to get this working on old versions of OpenSSL. if(!EVP_EncryptUpdate(&ctx,Buffer,&outLength,ctx.buf,0) || outLength != blockSize) { THROW_EXCEPTION(CipherException, EVPFinalFailure) } // Clean up EVP_CIPHER_CTX_cleanup(&ctx); } else { // Check that the length is correct if((ctx.buf_len % blockSize) != 0) { THROW_EXCEPTION(CipherException, EVPFinalFailure) } // For encryption, assume that the last block entirely is // padding, and remove it. char temp[1024]; outLength = sizeof(temp); if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1) { THROW_EXCEPTION(CipherException, EVPFinalFailure) } // Remove last block, assuming it's full of padded bytes only. outLength -= blockSize; // Copy anything to the main buffer // (can't just use main buffer, because it might overwrite something important) if(outLength > 0) { ::memcpy(Buffer, temp, outLength); } } } // Reinitialise the cipher for the next time around if(EVP_CipherInit(&ctx, mpDescription->GetCipher(), NULL, NULL, mFunction) != 1) { THROW_EXCEPTION(CipherException, EVPInitFailure) } mpDescription->SetupParameters(&ctx); // Update length for caller rOutLengthOut = outLength; } #endif // -------------------------------------------------------------------------- // // Function // Name: CipherContext::InSizeForOutBufferSize(int) // Purpose: Returns the maximum amount of data that can be sent in // given a output buffer size. // Created: 1/12/03 // // -------------------------------------------------------------------------- int CipherContext::InSizeForOutBufferSize(int OutLength) { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } // Strictly speaking, the *2 is unnecessary. However... // Final() is paranoid, and requires two input blocks of space to work. return OutLength - (EVP_CIPHER_CTX_block_size(&ctx) * 2); } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::MaxOutSizeForInBufferSize(int) // Purpose: Returns the maximum output size for an input of a given length. // Will tend to over estimate, as it needs to allow space for Final() to be called. // Created: 3/12/03 // // -------------------------------------------------------------------------- int CipherContext::MaxOutSizeForInBufferSize(int InLength) { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } // Final() is paranoid, and requires two input blocks of space to work, and so we need to add // three blocks on to be absolutely sure. return InLength + (EVP_CIPHER_CTX_block_size(&ctx) * 3); } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::TransformBlock(void *, int, const void *, int) // Purpose: Transform one block to another all in one go, no Final required. // Created: 1/12/03 // // -------------------------------------------------------------------------- int CipherContext::TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength) { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } // Warn if in a transformation if(mWithinTransform) { BOX_WARNING("CipherContext::TransformBlock called when " "context flagged as within a transform"); } // Check output buffer size if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx))) { // Check if padding is off, in which case the buffer can be smaller if(!mPaddingOn && OutLength <= InLength) { // This is OK. } else { THROW_EXCEPTION(CipherException, OutputBufferTooSmall); } } // Initialise the cipher context again if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1) { THROW_EXCEPTION(CipherException, EVPInitFailure) } // Do the entire block int outLength = 0; try { // Update outLength = OutLength; if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1) { THROW_EXCEPTION(CipherException, EVPUpdateFailure) } // Finalise int outLength2 = OutLength - outLength; #ifndef HAVE_OLD_SSL if(EVP_CipherFinal_ex(&ctx, ((unsigned char*)pOutBuffer) + outLength, &outLength2) != 1) { THROW_EXCEPTION(CipherException, EVPFinalFailure) } #else OldOpenSSLFinal(((unsigned char*)pOutBuffer) + outLength, outLength2); #endif outLength += outLength2; } catch(...) { // Finalise the context, so definately ready for the next caller int outs = OutLength; #ifndef HAVE_OLD_SSL EVP_CipherFinal_ex(&ctx, (unsigned char*)pOutBuffer, &outs); #else OldOpenSSLFinal((unsigned char*)pOutBuffer, outs); #endif throw; } return outLength; } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::GetIVLength() // Purpose: Returns the size of the IV for this context // Created: 3/12/03 // // -------------------------------------------------------------------------- int CipherContext::GetIVLength() { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } return EVP_CIPHER_CTX_iv_length(&ctx); } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::SetIV(const void *) // Purpose: Sets the IV for this context (must be correctly sized, use GetIVLength) // Created: 3/12/03 // // -------------------------------------------------------------------------- void CipherContext::SetIV(const void *pIV) { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } // Warn if in a transformation if(mWithinTransform) { BOX_WARNING("CipherContext::SetIV called when context " "flagged as within a transform"); } // Set IV if(EVP_CipherInit(&ctx, NULL, NULL, (unsigned char *)pIV, -1) != 1) { THROW_EXCEPTION(CipherException, EVPInitFailure) } #ifdef HAVE_OLD_SSL // Update description if(mpDescription != 0) { mpDescription->SetIV(pIV); } #endif } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::SetRandomIV(int &) // Purpose: Set a random IV for the context, and return a pointer to the IV used, // and the length of this IV in the rLengthOut arg. // Created: 3/12/03 // // -------------------------------------------------------------------------- const void *CipherContext::SetRandomIV(int &rLengthOut) { if(!mInitialised) { THROW_EXCEPTION(CipherException, NotInitialised) } // Warn if in a transformation if(mWithinTransform) { BOX_WARNING("CipherContext::SetRandomIV called when " "context flagged as within a transform"); } // Get length of IV unsigned int ivLen = EVP_CIPHER_CTX_iv_length(&ctx); if(ivLen > sizeof(mGeneratedIV)) { THROW_EXCEPTION(CipherException, IVSizeImplementationLimitExceeded) } // Generate some random data Random::Generate(mGeneratedIV, ivLen); // Set IV if(EVP_CipherInit(&ctx, NULL, NULL, mGeneratedIV, -1) != 1) { THROW_EXCEPTION(CipherException, EVPInitFailure) } #ifdef HAVE_OLD_SSL // Update description if(mpDescription != 0) { mpDescription->SetIV(mGeneratedIV); } #endif // Return the IV and it's length rLengthOut = ivLen; return mGeneratedIV; } // -------------------------------------------------------------------------- // // Function // Name: CipherContext::UsePadding(bool) // Purpose: Set whether or not the context uses padding. // Created: 12/12/03 // // -------------------------------------------------------------------------- void CipherContext::UsePadding(bool Padding) { #ifndef HAVE_OLD_SSL if(EVP_CIPHER_CTX_set_padding(&ctx, Padding) != 1) { THROW_EXCEPTION(CipherException, EVPSetPaddingFailure) } #endif mPaddingOn = Padding; } boxbackup/lib/crypto/MD5Digest.h0000664000175000017500000000220210347400657017300 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MD5Digest.h // Purpose: Simple interface for creating MD5 digests // Created: 8/12/03 // // -------------------------------------------------------------------------- #ifndef MD5DIGEST_H #define MD5DIGEST_H #include #include // -------------------------------------------------------------------------- // // Function // Name: MD5Digest // Purpose: Simple interface for creating MD5 digests // Created: 8/12/03 // // -------------------------------------------------------------------------- class MD5Digest { public: MD5Digest(); virtual ~MD5Digest(); void Add(const std::string &rString); void Add(const void *pData, int Length); void Finish(); std::string DigestAsString(); uint8_t *DigestAsData(int *pLength = 0) { if(pLength) *pLength = sizeof(mDigest); return mDigest; } enum { DigestLength = MD5_DIGEST_LENGTH }; int CopyDigestTo(uint8_t *to); bool DigestMatches(uint8_t *pCompareWith) const; private: MD5_CTX md5; uint8_t mDigest[MD5_DIGEST_LENGTH]; }; #endif // MD5DIGEST_H boxbackup/lib/crypto/RollingChecksum.cpp0000664000175000017500000000361310347400657021206 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RollingChecksum.cpp // Purpose: A simple rolling checksum over a block of data // Created: 6/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include "RollingChecksum.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: RollingChecksum::RollingChecksum(const void *, unsigned int) // Purpose: Constructor -- does initial computation of the checksum. // Created: 6/12/03 // // -------------------------------------------------------------------------- RollingChecksum::RollingChecksum(const void * const data, const unsigned int Length) : a(0), b(0) { const uint8_t *block = (const uint8_t *)data; for(unsigned int x = Length; x >= 1; --x) { a += (*block); b += x * (*block); ++block; } } // -------------------------------------------------------------------------- // // Function // Name: RollingChecksum::RollForwardSeveral(uint8_t*, uint8_t*, unsigned int, unsigned int) // Purpose: Move the checksum forward a block, given a pointer to the first byte of the current block, // and a pointer just after the last byte of the current block and the length of the block and of the skip. // Created: 7/14/05 // // -------------------------------------------------------------------------- void RollingChecksum::RollForwardSeveral(const uint8_t * const StartOfThisBlock, const uint8_t * const LastOfNextBlock, const unsigned int Length, const unsigned int Skip) { // IMPLEMENTATION NOTE: Everything is implicitly mod 2^16 -- uint16_t's will overflow nicely. unsigned int i; uint16_t sumBegin=0, j,k; for(i=0; i < Skip; i++) { j = StartOfThisBlock[i]; k = LastOfNextBlock[i]; sumBegin += j; a += (k - j); b += a; } b -= Length * sumBegin; } boxbackup/lib/crypto/CipherDescription.cpp0000664000175000017500000000350410347400657021532 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherDescription.cpp // Purpose: Pure virtual base class for describing ciphers // Created: 1/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE #include "CipherDescription.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: CipherDescription::CipherDescription() // Purpose: Constructor // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherDescription::CipherDescription() { } // -------------------------------------------------------------------------- // // Function // Name: CipherDescription::CipherDescription(const CipherDescription &) // Purpose: Copy constructor // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherDescription::CipherDescription(const CipherDescription &rToCopy) { } // -------------------------------------------------------------------------- // // Function // Name: ~CipherDescription::CipherDescription() // Purpose: Destructor // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherDescription::~CipherDescription() { } // -------------------------------------------------------------------------- // // Function // Name: CipherDescription::operator=(const CipherDescription &) // Purpose: Assignment operator // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherDescription &CipherDescription::operator=(const CipherDescription &rToCopy) { return *this; } boxbackup/lib/crypto/CipherContext.h0000664000175000017500000000413710347400657020343 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherContext.h // Purpose: Context for symmetric encryption / descryption // Created: 1/12/03 // // -------------------------------------------------------------------------- #ifndef CIPHERCONTEXT__H #define CIPHERCONTEXT__H #ifdef BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_FALSE always include CipherContext.h first in any .cpp file #endif #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE #include class CipherDescription; #define CIPHERCONTEXT_MAX_GENERATED_IV_LENGTH 32 // -------------------------------------------------------------------------- // // Class // Name: CipherContext // Purpose: Context for symmetric encryption / descryption // Created: 1/12/03 // // -------------------------------------------------------------------------- class CipherContext { public: CipherContext(); ~CipherContext(); private: CipherContext(const CipherContext &); // no copying CipherContext &operator=(const CipherContext &); // no assignment public: typedef enum { Decrypt = 0, Encrypt = 1 } CipherFunction; void Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription); void Reset(); void Begin(); int Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength); int Final(void *pOutBuffer, int OutLength); int InSizeForOutBufferSize(int OutLength); int MaxOutSizeForInBufferSize(int InLength); int TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength); bool IsInitialised() {return mInitialised;} int GetIVLength(); void SetIV(const void *pIV); const void *SetRandomIV(int &rLengthOut); void UsePadding(bool Padding = true); #ifdef HAVE_OLD_SSL void OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut); #endif private: EVP_CIPHER_CTX ctx; bool mInitialised; bool mWithinTransform; bool mPaddingOn; uint8_t mGeneratedIV[CIPHERCONTEXT_MAX_GENERATED_IV_LENGTH]; #ifdef HAVE_OLD_SSL CipherFunction mFunction; CipherDescription *mpDescription; #endif }; #endif // CIPHERCONTEXT__H boxbackup/lib/crypto/Random.cpp0000664000175000017500000000563710654464221017343 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Random.cpp // Purpose: Random numbers // Created: 31/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "Random.h" #include "CipherException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: Random::Initialise() // Purpose: Add additional randomness to the standard library initialisation // Created: 18/6/04 // // -------------------------------------------------------------------------- void Random::Initialise() { #ifdef HAVE_RANDOM_DEVICE if(::RAND_load_file(RANDOM_DEVICE, 1024) != 1024) { THROW_EXCEPTION(CipherException, RandomInitFailed) } #else BOX_ERROR("No random device -- additional seeding of random number " "generator not performed."); #endif } // -------------------------------------------------------------------------- // // Function // Name: Random::Generate(void *, int) // Purpose: Generate Length bytes of random data // Created: 31/12/03 // // -------------------------------------------------------------------------- void Random::Generate(void *pOutput, int Length) { if(RAND_pseudo_bytes((uint8_t*)pOutput, Length) == -1) { THROW_EXCEPTION(CipherException, PseudoRandNotAvailable) } } // -------------------------------------------------------------------------- // // Function // Name: Random::GenerateHex(int) // Purpose: Generate Length bytes of hex encoded data. Note that the // maximum length requested is limited. (Returns a string // 2 x Length characters long.) // Created: 1/11/04 // // -------------------------------------------------------------------------- std::string Random::GenerateHex(int Length) { uint8_t r[256]; if(Length > (int)sizeof(r)) { THROW_EXCEPTION(CipherException, LengthRequestedTooLongForRandomHex) } Random::Generate(r, Length); std::string o; static const char *h = "0123456789abcdef"; for(int l = 0; l < Length; ++l) { o += h[r[l] >> 4]; o += h[r[l] & 0xf]; } return o; } // -------------------------------------------------------------------------- // // Function // Name: Random::RandomInt(int) // Purpose: Return a random integer between 0 and MaxValue inclusive. // Created: 21/1/04 // // -------------------------------------------------------------------------- uint32_t Random::RandomInt(uint32_t MaxValue) { uint32_t v = 0; // Generate a mask uint32_t mask = 0; while(mask < MaxValue) { mask = (mask << 1) | 1; } do { // Generate a random number uint32_t r = 0; Random::Generate(&r, sizeof(r)); // Mask off relevant bits v = r & mask; // Check that it's in the right range. } while(v > MaxValue); // NOTE: don't do a mod, because this doesn't give a correct random distribution return v; } boxbackup/lib/crypto/RollingChecksum.h0000664000175000017500000000731710347400657020660 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RollingChecksum.h // Purpose: A simple rolling checksum over a block of data // Created: 6/12/03 // // -------------------------------------------------------------------------- #ifndef ROLLINGCHECKSUM__H #define ROLLINGCHECKSUM__H // -------------------------------------------------------------------------- // // Class // Name: RollingChecksum // Purpose: A simple rolling checksum over a block of data -- can move the block // "forwards" in memory and get the next checksum efficiently. // // Implementation of http://rsync.samba.org/tech_report/node3.html // Created: 6/12/03 // // -------------------------------------------------------------------------- class RollingChecksum { public: RollingChecksum(const void * const data, const unsigned int Length); // -------------------------------------------------------------------------- // // Function // Name: RollingChecksum::RollForward(uint8_t, uint8_t, unsigned int) // Purpose: Move the checksum forward a block, given the first byte of the current block, // last byte of the next block (it's rolling forward to) and the length of the block. // Created: 6/12/03 // // -------------------------------------------------------------------------- inline void RollForward(const uint8_t StartOfThisBlock, const uint8_t LastOfNextBlock, const unsigned int Length) { // IMPLEMENTATION NOTE: Everything is implicitly mod 2^16 -- uint16_t's will overflow nicely. a -= StartOfThisBlock; a += LastOfNextBlock; b -= Length * StartOfThisBlock; b += a; } // -------------------------------------------------------------------------- // // Function // Name: RollingChecksum::RollForwardSeveral(uint8_t*, uint8_t*, unsigned int, unsigned int) // Purpose: Move the checksum forward a block, given a pointer to the first byte of the current block, // and a pointer just after the last byte of the current block and the length of the block and of the skip. // Created: 7/14/05 // // -------------------------------------------------------------------------- void RollForwardSeveral(const uint8_t * const StartOfThisBlock, const uint8_t * const LastOfNextBlock, const unsigned int Length, const unsigned int Skip); // -------------------------------------------------------------------------- // // Function // Name: RollingChecksum::GetChecksum() // Purpose: Returns the checksum // Created: 6/12/03 // // -------------------------------------------------------------------------- inline uint32_t GetChecksum() const { return ((uint32_t)a) | (((uint32_t)b) << 16); } // Components, just in case they're handy inline uint16_t GetComponent1() const {return a;} inline uint16_t GetComponent2() const {return b;} // -------------------------------------------------------------------------- // // Function // Name: RollingChecksum::GetComponentForHashing() // Purpose: Return the 16 bit component used for hashing and/or quick checks // Created: 6/12/03 // // -------------------------------------------------------------------------- inline uint16_t GetComponentForHashing() const { return b; } // -------------------------------------------------------------------------- // // Function // Name: RollingChecksum::ExtractHashingComponent(uint32_t) // Purpose: Static. Given a full checksum, extract the component used in the hashing table. // Created: 14/1/04 // // -------------------------------------------------------------------------- static inline uint16_t ExtractHashingComponent(const uint32_t Checksum) { return Checksum >> 16; } private: uint16_t a; uint16_t b; }; #endif // ROLLINGCHECKSUM__H boxbackup/lib/crypto/Makefile.extra0000664000175000017500000000032211345266370020166 0ustar siretartsiretart MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_CipherException.cpp autogen_CipherException.h: $(MAKEEXCEPTION) CipherException.txt $(_PERL) $(MAKEEXCEPTION) CipherException.txt boxbackup/lib/crypto/CipherBlowfish.h0000664000175000017500000000311610347400657020470 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherBlowfish.h // Purpose: Blowfish cipher description // Created: 1/12/03 // // -------------------------------------------------------------------------- #ifndef CIPHERBLOWFISH__H #define CIPHERBLOWFISH__H #ifdef HAVE_OLD_SSL #include #endif #include "CipherDescription.h" // -------------------------------------------------------------------------- // // Class // Name: CipherBlowfish // Purpose: Description of Blowfish cipher parameters -- note that copies are not made of key material and IV, careful with object lifetimes. // Created: 1/12/03 // // -------------------------------------------------------------------------- class CipherBlowfish : public CipherDescription { public: CipherBlowfish(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector = 0); CipherBlowfish(const CipherBlowfish &rToCopy); virtual ~CipherBlowfish(); CipherBlowfish &operator=(const CipherBlowfish &rToCopy); // Return OpenSSL cipher object virtual const EVP_CIPHER *GetCipher() const; // Setup any other parameters virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const; #ifdef HAVE_OLD_SSL CipherDescription *Clone() const; void SetIV(const void *pIV); #endif private: CipherDescription::CipherMode mMode; #ifndef HAVE_OLD_SSL const void *mpKey; unsigned int mKeyLength; const void *mpInitialisationVector; #else std::string mKey; uint8_t mInitialisationVector[8]; #endif }; #endif // CIPHERBLOWFISH__H boxbackup/lib/crypto/MD5Digest.cpp0000664000175000017500000000245510347400657017645 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MD5Digest.cpp // Purpose: Simple interface for creating MD5 digests // Created: 8/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include "MD5Digest.h" #include "MemLeakFindOn.h" MD5Digest::MD5Digest() { MD5_Init(&md5); for(unsigned int l = 0; l < sizeof(mDigest); ++l) { mDigest[l] = 0; } } MD5Digest::~MD5Digest() { } void MD5Digest::Add(const std::string &rString) { MD5_Update(&md5, rString.c_str(), rString.size()); } void MD5Digest::Add(const void *pData, int Length) { MD5_Update(&md5, pData, Length); } void MD5Digest::Finish() { MD5_Final(mDigest, &md5); } std::string MD5Digest::DigestAsString() { std::string r; static const char *hex = "0123456789abcdef"; for(unsigned int l = 0; l < sizeof(mDigest); ++l) { r += hex[(mDigest[l] & 0xf0) >> 4]; r += hex[(mDigest[l] & 0x0f)]; } return r; } int MD5Digest::CopyDigestTo(uint8_t *to) { for(int l = 0; l < MD5_DIGEST_LENGTH; ++l) { to[l] = mDigest[l]; } return MD5_DIGEST_LENGTH; } bool MD5Digest::DigestMatches(uint8_t *pCompareWith) const { for(int l = 0; l < MD5_DIGEST_LENGTH; ++l) { if(pCompareWith[l] != mDigest[l]) return false; } return true; } boxbackup/lib/crypto/CipherBlowfish.cpp0000664000175000017500000001246310347400657021030 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CipherBlowfish.cpp // Purpose: Blowfish cipher description // Created: 1/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #ifdef HAVE_OLD_SSL #include #include #endif #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE #include "CipherBlowfish.h" #include "CipherException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: CipherBlowfish::CipherBlowfish(CipherDescription::CipherMode, const void *, unsigned int, const void *) // Purpose: Constructor -- note key material and IV are not copied. KeyLength in bytes. // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherBlowfish::CipherBlowfish(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector) : CipherDescription(), mMode(Mode) #ifndef HAVE_OLD_SSL , mpKey(pKey), mKeyLength(KeyLength), mpInitialisationVector(pInitialisationVector) { } #else { mKey.assign((const char *)pKey, KeyLength); if(pInitialisationVector == 0) { bzero(mInitialisationVector, sizeof(mInitialisationVector)); } else { ::memcpy(mInitialisationVector, pInitialisationVector, sizeof(mInitialisationVector)); } } #endif // -------------------------------------------------------------------------- // // Function // Name: CipherBlowfish::CipherBlowfish(const CipherBlowfish &) // Purpose: Copy constructor // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherBlowfish::CipherBlowfish(const CipherBlowfish &rToCopy) : CipherDescription(rToCopy), mMode(rToCopy.mMode), #ifndef HAVE_OLD_SSL mpKey(rToCopy.mpKey), mKeyLength(rToCopy.mKeyLength), mpInitialisationVector(rToCopy.mpInitialisationVector) { } #else mKey(rToCopy.mKey) { ::memcpy(mInitialisationVector, rToCopy.mInitialisationVector, sizeof(mInitialisationVector)); } #endif #ifdef HAVE_OLD_SSL // Hack functions to support old OpenSSL API CipherDescription *CipherBlowfish::Clone() const { return new CipherBlowfish(*this); } void CipherBlowfish::SetIV(const void *pIV) { if(pIV == 0) { bzero(mInitialisationVector, sizeof(mInitialisationVector)); } else { ::memcpy(mInitialisationVector, pIV, sizeof(mInitialisationVector)); } } #endif // -------------------------------------------------------------------------- // // Function // Name: ~CipherBlowfish::CipherBlowfish() // Purpose: Destructor // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherBlowfish::~CipherBlowfish() { #ifdef HAVE_OLD_SSL // Zero copy of key for(unsigned int l = 0; l < mKey.size(); ++l) { mKey[l] = '\0'; } #endif } // -------------------------------------------------------------------------- // // Function // Name: CipherBlowfish::operator=(const CipherBlowfish &) // Purpose: Assignment operator // Created: 1/12/03 // // -------------------------------------------------------------------------- CipherBlowfish &CipherBlowfish::operator=(const CipherBlowfish &rToCopy) { CipherDescription::operator=(rToCopy); mMode = rToCopy.mMode; #ifndef HAVE_OLD_SSL mpKey = rToCopy.mpKey; mKeyLength = rToCopy.mKeyLength; mpInitialisationVector = rToCopy.mpInitialisationVector; #else mKey = rToCopy.mKey; ::memcpy(mInitialisationVector, rToCopy.mInitialisationVector, sizeof(mInitialisationVector)); #endif return *this; } // -------------------------------------------------------------------------- // // Function // Name: CipherBlowfish::GetCipher() // Purpose: Returns cipher object // Created: 1/12/03 // // -------------------------------------------------------------------------- const EVP_CIPHER *CipherBlowfish::GetCipher() const { switch(mMode) { case CipherDescription::Mode_ECB: return EVP_bf_ecb(); break; case CipherDescription::Mode_CBC: return EVP_bf_cbc(); break; case CipherDescription::Mode_CFB: return EVP_bf_cfb(); break; case CipherDescription::Mode_OFB: return EVP_bf_ofb(); break; default: break; } // Unknown! THROW_EXCEPTION(CipherException, UnknownCipherMode) } // -------------------------------------------------------------------------- // // Function // Name: CipherBlowfish::SetupParameters(EVP_CIPHER_CTX *) // Purpose: Set up various parameters for cipher // Created: 1/12/03 // // -------------------------------------------------------------------------- void CipherBlowfish::SetupParameters(EVP_CIPHER_CTX *pCipherContext) const { ASSERT(pCipherContext != 0); // Set key length #ifndef HAVE_OLD_SSL if(EVP_CIPHER_CTX_set_key_length(pCipherContext, mKeyLength) != 1) #else if(EVP_CIPHER_CTX_set_key_length(pCipherContext, mKey.size()) != 1) #endif { THROW_EXCEPTION(CipherException, EVPBadKeyLength) } // Set key #ifndef HAVE_OLD_SSL if(EVP_CipherInit_ex(pCipherContext, NULL, NULL, (unsigned char*)mpKey, (unsigned char*)mpInitialisationVector, -1) != 1) #else if(EVP_CipherInit(pCipherContext, NULL, (unsigned char*)mKey.c_str(), (unsigned char*)mInitialisationVector, -1) != 1) #endif { THROW_EXCEPTION(CipherException, EVPInitFailure) } } boxbackup/lib/raidfile/0000775000175000017500000000000011652362374015650 5ustar siretartsiretartboxbackup/lib/raidfile/RaidFileWrite.cpp0000664000175000017500000006412711443463447021061 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileWrite.cpp // Purpose: Writing RAID like files // Created: 2003/07/10 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include #include #include #include "Guards.h" #include "RaidFileWrite.h" #include "RaidFileController.h" #include "RaidFileException.h" #include "RaidFileUtil.h" #include "Utils.h" // For DirectoryExists fn #include "RaidFileRead.h" #include "MemLeakFindOn.h" // should be a multiple of 2 #define TRANSFORM_BLOCKS_TO_LOAD 4 // Must have this number of discs in the set #define TRANSFORM_NUMBER_DISCS_REQUIRED 3 // we want to use POSIX fstat() for now, not the emulated one #undef fstat // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::RaidFileWrite(int, const std::string &) // Purpose: Simple constructor, just stores required details // Created: 2003/07/10 // // -------------------------------------------------------------------------- RaidFileWrite::RaidFileWrite(int SetNumber, const std::string &Filename) : mSetNumber(SetNumber), mFilename(Filename), mOSFileHandle(-1), // not valid file handle mRefCount(-1) // unknown refcount { } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::RaidFileWrite(int, // const std::string &, int refcount) // Purpose: Constructor with check for overwriting file // with multiple references // Created: 2009/07/05 // // -------------------------------------------------------------------------- RaidFileWrite::RaidFileWrite(int SetNumber, const std::string &Filename, int refcount) : mSetNumber(SetNumber), mFilename(Filename), mOSFileHandle(-1), // not valid file handle mRefCount(refcount) { // Can't check for zero refcount here, because it's legal // to create a RaidFileWrite to delete an object with zero refcount. // Check in Commit() and Delete() instead. if (refcount > 1) { BOX_ERROR("Attempted to modify object " << mFilename << ", which has " << refcount << " references"); THROW_EXCEPTION(RaidFileException, RequestedModifyMultiplyReferencedFile); } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::~RaidFileWrite() // Purpose: Destructor (will discard written file if not commited) // Created: 2003/07/10 // // -------------------------------------------------------------------------- RaidFileWrite::~RaidFileWrite() { if(mOSFileHandle != -1) { Discard(); } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Open() // Purpose: Opens the file for writing // Created: 2003/07/10 // // -------------------------------------------------------------------------- void RaidFileWrite::Open(bool AllowOverwrite) { if(mOSFileHandle != -1) { THROW_EXCEPTION(RaidFileException, AlreadyOpen) } // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); // Check for overwriting? (step 1) if(!AllowOverwrite) { // See if the file exists already -- can't overwrite existing files RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename); if(existance != RaidFileUtil::NoFile) { BOX_ERROR("Attempted to overwrite raidfile " << mSetNumber << " " << mFilename); THROW_EXCEPTION(RaidFileException, CannotOverwriteExistingFile) } } // Get the filename for the write file std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); // Add on a temporary extension writeFilename += 'X'; // Attempt to open mOSFileHandle = ::open(writeFilename.c_str(), O_WRONLY | O_CREAT | O_BINARY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if(mOSFileHandle == -1) { BOX_LOG_SYS_ERROR("Failed to open file: " << writeFilename); THROW_EXCEPTION(RaidFileException, ErrorOpeningWriteFile) } // Get a lock on the write file #ifdef HAVE_FLOCK int errnoBlock = EWOULDBLOCK; if(::flock(mOSFileHandle, LOCK_EX | LOCK_NB) != 0) #elif HAVE_DECL_F_SETLK int errnoBlock = EAGAIN; struct flock desc; desc.l_type = F_WRLCK; desc.l_whence = SEEK_SET; desc.l_start = 0; desc.l_len = 0; if(::fcntl(mOSFileHandle, F_SETLK, &desc) != 0) #else int errnoBlock = ENOSYS; if (0) #endif { // Lock was not obtained. bool wasLocked = (errno == errnoBlock); // Close the file ::close(mOSFileHandle); mOSFileHandle = -1; // Report an exception? if(wasLocked) { THROW_EXCEPTION(RaidFileException, FileIsCurrentlyOpenForWriting) } else { // Random error occured THROW_EXCEPTION(RaidFileException, OSError) } } // Truncate it to size zero if(::ftruncate(mOSFileHandle, 0) != 0) { THROW_EXCEPTION(RaidFileException, ErrorOpeningWriteFileOnTruncate) } // Done! } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Write(const void *, int) // Purpose: Writes a block of data // Created: 2003/07/10 // // -------------------------------------------------------------------------- void RaidFileWrite::Write(const void *pBuffer, int Length) { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Write data int written = ::write(mOSFileHandle, pBuffer, Length); if(written != Length) { BOX_LOG_SYS_ERROR("RaidFileWrite failed, Length = " << Length << ", written = " << written); THROW_EXCEPTION(RaidFileException, OSError) } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::GetPosition() // Purpose: Returns current position in file // Created: 2003/07/10 // // -------------------------------------------------------------------------- IOStream::pos_type RaidFileWrite::GetPosition() const { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Use lseek to find the current file position off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); if(p == -1) { THROW_EXCEPTION(RaidFileException, OSError) } return p; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Seek(RaidFileWrite::pos_type, bool) // Purpose: Seeks in the file, relative to current position if Relative is true. // Created: 2003/07/10 // // -------------------------------------------------------------------------- void RaidFileWrite::Seek(IOStream::pos_type SeekTo, int SeekType) { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Seek... if(::lseek(mOSFileHandle, SeekTo, ConvertSeekTypeToOSWhence(SeekType)) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Commit(bool) // Purpose: Closes, and commits the written file // Created: 2003/07/10 // // -------------------------------------------------------------------------- void RaidFileWrite::Commit(bool ConvertToRaidNow) { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } if (mRefCount == 0) { BOX_ERROR("Attempted to modify object " << mFilename << ", which has no references"); THROW_EXCEPTION(RaidFileException, RequestedModifyUnreferencedFile); } // Rename it into place -- BEFORE it's closed so lock remains #ifdef WIN32 // Except on Win32 which doesn't allow renaming open files // Close file... if(::close(mOSFileHandle) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } mOSFileHandle = -1; #endif // WIN32 RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); // Get the filename for the write file std::string renameTo(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); // And the current name std::string renameFrom(renameTo + 'X'); #ifdef WIN32 // need to delete the target first if(::unlink(renameTo.c_str()) != 0 && GetLastError() != ERROR_FILE_NOT_FOUND) { BOX_LOG_WIN_ERROR("Failed to delete file: " << renameTo); THROW_EXCEPTION(RaidFileException, OSError) } #endif if(::rename(renameFrom.c_str(), renameTo.c_str()) != 0) { BOX_LOG_SYS_ERROR("Failed to rename file: " << renameFrom << " to " << renameTo); THROW_EXCEPTION(RaidFileException, OSError) } #ifndef WIN32 // Close file... if(::close(mOSFileHandle) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } mOSFileHandle = -1; #endif // !WIN32 // Raid it? if(ConvertToRaidNow) { TransformToRaidStorage(); } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Discard() // Purpose: Closes, discarding the data written. // Created: 2003/07/10 // // -------------------------------------------------------------------------- void RaidFileWrite::Discard() { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); // Get the filename for the write file (temporary) std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); writeFilename += 'X'; // Unlink and close it #ifdef WIN32 // On Win32 we must close it first if (::close(mOSFileHandle) != 0 || ::unlink(writeFilename.c_str()) != 0) #else // !WIN32 if (::unlink(writeFilename.c_str()) != 0 || ::close(mOSFileHandle) != 0) #endif // !WIN32 { BOX_LOG_SYS_ERROR("Failed to delete file: " << writeFilename); THROW_EXCEPTION(RaidFileException, OSError) } // reset file handle mOSFileHandle = -1; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::TransformToRaidStorage() // Purpose: Turns the file into the RAID storage form // Created: 2003/07/11 // // -------------------------------------------------------------------------- void RaidFileWrite::TransformToRaidStorage() { // open? if(mOSFileHandle != -1) { THROW_EXCEPTION(RaidFileException, WriteFileOpenOnTransform) } // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); if(rdiscSet.IsNonRaidSet()) { // Not in RAID mode -- do nothing return; } // Otherwise check that it's the right sized set if(TRANSFORM_NUMBER_DISCS_REQUIRED != rdiscSet.size()) { THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) } unsigned int blockSize = rdiscSet.GetBlockSize(); // Get the filename for the write file (and get the disc set name for the start disc) int startDisc = 0; std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename, &startDisc)); // Open it FileHandleGuard<> writeFile(writeFilename.c_str()); // Get file information for write file struct stat writeFileStat; if(::fstat(writeFile, &writeFileStat) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } // // DEBUG MODE -- check file system size block size is same as block size for files // // doesn't really apply, as space benefits of using fragment size are worth efficiency, // // and anyway, it'll be buffered eventually so it won't matter. // #ifndef BOX_RELEASE_BUILD // { // if(writeFileStat.st_blksize != blockSize) // { // TRACE2("TransformToRaidStorage: optimal block size of file = %d, of set = %d, MISMATCH\n", // writeFileStat.st_blksize, blockSize); // } // } // #endif // How many blocks is the file? (rounding up) int writeFileSizeInBlocks = (writeFileStat.st_size + (blockSize - 1)) / blockSize; // And how big should the buffer be? (round up to multiple of 2, and no bigger than the preset limit) int bufferSizeBlocks = (writeFileSizeInBlocks + 1) & ~1; if(bufferSizeBlocks > TRANSFORM_BLOCKS_TO_LOAD) bufferSizeBlocks = TRANSFORM_BLOCKS_TO_LOAD; // How big should the buffer be? int bufferSize = (TRANSFORM_BLOCKS_TO_LOAD * blockSize); // Allocate buffer... MemoryBlockGuard buffer(bufferSize); // Allocate buffer for parity file MemoryBlockGuard parityBuffer(blockSize); // Get filenames of eventual files std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 0) % TRANSFORM_NUMBER_DISCS_REQUIRED)); std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 1) % TRANSFORM_NUMBER_DISCS_REQUIRED)); std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 2) % TRANSFORM_NUMBER_DISCS_REQUIRED)); // Make write equivalents std::string stripe1FilenameW(stripe1Filename + 'P'); std::string stripe2FilenameW(stripe2Filename + 'P'); std::string parityFilenameW(parityFilename + 'P'); // Then open them all for writing (in strict order) try { #if HAVE_DECL_O_EXLOCK FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> stripe1(stripe1FilenameW.c_str()); FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> stripe2(stripe2FilenameW.c_str()); FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> parity(parityFilenameW.c_str()); #else FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe1(stripe1FilenameW.c_str()); FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe2(stripe2FilenameW.c_str()); FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> parity(parityFilenameW.c_str()); #endif // Then... read in data... int bytesRead = -1; bool sizeRecordRequired = false; int blocksDone = 0; while((bytesRead = ::read(writeFile, buffer, bufferSize)) > 0) { // Blocks to do... int blocksToDo = (bytesRead + (blockSize - 1)) / blockSize; // Need to add zeros to end? int blocksRoundUp = (blocksToDo + 1) & ~1; int zerosEnd = (blocksRoundUp * blockSize); if(bytesRead != zerosEnd) { // Set the end of the blocks to zero ::memset(buffer + bytesRead, 0, zerosEnd - bytesRead); } // number of int's to XOR unsigned int num = blockSize / sizeof(unsigned int); // Then... calculate and write parity data for(int b = 0; b < blocksToDo; b += 2) { // Calculate int pointers unsigned int *pstripe1 = (unsigned int *)(buffer + (b * blockSize)); unsigned int *pstripe2 = (unsigned int *)(buffer + ((b+1) * blockSize)); unsigned int *pparity = (unsigned int *)((char*)parityBuffer); // Do XOR for(unsigned int n = 0; n < num; ++n) { pparity[n] = pstripe1[n] ^ pstripe2[n]; } // Size of parity to write... int parityWriteSize = blockSize; // Adjust if it's the last block if((blocksDone + (b + 2)) >= writeFileSizeInBlocks) { // Yes... unsigned int bytesInLastTwoBlocks = bytesRead - (b * blockSize); // Some special cases... // Zero will never happen... but in the (imaginary) case it does, the file size will be appended // by the test at the end. if(bytesInLastTwoBlocks == sizeof(RaidFileRead::FileSizeType) || bytesInLastTwoBlocks == blockSize) { // Write the entire block, and put the file size at end sizeRecordRequired = true; } else if(bytesInLastTwoBlocks < blockSize) { // write only these bits parityWriteSize = bytesInLastTwoBlocks; } else if(bytesInLastTwoBlocks < ((blockSize * 2) - sizeof(RaidFileRead::FileSizeType))) { // XOR in the size at the end of the parity block ASSERT(sizeof(RaidFileRead::FileSizeType) == (2*sizeof(unsigned int))); ASSERT(sizeof(RaidFileRead::FileSizeType) >= sizeof(off_t)); int sizePos = (blockSize/sizeof(unsigned int)) - 2; union { RaidFileRead::FileSizeType l; unsigned int i[2]; } sw; sw.l = box_hton64(writeFileStat.st_size); pparity[sizePos+0] = pstripe1[sizePos+0] ^ sw.i[0]; pparity[sizePos+1] = pstripe1[sizePos+1] ^ sw.i[1]; } else { // Write the entire block, and put the file size at end sizeRecordRequired = true; } } // Write block if(::write(parity, parityBuffer, parityWriteSize) != parityWriteSize) { THROW_EXCEPTION(RaidFileException, OSError) } } // Write stripes char *writeFrom = buffer; for(int l = 0; l < blocksToDo; ++l) { // Write the block int toWrite = (l == (blocksToDo - 1)) ?(bytesRead - ((blocksToDo-1)*blockSize)) :blockSize; if(::write(((l&1)==0)?stripe1:stripe2, writeFrom, toWrite) != toWrite) { THROW_EXCEPTION(RaidFileException, OSError) } // Next block writeFrom += blockSize; } // Count of blocks done blocksDone += blocksToDo; } // Error on read? if(bytesRead == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Special case for zero length files if(writeFileStat.st_size == 0) { sizeRecordRequired = true; } // Might need to write the file size to the end of the parity file // if it can't be worked out some other means -- size is required to rebuild the file if one of the stripe files is missing if(sizeRecordRequired) { ASSERT(sizeof(writeFileStat.st_size) <= sizeof(RaidFileRead::FileSizeType)); RaidFileRead::FileSizeType sw = box_hton64(writeFileStat.st_size); ASSERT((::lseek(parity, 0, SEEK_CUR) % blockSize) == 0); if(::write(parity, &sw, sizeof(sw)) != sizeof(sw)) { BOX_LOG_SYS_ERROR("Failed to write to file: " << writeFilename); THROW_EXCEPTION(RaidFileException, OSError) } } // Then close the written files (note in reverse order of opening) parity.Close(); stripe2.Close(); stripe1.Close(); #ifdef WIN32 // Must delete before renaming #define CHECK_UNLINK(file) \ { \ if (::unlink(file) != 0 && errno != ENOENT) \ { \ THROW_EXCEPTION(RaidFileException, OSError); \ } \ } CHECK_UNLINK(stripe1Filename.c_str()); CHECK_UNLINK(stripe2Filename.c_str()); CHECK_UNLINK(parityFilename.c_str()); #undef CHECK_UNLINK #endif // Rename them into place if(::rename(stripe1FilenameW.c_str(), stripe1Filename.c_str()) != 0 || ::rename(stripe2FilenameW.c_str(), stripe2Filename.c_str()) != 0 || ::rename(parityFilenameW.c_str(), parityFilename.c_str()) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } // Close the write file writeFile.Close(); // Finally delete the write file if(::unlink(writeFilename.c_str()) != 0) { BOX_LOG_SYS_ERROR("Failed to delete file: " << writeFilename); THROW_EXCEPTION(RaidFileException, OSError) } } catch(...) { // Unlink all the dodgy files ::unlink(stripe1Filename.c_str()); ::unlink(stripe2Filename.c_str()); ::unlink(parityFilename.c_str()); ::unlink(stripe1FilenameW.c_str()); ::unlink(stripe2FilenameW.c_str()); ::unlink(parityFilenameW.c_str()); // and send the error on its way throw; } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Delete() // Purpose: Deletes a RAID file // Created: 2003/07/13 // // -------------------------------------------------------------------------- void RaidFileWrite::Delete() { if (mRefCount != 0 && mRefCount != -1) { BOX_ERROR("Attempted to delete object " << mFilename << " which has " << mRefCount << " references"); THROW_EXCEPTION(RaidFileException, RequestedDeleteReferencedFile); } // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); // See if the file exists already -- can't delete files which don't exist RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename); if(existance == RaidFileUtil::NoFile) { THROW_EXCEPTION(RaidFileException, RaidFileDoesntExist) } // Get the filename for the write file std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); // Attempt to delete it bool deletedSomething = false; if(::unlink(writeFilename.c_str()) == 0) { deletedSomething = true; } // If we're not running in RAID mode, stop now if(rdiscSet.size() == 1) { return; } // Now the other files std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 0 % TRANSFORM_NUMBER_DISCS_REQUIRED)); std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 1 % TRANSFORM_NUMBER_DISCS_REQUIRED)); std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 2 % TRANSFORM_NUMBER_DISCS_REQUIRED)); if(::unlink(stripe1Filename.c_str()) == 0) { deletedSomething = true; } if(::unlink(stripe2Filename.c_str()) == 0) { deletedSomething = true; } if(::unlink(parityFilename.c_str()) == 0) { deletedSomething = true; } // Check something happened if(!deletedSomething) { THROW_EXCEPTION(RaidFileException, OSError) } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::CreateDirectory(int, const std::string &, bool, int) // Purpose: Creates a directory within the raid file directories with the given name. // Created: 2003/08/20 // // -------------------------------------------------------------------------- void RaidFileWrite::CreateDirectory(int SetNumber, const std::string &rDirName, bool Recursive, int mode) { // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); // Pass on... CreateDirectory(rdiscSet, rDirName, Recursive, mode); } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::CreateDirectory(const RaidFileDiscSet &, const std::string &, bool, int) // Purpose: Creates a directory within the raid file directories with the given name. // Created: 2003/08/20 // // -------------------------------------------------------------------------- void RaidFileWrite::CreateDirectory(const RaidFileDiscSet &rSet, const std::string &rDirName, bool Recursive, int mode) { if(Recursive) { // split up string std::vector elements; SplitString(rDirName, DIRECTORY_SEPARATOR_ASCHAR, elements); // Do each element in turn std::string pn; for(unsigned int e = 0; e < elements.size(); ++e) { // Only do this if the element has some text in it if(elements[e].size() > 0) { pn += elements[e]; if(!RaidFileRead::DirectoryExists(rSet, pn)) { CreateDirectory(rSet, pn, false, mode); } // add separator pn += DIRECTORY_SEPARATOR_ASCHAR; } } return; } // Create a directory in every disc of the set for(unsigned int l = 0; l < rSet.size(); ++l) { // build name std::string dn(rSet[l] + DIRECTORY_SEPARATOR + rDirName); // attempt to create if(::mkdir(dn.c_str(), mode) != 0) { if(errno == EEXIST) { // No. Bad things. THROW_EXCEPTION(RaidFileException, FileExistsInDirectoryCreation) } else { THROW_EXCEPTION(RaidFileException, OSError) } } } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Read(void *, int, int) // Purpose: Unsupported, will exception // Created: 2003/08/21 // // -------------------------------------------------------------------------- int RaidFileWrite::Read(void *pBuffer, int NBytes, int Timeout) { THROW_EXCEPTION(RaidFileException, UnsupportedReadWriteOrClose) } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::Close() // Purpose: Close, discarding file. // Created: 2003/08/21 // // -------------------------------------------------------------------------- void RaidFileWrite::Close() { if(mOSFileHandle != -1) { BOX_WARNING("RaidFileWrite::Close() called, discarding file"); Discard(); } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::StreamDataLeft() // Purpose: Never any data left to read! // Created: 2003/08/21 // // -------------------------------------------------------------------------- bool RaidFileWrite::StreamDataLeft() { return false; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::StreamClosed() // Purpose: Is stream closed for writing? // Created: 2003/08/21 // // -------------------------------------------------------------------------- bool RaidFileWrite::StreamClosed() { return mOSFileHandle == -1; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::GetFileSize() // Purpose: Returns the size of the file written. // Can only be used before the file is commited. // Created: 2003/09/03 // // -------------------------------------------------------------------------- IOStream::pos_type RaidFileWrite::GetFileSize() { if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, CanOnlyGetFileSizeBeforeCommit) } // Stat to get size struct stat st; if(fstat(mOSFileHandle, &st) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } return st.st_size; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileWrite::GetDiscUsageInBlocks() // Purpose: Returns the amount of disc space used, in blocks. // Can only be used before the file is commited. // Created: 2003/09/03 // // -------------------------------------------------------------------------- IOStream::pos_type RaidFileWrite::GetDiscUsageInBlocks() { if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, CanOnlyGetUsageBeforeCommit) } // Stat to get size struct stat st; if(fstat(mOSFileHandle, &st) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } // Then return calculation RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); return RaidFileUtil::DiscUsageInBlocks(st.st_size, rdiscSet); } boxbackup/lib/raidfile/RaidFileRead.h0000664000175000017500000000356511165365160020300 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileRead.h // Purpose: Read Raid like Files // Created: 2003/07/13 // // -------------------------------------------------------------------------- #ifndef RAIDFILEREAD__H #define RAIDFILEREAD__H #include #include #include #include #include "IOStream.h" class RaidFileDiscSet; // -------------------------------------------------------------------------- // // Class // Name: RaidFileRead // Purpose: Read RAID like files // Created: 2003/07/13 // // -------------------------------------------------------------------------- class RaidFileRead : public IOStream { protected: RaidFileRead(int SetNumber, const std::string &Filename); public: virtual ~RaidFileRead(); private: RaidFileRead(const RaidFileRead &rToCopy); public: // Open a raid file static std::auto_ptr Open(int SetNumber, const std::string &Filename, int64_t *pRevisionID = 0, int BufferSizeHint = 4096); // Extra info virtual pos_type GetFileSize() const = 0; // Utility functions static bool FileExists(int SetNumber, const std::string &rFilename, int64_t *pRevisionID = 0); static bool DirectoryExists(const RaidFileDiscSet &rSet, const std::string &rDirName); static bool DirectoryExists(int SetNumber, const std::string &rDirName); enum { DirReadType_FilesOnly = 0, DirReadType_DirsOnly = 1 }; static bool ReadDirectoryContents(int SetNumber, const std::string &rDirName, int DirReadType, std::vector &rOutput); // Common IOStream interface implementation virtual void Write(const void *pBuffer, int NBytes); virtual bool StreamClosed(); virtual pos_type BytesLeftToRead(); pos_type GetDiscUsageInBlocks(); typedef int64_t FileSizeType; protected: int mSetNumber; std::string mFilename; }; #endif // RAIDFILEREAD__H boxbackup/lib/raidfile/RaidFileController.h0000664000175000017500000000574011167477470021557 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileController.h // Purpose: Controls config and daemon comms for RaidFile classes // Created: 2003/07/08 // // -------------------------------------------------------------------------- /* NOTE: will log to local5: include a line like local5.info /var/log/raidfile in /etc/syslog.conf */ #ifndef RAIDFILECONTROLLER__H #define RAIDFILECONTROLLER__H #include #include // -------------------------------------------------------------------------- // // Class // Name: RaidFileDiscSet // Purpose: Describes a set of paritions for RAID like files. // Use as list of directories containing the files. // Created: 2003/07/08 // // -------------------------------------------------------------------------- class RaidFileDiscSet : public std::vector { public: RaidFileDiscSet(int SetID, unsigned int BlockSize) : mSetID(SetID), mBlockSize(BlockSize) { } RaidFileDiscSet(const RaidFileDiscSet &rToCopy) : std::vector(rToCopy), mSetID(rToCopy.mSetID), mBlockSize(rToCopy.mBlockSize) { } ~RaidFileDiscSet() { } int GetSetID() const {return mSetID;} int GetSetNumForWriteFiles(const std::string &rFilename) const; unsigned int GetBlockSize() const {return mBlockSize;} // Is this disc set a non-RAID disc set? (ie files never get transformed to raid storage) bool IsNonRaidSet() const {return 1 == size();} private: int mSetID; unsigned int mBlockSize; }; class _RaidFileController; // compiler warning avoidance // -------------------------------------------------------------------------- // // Class // Name: RaidFileController // Purpose: Manages the configuration of the RaidFile system, handles // communication with the daemon. // Created: 2003/07/08 // // -------------------------------------------------------------------------- class RaidFileController { friend class _RaidFileController; // to avoid compiler warning private: RaidFileController(); RaidFileController(const RaidFileController &rController); public: ~RaidFileController(); public: void Initialise(const std::string& rConfigFilename = "/etc/boxbackup/raidfile.conf"); int GetNumDiscSets() {return mSetList.size();} // -------------------------------------------------------------------------- // // Function // Name: RaidFileController::GetController() // Purpose: Gets the one and only controller object. // Created: 2003/07/08 // // -------------------------------------------------------------------------- static RaidFileController &GetController() {return mController;} RaidFileDiscSet &GetDiscSet(unsigned int DiscSetNum); static std::string DiscSetPathToFileSystemPath(unsigned int DiscSetNum, const std::string &rFilename, int DiscOffset); private: std::vector mSetList; static RaidFileController mController; }; #endif // RAIDFILECONTROLLER__H boxbackup/lib/raidfile/RaidFileController.cpp0000664000175000017500000001470711125267607022106 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileController.cpp // Purpose: Controls config and daemon comms for RaidFile classes // Created: 2003/07/08 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "RaidFileController.h" #include "RaidFileException.h" #include "Configuration.h" #include "MemLeakFindOn.h" RaidFileController RaidFileController::mController; // -------------------------------------------------------------------------- // // Function // Name: RaidFileController::RaidFileController() // Purpose: Constructor // Created: 2003/07/08 // // -------------------------------------------------------------------------- RaidFileController::RaidFileController() { } // -------------------------------------------------------------------------- // // Function // Name: RaidFileController::~RaidFileController() // Purpose: Destructor // Created: 2003/07/08 // // -------------------------------------------------------------------------- RaidFileController::~RaidFileController() { } // -------------------------------------------------------------------------- // // Function // Name: RaidFileController::RaidFileController() // Purpose: Copy constructor // Created: 2003/07/08 // // -------------------------------------------------------------------------- RaidFileController::RaidFileController(const RaidFileController &rController) { THROW_EXCEPTION(RaidFileException, Internal) } // -------------------------------------------------------------------------- // // Function // Name: RaidFileController::Initialise(const std::string&) // Purpose: Initialises the system, loading the configuration file. // Created: 2003/07/08 // // -------------------------------------------------------------------------- void RaidFileController::Initialise(const std::string& rConfigFilename) { MEMLEAKFINDER_NO_LEAKS; static const ConfigurationVerifyKey verifykeys[] = { ConfigurationVerifyKey("SetNumber", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("BlockSize", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("Dir0", ConfigTest_Exists), ConfigurationVerifyKey("Dir1", ConfigTest_Exists), ConfigurationVerifyKey("Dir2", ConfigTest_Exists | ConfigTest_LastEntry) }; static const ConfigurationVerify subverify = { "*", 0, verifykeys, ConfigTest_LastEntry, 0 }; static const ConfigurationVerify verify = { "RAID FILE CONFIG", &subverify, 0, ConfigTest_LastEntry, 0 }; // Load the configuration std::string err; std::auto_ptr pconfig = Configuration::LoadAndVerify( rConfigFilename, &verify, err); if(pconfig.get() == 0 || !err.empty()) { BOX_ERROR("RaidFile configuration file errors: " << err); THROW_EXCEPTION(RaidFileException, BadConfigFile) } // Allow reinitializing the controller by remove any existing // disc sets. Used by Boxi unit tests. mSetList.clear(); // Use the values int expectedSetNum = 0; std::vector confdiscs(pconfig->GetSubConfigurationNames()); for(std::vector::const_iterator i(confdiscs.begin()); i != confdiscs.end(); ++i) { const Configuration &disc(pconfig->GetSubConfiguration((*i).c_str())); int setNum = disc.GetKeyValueInt("SetNumber"); if(setNum != expectedSetNum) { THROW_EXCEPTION(RaidFileException, BadConfigFile) } RaidFileDiscSet set(setNum, (unsigned int)disc.GetKeyValueInt("BlockSize")); // Get the values of the directory keys std::string d0(disc.GetKeyValue("Dir0")); std::string d1(disc.GetKeyValue("Dir1")); std::string d2(disc.GetKeyValue("Dir2")); // Are they all different (using RAID) or all the same (not using RAID) if(d0 != d1 && d1 != d2 && d0 != d2) { set.push_back(d0); set.push_back(d1); set.push_back(d2); } else if(d0 == d1 && d0 == d2) { // Just push the first one, which is the non-RAID place to store files set.push_back(d0); } else { // One must be the same as another! Which is bad. THROW_EXCEPTION(RaidFileException, BadConfigFile) } mSetList.push_back(set); expectedSetNum++; } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileController::GetDiscSet(int) // Purpose: Returns the numbered disc set // Created: 2003/07/08 // // -------------------------------------------------------------------------- RaidFileDiscSet &RaidFileController::GetDiscSet(unsigned int DiscSetNum) { if(DiscSetNum < 0 || DiscSetNum >= mSetList.size()) { THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) } return mSetList[DiscSetNum]; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileDiscSet::GetSetNumForWriteFiles(const std::string &) // Purpose: Returns the set number the 'temporary' written files should // be stored on, given a filename. // Created: 2003/07/10 // // -------------------------------------------------------------------------- int RaidFileDiscSet::GetSetNumForWriteFiles(const std::string &rFilename) const { // Simple hash function, add up the ASCII values of all the characters, // and get modulo number of partitions in the set. std::string::const_iterator i(rFilename.begin()); int h = 0; for(; i != rFilename.end(); ++i) { h += (*i); } return h % size(); } // -------------------------------------------------------------------------- // // Function // Name: RaidFileController::DiscSetPathToFileSystemPath(unsigned int, const std::string &, int) // Purpose: Given a Raid File style file name, return a filename for the physical filing system. // DiscOffset is effectively the disc number (but remember files are rotated around the // discs in a disc set) // Created: 19/1/04 // // -------------------------------------------------------------------------- std::string RaidFileController::DiscSetPathToFileSystemPath(unsigned int DiscSetNum, const std::string &rFilename, int DiscOffset) { if(DiscSetNum < 0 || DiscSetNum >= mController.mSetList.size()) { THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) } // Work out which disc it's to be on int disc = (mController.mSetList[DiscSetNum].GetSetNumForWriteFiles(rFilename) + DiscOffset) % mController.mSetList[DiscSetNum].size(); // Make the string std::string r((mController.mSetList[DiscSetNum])[disc]); r += DIRECTORY_SEPARATOR_ASCHAR; r += rFilename; return r; } boxbackup/lib/raidfile/RaidFileRead.cpp0000664000175000017500000013543611162175316020635 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileRead.cpp // Purpose: Read Raid like Files // Created: 2003/07/13 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include #ifdef HAVE_SYS_UIO_H #include #endif #ifdef HAVE_DIRENT_H #include #endif #include #include #include #include #include #include "RaidFileRead.h" #include "RaidFileException.h" #include "RaidFileController.h" #include "RaidFileUtil.h" #include "MemLeakFindOn.h" #define READ_NUMBER_DISCS_REQUIRED 3 #define READV_MAX_BLOCKS 64 // We want to use POSIX fstat() for now, not the emulated one #undef fstat // -------------------------------------------------------------------------- // // Class // Name: RaidFileRead_NonRaid // Purpose: Internal class for reading RaidFiles which haven't been transformed // into the RAID like form yet. // Created: 2003/07/13 // // -------------------------------------------------------------------------- class RaidFileRead_NonRaid : public RaidFileRead { public: RaidFileRead_NonRaid(int SetNumber, const std::string &Filename, int OSFileHandle); virtual ~RaidFileRead_NonRaid(); private: RaidFileRead_NonRaid(const RaidFileRead_NonRaid &rToCopy); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type GetPosition() const; virtual void Seek(IOStream::pos_type Offset, int SeekType); virtual void Close(); virtual pos_type GetFileSize() const; virtual bool StreamDataLeft(); private: int mOSFileHandle; bool mEOF; }; // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid(int, const std::string &, const std::string &) // Purpose: Constructor // Created: 2003/07/13 // // -------------------------------------------------------------------------- RaidFileRead_NonRaid::RaidFileRead_NonRaid(int SetNumber, const std::string &Filename, int OSFileHandle) : RaidFileRead(SetNumber, Filename), mOSFileHandle(OSFileHandle), mEOF(false) { } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::~RaidFileRead_NonRaid() // Purpose: Destructor // Created: 2003/07/13 // // -------------------------------------------------------------------------- RaidFileRead_NonRaid::~RaidFileRead_NonRaid() { if(mOSFileHandle != -1) { Close(); } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::Read(const void *, int) // Purpose: Reads bytes from the file // Created: 2003/07/13 // // -------------------------------------------------------------------------- int RaidFileRead_NonRaid::Read(void *pBuffer, int NBytes, int Timeout) { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Read data int bytesRead = ::read(mOSFileHandle, pBuffer, NBytes); if(bytesRead == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Check for EOF if(bytesRead == 0) { mEOF = true; } return bytesRead; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::GetPosition() // Purpose: Returns current position // Created: 2003/07/13 // // -------------------------------------------------------------------------- RaidFileRead::pos_type RaidFileRead_NonRaid::GetPosition() const { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Use lseek to find the current file position off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); if(p == -1) { THROW_EXCEPTION(RaidFileException, OSError) } return p; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::Seek(pos_type, int) // Purpose: Seek within the file // Created: 2003/07/13 // // -------------------------------------------------------------------------- void RaidFileRead_NonRaid::Seek(IOStream::pos_type Offset, int SeekType) { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Seek... if(::lseek(mOSFileHandle, Offset, ConvertSeekTypeToOSWhence(SeekType)) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Not EOF any more mEOF = false; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::Close() // Purpose: Close the file (automatically done by destructor) // Created: 2003/07/13 // // -------------------------------------------------------------------------- void RaidFileRead_NonRaid::Close() { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // Close file... if(::close(mOSFileHandle) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } mOSFileHandle = -1; mEOF = true; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::GetFileSize() // Purpose: Returns file size. // Created: 2003/07/14 // // -------------------------------------------------------------------------- RaidFileRead::pos_type RaidFileRead_NonRaid::GetFileSize() const { // open? if(mOSFileHandle == -1) { THROW_EXCEPTION(RaidFileException, NotOpen) } // stat the file struct stat st; if(::fstat(mOSFileHandle, &st) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } return st.st_size; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::StreamDataLeft() // Purpose: Any data left? // Created: 2003/08/21 // // -------------------------------------------------------------------------- bool RaidFileRead_NonRaid::StreamDataLeft() { return !mEOF; } // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // // Class // Name: RaidFileRead_Raid // Purpose: Internal class for reading RaidFiles have been transformed. // Created: 2003/07/13 // // -------------------------------------------------------------------------- class RaidFileRead_Raid : public RaidFileRead { public: friend class RaidFileRead; RaidFileRead_Raid(int SetNumber, const std::string &Filename, int Stripe1Handle, int Stripe2Handle, int ParityHandle, pos_type FileSize, unsigned int BlockSize, bool LastBlockHasSize); virtual ~RaidFileRead_Raid(); private: RaidFileRead_Raid(const RaidFileRead_Raid &rToCopy); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type GetPosition() const; virtual void Seek(IOStream::pos_type Offset, int SeekType); virtual void Close(); virtual pos_type GetFileSize() const; virtual bool StreamDataLeft(); private: int ReadRecovered(void *pBuffer, int NBytes); void AttemptToRecoverFromIOError(bool Stripe1); void SetPosition(pos_type FilePosition); static void MoveDamagedFileAlertDaemon(int SetNumber, const std::string &Filename, bool Stripe1); private: int mStripe1Handle; int mStripe2Handle; int mParityHandle; pos_type mFileSize; unsigned int mBlockSize; pos_type mCurrentPosition; char *mRecoveryBuffer; pos_type mRecoveryBufferStart; bool mLastBlockHasSize; bool mEOF; }; // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid(int, const std::string &, const std::string &) // Purpose: Constructor // Created: 2003/07/13 // // -------------------------------------------------------------------------- RaidFileRead_Raid::RaidFileRead_Raid(int SetNumber, const std::string &Filename, int Stripe1Handle, int Stripe2Handle, int ParityHandle, pos_type FileSize, unsigned int BlockSize, bool LastBlockHasSize) : RaidFileRead(SetNumber, Filename), mStripe1Handle(Stripe1Handle), mStripe2Handle(Stripe2Handle), mParityHandle(ParityHandle), mFileSize(FileSize), mBlockSize(BlockSize), mCurrentPosition(0), mRecoveryBuffer(0), mRecoveryBufferStart(-1), mLastBlockHasSize(LastBlockHasSize), mEOF(false) { // Make sure size of the IOStream::pos_type matches the pos_type used ASSERT(sizeof(pos_type) >= sizeof(off_t)); // Sanity check handles if(mStripe1Handle != -1 && mStripe2Handle != -1) { // Everything is lovely, got two perfect files } else { // Check we have at least one stripe and a parity file if((mStripe1Handle == -1 && mStripe2Handle == -1) || mParityHandle == -1) { // Should never have got this far THROW_EXCEPTION(RaidFileException, Internal) } } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::~RaidFileRead_Raid() // Purpose: Destructor // Created: 2003/07/13 // // -------------------------------------------------------------------------- RaidFileRead_Raid::~RaidFileRead_Raid() { Close(); if(mRecoveryBuffer != 0) { ::free(mRecoveryBuffer); } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::Read(const void *, int) // Purpose: Reads bytes from the file // Created: 2003/07/13 // // -------------------------------------------------------------------------- int RaidFileRead_Raid::Read(void *pBuffer, int NBytes, int Timeout) { // How many more bytes could we read? unsigned int maxRead = mFileSize - mCurrentPosition; if((unsigned int)NBytes > maxRead) { NBytes = maxRead; } // Return immediately if there's nothing to read, and set EOF if(NBytes == 0) { mEOF = true; return 0; } // Can we use the normal file reading routine? if(mStripe1Handle == -1 || mStripe2Handle == -1) { // File is damaged, try a the recovery read function return ReadRecovered(pBuffer, NBytes); } // Vectors for reading stuff from the files struct iovec stripe1Reads[READV_MAX_BLOCKS]; struct iovec stripe2Reads[READV_MAX_BLOCKS]; struct iovec *stripeReads[2] = {stripe1Reads, stripe2Reads}; unsigned int stripeReadsDataSize[2] = {0, 0}; unsigned int stripeReadsSize[2] = {0, 0}; int stripeHandles[2] = {mStripe1Handle, mStripe2Handle}; // Which block are we doing? unsigned int currentBlock = mCurrentPosition / mBlockSize; unsigned int bytesLeftInCurrentBlock = mBlockSize - (mCurrentPosition % mBlockSize); ASSERT(bytesLeftInCurrentBlock > 0) unsigned int leftToRead = NBytes; char *bufferPtr = (char*)pBuffer; // Now... add some whole block entries in... try { while(leftToRead > 0) { int whichStripe = (currentBlock & 1); size_t rlen = mBlockSize; // Adjust if it's the first block if(bytesLeftInCurrentBlock != 0) { rlen = bytesLeftInCurrentBlock; bytesLeftInCurrentBlock = 0; } // Adjust if we're out of bytes if(rlen > leftToRead) { rlen = leftToRead; } stripeReads[whichStripe][stripeReadsSize[whichStripe]].iov_base = bufferPtr; stripeReads[whichStripe][stripeReadsSize[whichStripe]].iov_len = rlen; stripeReadsSize[whichStripe]++; stripeReadsDataSize[whichStripe] += rlen; leftToRead -= rlen; bufferPtr += rlen; currentBlock++; // Read data? for(int s = 0; s < 2; ++s) { if((leftToRead == 0 || stripeReadsSize[s] >= READV_MAX_BLOCKS) && stripeReadsSize[s] > 0) { int r = ::readv(stripeHandles[s], stripeReads[s], stripeReadsSize[s]); if(r == -1) { // Bad news... IO error? if(errno == EIO) { // Attempt to recover from this failure AttemptToRecoverFromIOError((s == 0) /* is stripe 1 */); // Retry return Read(pBuffer, NBytes, Timeout); } else { // Can't do anything, throw THROW_EXCEPTION(RaidFileException, OSError) } } else if(r != (int)stripeReadsDataSize[s]) { // Got the file sizes wrong/logic error! THROW_EXCEPTION(RaidFileException, Internal) } stripeReadsSize[s] = 0; stripeReadsDataSize[s] = 0; } } } } catch(...) { // Get file pointers to right place (to meet exception safe stuff) SetPosition(mCurrentPosition); throw; } // adjust current position mCurrentPosition += NBytes; return NBytes; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::MoveDamagedFileAlertDaemon(bool) // Purpose: Moves a file into the damaged directory, and alerts the Daemon to recover it properly later. // Created: 2003/07/22 // // -------------------------------------------------------------------------- void RaidFileRead_Raid::MoveDamagedFileAlertDaemon(int SetNumber, const std::string &Filename, bool Stripe1) { // Move the dodgy file away // Get the controller and the disc set we're on RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size()) { THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) } // Start disc int startDisc = rdiscSet.GetSetNumForWriteFiles(Filename); int errOnDisc = (startDisc + (Stripe1?0:1)) % READ_NUMBER_DISCS_REQUIRED; // Make a munged filename for renaming std::string mungeFn(Filename + RAIDFILE_EXTENSION); std::string awayName; for(std::string::const_iterator i = mungeFn.begin(); i != mungeFn.end(); ++i) { char c = (*i); if(c == DIRECTORY_SEPARATOR_ASCHAR) { awayName += '_'; } else if(c == '_') { awayName += "__"; } else { awayName += c; } } // Make sure the error files directory exists std::string dirname(rdiscSet[errOnDisc] + DIRECTORY_SEPARATOR ".raidfile-unreadable"); int mdr = ::mkdir(dirname.c_str(), 0750); if(mdr != 0 && errno != EEXIST) { THROW_EXCEPTION(RaidFileException, OSError) } // Attempt to rename the file there -- ignore any return code here, as it's dubious anyway std::string errorFile(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, errOnDisc)); ::rename(errorFile.c_str(), (dirname + DIRECTORY_SEPARATOR_ASCHAR + awayName).c_str()); // TODO: Inform the recovery daemon } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::AttemptToRecoverFromIOError(bool) // Purpose: Attempt to recover from an IO error, setting up to read from parity instead. // Will exception if this isn't possible. // Created: 2003/07/14 // // -------------------------------------------------------------------------- void RaidFileRead_Raid::AttemptToRecoverFromIOError(bool Stripe1) { BOX_WARNING("Attempting to recover from I/O error: " << mSetNumber << " " << mFilename << ", on stripe " << (Stripe1?1:2)); // Close offending file if(Stripe1) { if(mStripe1Handle != -1) { ::close(mStripe1Handle); mStripe1Handle = -1; } } else { if(mStripe2Handle != -1) { ::close(mStripe2Handle); mStripe2Handle = -1; } } // Check... ASSERT((Stripe1?mStripe2Handle:mStripe1Handle) != -1); // Get rid of the damaged file MoveDamagedFileAlertDaemon(mSetNumber, mFilename, Stripe1); // Get the controller and the disc set we're on RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size()) { THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) } // Start disc int startDisc = rdiscSet.GetSetNumForWriteFiles(mFilename); // Mark as nothing in recovery buffer mRecoveryBufferStart = -1; // Seek to zero on the remaining file -- get to nice state if(::lseek(Stripe1?mStripe2Handle:mStripe1Handle, 0, SEEK_SET) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Open the parity file std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (2 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); mParityHandle = ::open(parityFilename.c_str(), O_RDONLY | O_BINARY, 0555); if(mParityHandle == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Work out whether or not there's a size XORed into the last block unsigned int bytesInLastTwoBlocks = mFileSize % (mBlockSize * 2); if(bytesInLastTwoBlocks > mBlockSize && bytesInLastTwoBlocks < ((mBlockSize * 2) - sizeof(FileSizeType))) { // Yes, there's something to XOR in the last block mLastBlockHasSize = true; } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::ReadRecovered(const void *, int) // Purpose: Reads data recreating from the parity stripe // Created: 2003/07/14 // // -------------------------------------------------------------------------- int RaidFileRead_Raid::ReadRecovered(void *pBuffer, int NBytes) { // Note: NBytes has been adjusted to definately be a range // inside the given file length. // Make sure a buffer is allocated if(mRecoveryBuffer == 0) { mRecoveryBuffer = (char*)::malloc(mBlockSize * 2); if(mRecoveryBuffer == 0) { throw std::bad_alloc(); } } // Which stripe? int stripe = (mStripe1Handle != -1)?mStripe1Handle:mStripe2Handle; if(stripe == -1) { // Not enough file handles around THROW_EXCEPTION(RaidFileException, FileIsDamagedNotRecoverable) } char *outptr = (char*)pBuffer; int bytesToGo = NBytes; pos_type preservedCurrentPosition = mCurrentPosition; try { // Start offset within buffer int offset = (mCurrentPosition - mRecoveryBufferStart); // Let's go! while(bytesToGo > 0) { int bytesLeftInBuffer = 0; if(mRecoveryBufferStart != -1) { bytesLeftInBuffer = (mRecoveryBufferStart + (mBlockSize*2)) - mCurrentPosition; ASSERT(bytesLeftInBuffer >= 0); } // How many bytes can be copied out? int toCopy = bytesLeftInBuffer; if(toCopy > bytesToGo) toCopy = bytesToGo; //printf("offset = %d, tocopy = %d, bytestogo = %d, leftinbuffer = %d\n", (int)offset, toCopy, bytesToGo, bytesLeftInBuffer); if(toCopy > 0) { for(int l = 0; l < toCopy; ++l) { *(outptr++) = mRecoveryBuffer[offset++]; } bytesToGo -= toCopy; mCurrentPosition += toCopy; } // Load in the next buffer? if(bytesToGo > 0) { // Calculate the blocks within the file that are needed to be loaded. pos_type fileBlock = mCurrentPosition / (mBlockSize * 2); // Is this the last block bool isLastBlock = (fileBlock == (mFileSize / (mBlockSize * 2))); // Need to reposition file pointers? if(mRecoveryBufferStart == -1) { // Yes! // And the offset from which to read it pos_type filePos = fileBlock * mBlockSize; // Then seek if(::lseek(stripe, filePos, SEEK_SET) == -1 || ::lseek(mParityHandle, filePos, SEEK_SET) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } } // Load a block from each file, getting the ordering the right way round int r1 = ::read((mStripe1Handle != -1)?stripe:mParityHandle, mRecoveryBuffer, mBlockSize); int r2 = ::read((mStripe1Handle != -1)?mParityHandle:stripe, mRecoveryBuffer + mBlockSize, mBlockSize); if(r1 == -1 || r2 == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // error checking and manipulation if(isLastBlock) { // Allow not full reads, and append zeros if necessary to fill the space. int r1zeros = mBlockSize - r1; if(r1zeros > 0) { ::memset(mRecoveryBuffer + r1, 0, r1zeros); } int r2zeros = mBlockSize - r2; if(r2zeros > 0) { ::memset(mRecoveryBuffer + mBlockSize + r2, 0, r2zeros); } // if it's got the file size in it, XOR it off if(mLastBlockHasSize) { int sizeXorOffset = (mBlockSize - sizeof(FileSizeType)) + ((mStripe1Handle != -1)?mBlockSize:0); *((FileSizeType*)(mRecoveryBuffer + sizeXorOffset)) ^= box_ntoh64(mFileSize); } } else { // Must have got a full block, otherwise things are a bit bad here. if(r1 != (int)mBlockSize || r2 != (int)mBlockSize) { THROW_EXCEPTION(RaidFileException, InvalidRaidFile) } } // Go XORing! unsigned int *b1 = (unsigned int*)mRecoveryBuffer; unsigned int *b2 = (unsigned int *)(mRecoveryBuffer + mBlockSize); if((mStripe1Handle == -1)) { b1 = b2; b2 = (unsigned int*)mRecoveryBuffer; } for(int x = ((mBlockSize/sizeof(unsigned int)) - 1); x >= 0; --x) { *b2 = (*b1) ^ (*b2); ++b1; ++b2; } // New block location mRecoveryBufferStart = fileBlock * (mBlockSize * 2); // New offset withing block offset = (mCurrentPosition - mRecoveryBufferStart); ASSERT(offset >= 0); } } } catch(...) { // Change variables so 1) buffer is invalidated and 2) the file will be seeked properly the next time round mRecoveryBufferStart = -1; mCurrentPosition = preservedCurrentPosition; throw; } return NBytes; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::GetPosition() // Purpose: Returns current position // Created: 2003/07/13 // // -------------------------------------------------------------------------- IOStream::pos_type RaidFileRead_Raid::GetPosition() const { return mCurrentPosition; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::Seek(RaidFileRead::pos_type, bool) // Purpose: Seek within the file // Created: 2003/07/13 // // -------------------------------------------------------------------------- void RaidFileRead_Raid::Seek(IOStream::pos_type Offset, int SeekType) { pos_type newpos = mCurrentPosition; switch(SeekType) { case IOStream::SeekType_Absolute: newpos = Offset; break; case IOStream::SeekType_Relative: newpos += Offset; break; case IOStream::SeekType_End: newpos = mFileSize + Offset; break; default: THROW_EXCEPTION(CommonException, IOStreamBadSeekType) } if(newpos != mCurrentPosition) { SetPosition(newpos); } } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::SetPosition(pos_type) // Purpose: Move the file pointers // Created: 2003/07/14 // // -------------------------------------------------------------------------- void RaidFileRead_Raid::SetPosition(pos_type FilePosition) { if(FilePosition > mFileSize) { FilePosition = mFileSize; } if(mStripe1Handle != -1 && mStripe2Handle != -1) { // right then... which block is it in? pos_type block = FilePosition / mBlockSize; pos_type offset = FilePosition % mBlockSize; // Calculate offsets for each file pos_type basepos = (block / 2) * mBlockSize; pos_type s1p, s2p; if((block & 1) == 0) { s1p = basepos + offset; s2p = basepos; } else { s1p = basepos + mBlockSize; s2p = basepos + offset; } // Note: lseek isn't in the man pages to return EIO, but assuming that it can return this, // as it calls various OS bits and returns their error codes, and those fns look like they might. if(::lseek(mStripe1Handle, s1p, SEEK_SET) == -1) { if(errno == EIO) { BOX_ERROR("I/O error when seeking in " << mSetNumber << " " << mFilename << " (to " << FilePosition << "), " << "stripe 1"); // Attempt to recover AttemptToRecoverFromIOError(true /* is stripe 1 */); ASSERT(mStripe1Handle == -1); // Retry SetPosition(FilePosition); return; } else { THROW_EXCEPTION(RaidFileException, OSError) } } if(::lseek(mStripe2Handle, s2p, SEEK_SET) == -1) { if(errno == EIO) { BOX_ERROR("I/O error when seeking in " << mSetNumber << " " << mFilename << " (to " << FilePosition << "), " << "stripe 2"); // Attempt to recover AttemptToRecoverFromIOError(false /* is stripe 2 */); ASSERT(mStripe2Handle == -1); // Retry SetPosition(FilePosition); return; } else { THROW_EXCEPTION(RaidFileException, OSError) } } // Store position mCurrentPosition = FilePosition; } else { // Simply store, and mark the recovery buffer invalid mCurrentPosition = FilePosition; mRecoveryBufferStart = -1; } // not EOF any more mEOF = false; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::Close() // Purpose: Close the file (automatically done by destructor) // Created: 2003/07/13 // // -------------------------------------------------------------------------- void RaidFileRead_Raid::Close() { if(mStripe1Handle != -1) { ::close(mStripe1Handle); mStripe1Handle = -1; } if(mStripe2Handle != -1) { ::close(mStripe2Handle); mStripe2Handle = -1; } if(mParityHandle != -1) { ::close(mParityHandle); mParityHandle = -1; } mEOF = true; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_NonRaid::StreamDataLeft() // Purpose: Any data left? // Created: 2003/08/21 // // -------------------------------------------------------------------------- bool RaidFileRead_Raid::StreamDataLeft() { return !mEOF; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead_Raid::GetFileSize() // Purpose: Returns file size. // Created: 2003/07/14 // // -------------------------------------------------------------------------- RaidFileRead::pos_type RaidFileRead_Raid::GetFileSize() const { return mFileSize; } // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::RaidFileRead(int, const std::string &) // Purpose: Constructor // Created: 2003/07/13 // // -------------------------------------------------------------------------- RaidFileRead::RaidFileRead(int SetNumber, const std::string &Filename) : mSetNumber(SetNumber), mFilename(Filename) { } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::~RaidFileRead() // Purpose: Destructor // Created: 2003/07/13 // // -------------------------------------------------------------------------- RaidFileRead::~RaidFileRead() { } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::Open(int, const std::string &, int) // Purpose: Opens a RaidFile for reading. // Created: 2003/07/13 // // -------------------------------------------------------------------------- std::auto_ptr RaidFileRead::Open(int SetNumber, const std::string &Filename, int64_t *pRevisionID, int BufferSizeHint) { // See what's available... // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size() && 1 != rdiscSet.size()) // allow non-RAID configurations { THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) } // See if the file exists int startDisc = 0, existingFiles = 0; RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, Filename, &startDisc, &existingFiles, pRevisionID); if(existance == RaidFileUtil::NoFile) { BOX_ERROR("Expected raidfile " << Filename << " does not exist"); THROW_EXCEPTION(RaidFileException, RaidFileDoesntExist) } else if(existance == RaidFileUtil::NonRaid) { // Simple non-RAID file so far... // Get the filename for the write file std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, Filename)); // Attempt to open int osFileHandle = ::open(writeFilename.c_str(), O_RDONLY | O_BINARY, 0); if(osFileHandle == -1) { THROW_EXCEPTION(RaidFileException, ErrorOpeningFileForRead) } // Return a read object for this file try { return std::auto_ptr(new RaidFileRead_NonRaid(SetNumber, Filename, osFileHandle)); } catch(...) { ::close(osFileHandle); throw; } } else if(existance == RaidFileUtil::AsRaid || ((existingFiles & RaidFileUtil::Stripe1Exists) && (existingFiles & RaidFileUtil::Stripe2Exists))) { if(existance != RaidFileUtil::AsRaid) { BOX_ERROR("Opening " << SetNumber << " " << Filename << " in normal mode, but " "parity file doesn't exist"); // TODO: Alert recovery daemon } // Open the two stripe files std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (0 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (1 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); int stripe1 = -1; int stripe1errno = 0; int stripe2 = -1; int stripe2errno = 0; try { // Open stripe1 stripe1 = ::open(stripe1Filename.c_str(), O_RDONLY | O_BINARY, 0555); if(stripe1 == -1) { stripe1errno = errno; } // Open stripe2 stripe2 = ::open(stripe2Filename.c_str(), O_RDONLY | O_BINARY, 0555); if(stripe2 == -1) { stripe2errno = errno; } if(stripe1errno != 0 || stripe2errno != 0) { THROW_EXCEPTION(RaidFileException, ErrorOpeningFileForRead) } // stat stripe 1 to find ('half' of) length... struct stat st; if(::fstat(stripe1, &st) != 0) { stripe1errno = errno; } pos_type length = st.st_size; // stat stripe2 to find (other 'half' of) length... if(::fstat(stripe2, &st) != 0) { stripe2errno = errno; } length += st.st_size; // Handle errors if(stripe1errno != 0 || stripe2errno != 0) { THROW_EXCEPTION(RaidFileException, OSError) } // Make a nice object to represent this file return std::auto_ptr(new RaidFileRead_Raid(SetNumber, Filename, stripe1, stripe2, -1, length, rdiscSet.GetBlockSize(), false /* actually we don't know */)); } catch(...) { // Close open files if(stripe1 != -1) { ::close(stripe1); stripe1 = -1; } if(stripe2 != -1) { ::close(stripe2); stripe2 = -1; } // Now... maybe we can try again with one less file? bool oktotryagain = true; if(stripe1errno == EIO) { BOX_ERROR("I/O error on opening " << SetNumber << " " << Filename << " stripe 1, trying recovery mode"); RaidFileRead_Raid::MoveDamagedFileAlertDaemon(SetNumber, Filename, true /* is stripe 1 */); existingFiles = existingFiles & ~RaidFileUtil::Stripe1Exists; existance = (existance == RaidFileUtil::AsRaidWithMissingReadable) ?RaidFileUtil::AsRaidWithMissingNotRecoverable :RaidFileUtil::AsRaidWithMissingReadable; } else if(stripe1errno != 0) { oktotryagain = false; } if(stripe2errno == EIO) { BOX_ERROR("I/O error on opening " << SetNumber << " " << Filename << " stripe 2, trying recovery mode"); RaidFileRead_Raid::MoveDamagedFileAlertDaemon(SetNumber, Filename, false /* is stripe 2 */); existingFiles = existingFiles & ~RaidFileUtil::Stripe2Exists; existance = (existance == RaidFileUtil::AsRaidWithMissingReadable) ?RaidFileUtil::AsRaidWithMissingNotRecoverable :RaidFileUtil::AsRaidWithMissingReadable; } else if(stripe2errno != 0) { oktotryagain = false; } if(!oktotryagain) { throw; } } } if(existance == RaidFileUtil::AsRaidWithMissingReadable) { BOX_ERROR("Attempting to open RAID file " << SetNumber << " " << Filename << " in recovery mode (stripe " << ((existingFiles & RaidFileUtil::Stripe1Exists)?1:2) << " present)"); // Generate the filenames of all the lovely files std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (0 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (1 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (2 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); int stripe1 = -1; int stripe2 = -1; int parity = -1; try { // Open stripe1? if(existingFiles & RaidFileUtil::Stripe1Exists) { stripe1 = ::open(stripe1Filename.c_str(), O_RDONLY | O_BINARY, 0555); if(stripe1 == -1) { THROW_EXCEPTION(RaidFileException, OSError) } } // Open stripe2? if(existingFiles & RaidFileUtil::Stripe2Exists) { stripe2 = ::open(stripe2Filename.c_str(), O_RDONLY | O_BINARY, 0555); if(stripe2 == -1) { THROW_EXCEPTION(RaidFileException, OSError) } } // Open parity parity = ::open(parityFilename.c_str(), O_RDONLY | O_BINARY, 0555); if(parity == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Find the length. This is slightly complex. unsigned int blockSize = rdiscSet.GetBlockSize(); pos_type length = 0; // The easy one... if the parity file is of an integral block size + sizeof(FileSizeType) // then it's stored at the end of the parity file struct stat st; if(::fstat(parity, &st) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } pos_type paritySize = st.st_size; FileSizeType parityLastData = 0; bool parityIntegralPlusOffT = ((paritySize % blockSize) == sizeof(FileSizeType)); if(paritySize >= static_cast(sizeof(parityLastData)) && (parityIntegralPlusOffT || stripe1 != -1)) { // Seek to near the end ASSERT(sizeof(FileSizeType) == 8); // compiler bug (I think) prevents from using 0 - sizeof(FileSizeType)... if(::lseek(parity, -8 /*(0 - sizeof(FileSizeType))*/, SEEK_END) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Read it in if(::read(parity, &parityLastData, sizeof(parityLastData)) != sizeof(parityLastData)) { THROW_EXCEPTION(RaidFileException, OSError) } // Set back to beginning of file if(::lseek(parity, 0, SEEK_SET) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } } bool lastBlockHasSize = false; if(parityIntegralPlusOffT) { // Wonderful! Have the value length = box_ntoh64(parityLastData); } else { // Have to resort to more devious means. if(existingFiles & RaidFileUtil::Stripe1Exists) { // Procedure for stripe 1 existence... // Get size of stripe1. // If this is not an integral block size, then size can use this // to work out the size of the file. // Otherwise, read in the end of the last block, and use a bit of XORing // to get the size from the FileSizeType value at end of the file. if(::fstat(stripe1, &st) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } pos_type stripe1Size = st.st_size; // Is size integral? if((stripe1Size % ((pos_type)blockSize)) != 0) { // No, so know the size. length = stripe1Size + ((stripe1Size / blockSize) * blockSize); } else { // Must read the last bit of data from the block and XOR. FileSizeType stripe1LastData = 0; // initialise to zero, as we may not read everything from it // Work out how many bytes to read int btr = 0; // bytes to read off end unsigned int lbs = stripe1Size % blockSize; if(lbs == 0 && stripe1Size > 0) { // integral size, need the entire bit btr = sizeof(FileSizeType); } else if(lbs > (blockSize - sizeof(FileSizeType))) { btr = lbs - (blockSize - sizeof(FileSizeType)); } // Seek to near the end if(btr > 0) { ASSERT(sizeof(FileSizeType) == 8); // compiler bug (I think) prevents from using 0 - sizeof(FileSizeType)... ASSERT(btr <= (int)sizeof(FileSizeType)); if(::lseek(stripe1, 0 - btr, SEEK_END) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } // Read it in if(::read(stripe1, &stripe1LastData, btr) != btr) { THROW_EXCEPTION(RaidFileException, OSError) } // Set back to beginning of file if(::lseek(stripe1, 0, SEEK_SET) == -1) { THROW_EXCEPTION(RaidFileException, OSError) } } // Lovely! length = stripe1LastData ^ parityLastData; // Convert to host byte order length = box_ntoh64(length); ASSERT(length <= (paritySize + stripe1Size)); // Mark is as having this to aid code later lastBlockHasSize = true; } } else { ASSERT(existingFiles & RaidFileUtil::Stripe2Exists); } if(existingFiles & RaidFileUtil::Stripe2Exists) { // Get size of stripe2 file if(::fstat(stripe2, &st) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } pos_type stripe2Size = st.st_size; // Is it an integral size? if(stripe2Size % blockSize != 0) { // No. Working out the size is easy. length = stripe2Size + (((stripe2Size / blockSize)+1) * blockSize); // Got last block size in there? if((stripe2Size % blockSize) <= static_cast((blockSize - sizeof(pos_type)))) { // Yes... lastBlockHasSize = true; } } else { // Yes. So we need to compare with the parity file to get a clue... pos_type stripe2Blocks = stripe2Size / blockSize; pos_type parityBlocks = paritySize / blockSize; if(stripe2Blocks == parityBlocks) { // Same size, so stripe1 must be the same size length = (stripe2Blocks * 2) * blockSize; } else { // Different size, so stripe1 must be one block bigger ASSERT(stripe2Blocks < parityBlocks); length = ((stripe2Blocks * 2)+1) * blockSize; } // Then... add in the extra bit of the parity length unsigned int lastBlockSize = paritySize % blockSize; length += lastBlockSize; } } else { ASSERT(existingFiles & RaidFileUtil::Stripe1Exists); } } // Create a lovely object to return return std::auto_ptr(new RaidFileRead_Raid(SetNumber, Filename, stripe1, stripe2, parity, length, blockSize, lastBlockHasSize)); } catch(...) { // Close open files if(stripe1 != -1) { ::close(stripe1); stripe1 = -1; } if(stripe2 != -1) { ::close(stripe2); stripe2 = -1; } if(parity != -1) { ::close(parity); parity = -1; } throw; } } THROW_EXCEPTION(RaidFileException, FileIsDamagedNotRecoverable) // Avoid compiler warning -- it'll never get here... return std::auto_ptr(); } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::DirectoryExists(int, const std::string &) // Purpose: Returns true if the directory exists. Throws exception if it's partially in existence. // Created: 2003/08/20 // // -------------------------------------------------------------------------- bool RaidFileRead::DirectoryExists(int SetNumber, const std::string &rDirName) { // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); return DirectoryExists(rdiscSet, rDirName); } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::DirectoryExists(const RaidFileDiscSet &, const std::string &) // Purpose: Returns true if the directory exists. Throws exception if it's partially in existence. // Created: 2003/08/20 // // -------------------------------------------------------------------------- bool RaidFileRead::DirectoryExists(const RaidFileDiscSet &rSet, const std::string &rDirName) { // For each directory, test to see if it exists unsigned int nexist = 0; for(unsigned int l = 0; l < rSet.size(); ++l) { // build name std::string dn(rSet[l] + DIRECTORY_SEPARATOR + rDirName); // check for existence struct stat st; if(::stat(dn.c_str(), &st) == 0) { // Directory? if(st.st_mode & S_IFDIR) { // yes nexist++; } else { // No. It's a file. Bad! THROW_EXCEPTION(RaidFileException, UnexpectedFileInDirPlace) } } else { // Was it a non-exist error? if(errno != ENOENT) { // No. Bad things. THROW_EXCEPTION(RaidFileException, OSError) } } } // Were all of them found? if(nexist == 0) { // None. return false; } else if(nexist == rSet.size()) { // All return true; } // Some exist. We don't like this -- it shows something bad happened before // TODO: notify recovery daemon THROW_EXCEPTION(RaidFileException, DirectoryIncomplete) return false; // avoid compiler warning } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::FileExists(int, const std::string &, int64_t *) // Purpose: Does a Raid file exist? Optionally return a revision number, which is // unique to this saving of the file. (revision number may change // after transformation to RAID -- so only use for cache control, // not detecting changes to content). // Created: 2003/09/02 // // -------------------------------------------------------------------------- bool RaidFileRead::FileExists(int SetNumber, const std::string &rFilename, int64_t *pRevisionID) { // Get disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); return RaidFileUtil::RaidFileExists(rdiscSet, rFilename, 0, 0, pRevisionID) != RaidFileUtil::NoFile; } // -------------------------------------------------------------------------- // // Function // Name: ReadDirectoryContents(int, const std::string &, int, std::vector &) // Purpose: Read directory contents, returning whether or not all entries are likely to be readable or not // Created: 2003/08/20 // // -------------------------------------------------------------------------- bool RaidFileRead::ReadDirectoryContents(int SetNumber, const std::string &rDirName, int DirReadType, std::vector &rOutput) { // Remove anything in the vector to begin with. rOutput.clear(); // Controller and set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); // Collect the directory listings std::map counts; unsigned int numDiscs = rdiscSet.size(); for(unsigned int l = 0; l < numDiscs; ++l) { // build name std::string dn(rdiscSet[l] + DIRECTORY_SEPARATOR + rDirName); // read the contents... DIR *dirHandle = 0; try { dirHandle = ::opendir(dn.c_str()); if(dirHandle == 0) { THROW_EXCEPTION(RaidFileException, OSError) } struct dirent *en = 0; while((en = ::readdir(dirHandle)) != 0) { if(en->d_name[0] == '.' && (en->d_name[1] == '\0' || (en->d_name[1] == '.' && en->d_name[2] == '\0'))) { // ignore, it's . or .. continue; } // Entry... std::string name; unsigned int countToAdd = 1; // stat the file to find out what type it is #ifdef HAVE_VALID_DIRENT_D_TYPE if(DirReadType == DirReadType_FilesOnly && en->d_type == DT_REG) #else struct stat st; std::string fullName(dn + DIRECTORY_SEPARATOR + en->d_name); if(::lstat(fullName.c_str(), &st) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } if(DirReadType == DirReadType_FilesOnly && (st.st_mode & S_IFDIR) == 0) #endif { // File. Complex, need to check the extension int dot = -1; int p = 0; while(en->d_name[p] != '\0') { if(en->d_name[p] == '.') { // store location of dot dot = p; } ++p; } // p is length of string if(dot != -1 && ((p - dot) == 3 || (p - dot) == 4) && en->d_name[dot+1] == 'r' && en->d_name[dot+2] == 'f' && (en->d_name[dot+3] == 'w' || en->d_name[dot+3] == '\0')) { // so has right extension name.assign(en->d_name, dot); /* get name up to last . */ // Was it a write file (which counts as everything) if(en->d_name[dot+3] == 'w') { countToAdd = numDiscs; } } } #ifdef HAVE_VALID_DIRENT_D_TYPE if(DirReadType == DirReadType_DirsOnly && en->d_type == DT_DIR) #else if(DirReadType == DirReadType_DirsOnly && (st.st_mode & S_IFDIR)) #endif { // Directory, and we want directories name = en->d_name; } // Eligable for entry? if(!name.empty()) { // add to map... std::map::iterator i = counts.find(name); if(i != counts.end()) { // add to count i->second += countToAdd; } else { // insert into map counts[name] = countToAdd; } } } if(::closedir(dirHandle) != 0) { THROW_EXCEPTION(RaidFileException, OSError) } dirHandle = 0; } catch(...) { if(dirHandle != 0) { ::closedir(dirHandle); } throw; } } // Now go through the map, adding in entries bool everythingReadable = true; for(std::map::const_iterator i = counts.begin(); i != counts.end(); ++i) { if(i->second < (numDiscs - 1)) { // Too few discs to be confident of reading everything everythingReadable = false; } // Add name to vector rOutput.push_back(i->first); } return everythingReadable; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::Write(const void *, int) // Purpose: Not support, throws exception // Created: 2003/08/21 // // -------------------------------------------------------------------------- void RaidFileRead::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(RaidFileException, UnsupportedReadWriteOrClose) } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::StreamClosed() // Purpose: Never any data to write // Created: 2003/08/21 // // -------------------------------------------------------------------------- bool RaidFileRead::StreamClosed() { return true; } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::BytesLeftToRead() // Purpose: Can tell how many bytes there are to go // Created: 2003/08/26 // // -------------------------------------------------------------------------- IOStream::pos_type RaidFileRead::BytesLeftToRead() { return GetFileSize() - GetPosition(); } // -------------------------------------------------------------------------- // // Function // Name: RaidFileRead::GetDiscUsageInBlocks() // Purpose: Return how many blocks are used. // Created: 2003/09/03 // // -------------------------------------------------------------------------- IOStream::pos_type RaidFileRead::GetDiscUsageInBlocks() { RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); return RaidFileUtil::DiscUsageInBlocks(GetFileSize(), rdiscSet); } boxbackup/lib/raidfile/RaidFileUtil.h0000664000175000017500000000546511173734217020345 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileUtil.h // Purpose: Utilities for the raid file classes // Created: 2003/07/11 // // -------------------------------------------------------------------------- #ifndef RAIDFILEUTIL__H #define RAIDFILEUTIL__H #include "RaidFileController.h" #include "RaidFileException.h" // note: these are hardcoded into the directory searching code #define RAIDFILE_EXTENSION ".rf" #define RAIDFILE_WRITE_EXTENSION ".rfw" // -------------------------------------------------------------------------- // // Class // Name: RaidFileUtil // Purpose: Utility functions for RaidFile classes // Created: 2003/07/11 // // -------------------------------------------------------------------------- class RaidFileUtil { public: typedef enum { NoFile = 0, NonRaid = 1, AsRaid = 2, AsRaidWithMissingReadable = 3, AsRaidWithMissingNotRecoverable = 4 } ExistType; enum { Stripe1Exists = 1, Stripe2Exists = 2, ParityExists = 4 }; static ExistType RaidFileExists(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int *pStartDisc = 0, int *pExisitingFiles = 0, int64_t *pRevisionID = 0); static int64_t DiscUsageInBlocks(int64_t FileSize, const RaidFileDiscSet &rDiscSet); // -------------------------------------------------------------------------- // // Function // Name: std::string MakeRaidComponentName(RaidFileDiscSet &, const std::string &, int) // Purpose: Returns the OS filename for a file of part of a disc set // Created: 2003/07/11 // // -------------------------------------------------------------------------- static inline std::string MakeRaidComponentName(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int Disc) { if(Disc < 0 || Disc >= (int)rDiscSet.size()) { THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) } std::string r(rDiscSet[Disc]); r += DIRECTORY_SEPARATOR_ASCHAR; r += rFilename; r += RAIDFILE_EXTENSION; return r; } // -------------------------------------------------------------------------- // // Function // Name: std::string MakeWriteFileName(RaidFileDiscSet &, const std::string &) // Purpose: Returns the OS filename for the temporary write file // Created: 2003/07/11 // // -------------------------------------------------------------------------- static inline std::string MakeWriteFileName(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int *pOnDiscSet = 0) { int livesOnSet = rDiscSet.GetSetNumForWriteFiles(rFilename); // does the caller want to know which set it's on? if(pOnDiscSet) *pOnDiscSet = livesOnSet; // Make the string std::string r(rDiscSet[livesOnSet]); r += DIRECTORY_SEPARATOR_ASCHAR; r += rFilename; r += RAIDFILE_WRITE_EXTENSION; return r; } }; #endif // RAIDFILEUTIL__H boxbackup/lib/raidfile/RaidFileException.h0000664000175000017500000000061710347400657021360 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileException.h // Purpose: Exception // Created: 2003/07/08 // // -------------------------------------------------------------------------- #ifndef RAIDFILEEXCEPTION__H #define RAIDFILEEXCEPTION__H // Compatibility #include "autogen_RaidFileException.h" #endif // RAIDFILEEXCEPTION__H boxbackup/lib/raidfile/raidfile-config.in0000775000175000017500000000365011167477470021236 0ustar siretartsiretart#!@PERL@ use strict; # should be running as root if($> != 0) { printf "\nWARNING: this should be run as root\n\n" } # check and get command line parameters if($#ARGV != 4 && $#ARGV != 2) { print <<__E; Setup raidfile config utility. Bad command line parameters. Usage: raidfile-config config-dir block-size dir0 [dir1 dir2] Parameters: config-dir is usually @sysconfdir_expanded@/boxbackup block-size must be a power of two, and usually the block or fragment size of your file system dir0, dir1, dir2 are the directories used as the root of the raid file system If only one directory is specified, then userland RAID is disabled. Specifying three directories enables it. __E exit(1); } my ($config_dir,$block_size,@dirs) = @ARGV; my $conf = $config_dir . '/raidfile.conf'; # check dirs are unique, and exist my %d; for(@dirs) { die "$_ is used twice" if exists $d{$_}; die "$_ is not a directory" unless -d $_; die "$_ should be an absolute path" unless m/\A\//; $d{$_} = 1; } # check block size is OK $block_size = int($block_size); die "Bad block size" if $block_size <= 0; my $c = 1; while(1) { last if $c == $block_size; die "Block size $block_size is not a power of two" if $c > $block_size; $c = $c * 2; } # check that it doesn't already exist if(-f $conf) { die "$conf already exists. Delete and try again"; } # create directory if(!-d $config_dir) { print "Creating $config_dir...\n"; mkdir $config_dir,0755 or die "Can't create $config_dir"; } # adjust if userland RAID is disabled if($#dirs == 0) { $dirs[1] = $dirs[0]; $dirs[2] = $dirs[0]; print "WARNING: userland RAID is disabled.\n"; } # write the file open CONFIG,">$conf" or die "Can't open $conf for writing"; print CONFIG <<__E; disc0 { SetNumber = 0 BlockSize = $block_size Dir0 = $dirs[0] Dir1 = $dirs[1] Dir2 = $dirs[2] } __E close CONFIG; print "Config file written.\n"; boxbackup/lib/raidfile/Makefile.extra0000664000175000017500000000033211345266370020426 0ustar siretartsiretart MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_RaidFileException.h autogen_RaidFileException.cpp: $(MAKEEXCEPTION) RaidFileException.txt $(_PERL) $(MAKEEXCEPTION) RaidFileException.txt boxbackup/lib/raidfile/RaidFileWrite.h0000664000175000017500000000356411224217235020511 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileWrite.h // Purpose: Writing RAID like files // Created: 2003/07/10 // // -------------------------------------------------------------------------- #ifndef RAIDFILEWRITE__H #define RAIDFILEWRITE__H #include #include "IOStream.h" class RaidFileDiscSet; // -------------------------------------------------------------------------- // // Class // Name: RaidFileWrite // Purpose: Writing RAID like files // Created: 2003/07/10 // // -------------------------------------------------------------------------- class RaidFileWrite : public IOStream { public: RaidFileWrite(int SetNumber, const std::string &Filename); RaidFileWrite(int SetNumber, const std::string &Filename, int refcount); ~RaidFileWrite(); private: RaidFileWrite(const RaidFileWrite &rToCopy); public: // IOStream interface virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); // will exception virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(pos_type Offset, int SeekType); virtual void Close(); // will discard the file! Use commit instead. virtual bool StreamDataLeft(); virtual bool StreamClosed(); // Extra bits void Open(bool AllowOverwrite = false); void Commit(bool ConvertToRaidNow = false); void Discard(); void TransformToRaidStorage(); void Delete(); pos_type GetFileSize(); pos_type GetDiscUsageInBlocks(); static void CreateDirectory(int SetNumber, const std::string &rDirName, bool Recursive = false, int mode = 0777); static void CreateDirectory(const RaidFileDiscSet &rSet, const std::string &rDirName, bool Recursive = false, int mode = 0777); private: private: int mSetNumber; std::string mFilename; int mOSFileHandle; int mRefCount; }; #endif // RAIDFILEWRITE__H boxbackup/lib/raidfile/RaidFileUtil.cpp0000664000175000017500000001237611161233163020666 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RaidFileUtil.cpp // Purpose: Utilities for raid files // Created: 2003/07/11 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "RaidFileUtil.h" #include "FileModificationTime.h" #include "RaidFileRead.h" // for type definition #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: RaidFileUtil::RaidFileExists(RaidFileDiscSet &, // const std::string &, int *, int *, int64_t *) // Purpose: Check to see the state of a RaidFile on disc // (doesn't look at contents, just at existence of // files) // Created: 2003/07/11 // // -------------------------------------------------------------------------- RaidFileUtil::ExistType RaidFileUtil::RaidFileExists(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int *pStartDisc, int *pExistingFiles, int64_t *pRevisionID) { if(pExistingFiles) { *pExistingFiles = 0; } // For stat call, although the results are not examined struct stat st; // check various files int startDisc = 0; { std::string writeFile(RaidFileUtil::MakeWriteFileName(rDiscSet, rFilename, &startDisc)); if(pStartDisc) { *pStartDisc = startDisc; } if(::stat(writeFile.c_str(), &st) == 0) { // write file exists, use that // Get unique ID if(pRevisionID != 0) { #ifdef WIN32 *pRevisionID = st.st_mtime; #else *pRevisionID = FileModificationTime(st); #endif #ifdef BOX_RELEASE_BUILD // The resolution of timestamps may be very // low, e.g. 1 second. So add the size to it // to give a bit more chance of it changing. // TODO: Make this better. // Disabled in debug mode, to simulate // filesystem with 1-second timestamp // resolution, e.g. MacOS X HFS, Linux. (*pRevisionID) += st.st_size; #endif } // return non-raid file return NonRaid; } } // Now see how many of the raid components exist int64_t revisionID = 0; int setSize = rDiscSet.size(); int rfCount = 0; // TODO: replace this with better linux revision ID detection int64_t revisionIDplus = 0; for(int f = 0; f < setSize; ++f) { std::string componentFile(RaidFileUtil::MakeRaidComponentName(rDiscSet, rFilename, (f + startDisc) % setSize)); if(::stat(componentFile.c_str(), &st) == 0) { // Component file exists, add to count rfCount++; // Set flags for existance? if(pExistingFiles) { (*pExistingFiles) |= (1 << f); } // Revision ID if(pRevisionID != 0) { #ifdef WIN32 int64_t rid = st.st_mtime; #else int64_t rid = FileModificationTime(st); #endif if(rid > revisionID) revisionID = rid; revisionIDplus += st.st_size; } } } if(pRevisionID != 0) { (*pRevisionID) = revisionID; #ifdef BOX_RELEASE_BUILD // The resolution of timestamps may be very low, e.g. // 1 second. So add the size to it to give a bit more // chance of it changing. // TODO: Make this better. // Disabled in debug mode, to simulate filesystem with // 1-second timestamp resolution, e.g. MacOS X HFS, Linux. (*pRevisionID) += revisionIDplus; #endif } // Return a status based on how many parts are available if(rfCount == setSize) { return AsRaid; } else if((setSize > 1) && rfCount == (setSize - 1)) { return AsRaidWithMissingReadable; } else if(rfCount > 0) { return AsRaidWithMissingNotRecoverable; } return NoFile; // Obviously doesn't exist } // -------------------------------------------------------------------------- // // Function // Name: RaidFileUtil::DiscUsageInBlocks(int64_t, const RaidFileDiscSet &) // Purpose: Returns the size of the file in blocks, given the file size and disc set // Created: 2003/09/03 // // -------------------------------------------------------------------------- int64_t RaidFileUtil::DiscUsageInBlocks(int64_t FileSize, const RaidFileDiscSet &rDiscSet) { // Get block size int blockSize = rDiscSet.GetBlockSize(); // OK... so as the size of the file is always sizes of stripe1 + stripe2, we can // do a very simple calculation for the main data. int64_t blocks = (FileSize + (((int64_t)blockSize) - 1)) / ((int64_t)blockSize); // It's just that simple calculation for non-RAID disc sets if(rDiscSet.IsNonRaidSet()) { return blocks; } // It's the parity which is mildly complex. // First of all, add in size for all but the last two blocks. int64_t parityblocks = (FileSize / ((int64_t)blockSize)) / 2; blocks += parityblocks; // Work out how many bytes are left int bytesOver = (int)(FileSize - (parityblocks * ((int64_t)(blockSize*2)))); // Then... (let compiler optimise this out) if(bytesOver == 0) { // Extra block for the size info blocks++; } else if(bytesOver == sizeof(RaidFileRead::FileSizeType)) { // For last block of parity, plus the size info blocks += 2; } else if(bytesOver < blockSize) { // Just want the parity block blocks += 1; } else if(bytesOver == blockSize || bytesOver >= ((blockSize*2)-((int)sizeof(RaidFileRead::FileSizeType)))) { // Last block, plus size info blocks += 2; } else { // Just want parity block blocks += 1; } return blocks; } boxbackup/lib/raidfile/RaidFileException.txt0000664000175000017500000000260211224217235021735 0ustar siretartsiretartEXCEPTION RaidFile 2 Internal 0 CantOpenConfigFile 1 The raidfile.conf file is not accessible. Check that it is present in the default location or daemon configuration files point to the correct location. BadConfigFile 2 NoSuchDiscSet 3 CannotOverwriteExistingFile 4 AlreadyOpen 5 ErrorOpeningWriteFile 6 NotOpen 7 OSError 8 Error when accessing an underlying file. Check file permissions allow files to be read and written in the configured raid directories. WriteFileOpenOnTransform 9 WrongNumberOfDiscsInSet 10 There should be three directories in each disc set. RaidFileDoesntExist 11 Error when accessing a file on the store. Check the store with bbstoreaccounts check. ErrorOpeningFileForRead 12 FileIsDamagedNotRecoverable 13 InvalidRaidFile 14 DirectoryIncomplete 15 UnexpectedFileInDirPlace 16 FileExistsInDirectoryCreation 17 UnsupportedReadWriteOrClose 18 CanOnlyGetUsageBeforeCommit 19 CanOnlyGetFileSizeBeforeCommit 20 ErrorOpeningWriteFileOnTruncate 21 FileIsCurrentlyOpenForWriting 22 RequestedModifyUnreferencedFile 23 Internal error: the server attempted to modify a file which has no references. RequestedModifyMultiplyReferencedFile 24 Internal error: the server attempted to modify a file which has multiple references. RequestedDeleteReferencedFile 25 Internal error: the server attempted to delete a file which is still referenced. boxbackup/lib/backupstore/0000775000175000017500000000000011652362374016413 5ustar siretartsiretartboxbackup/lib/backupstore/BackupStoreCheck.cpp0000664000175000017500000005170010775523641022303 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreCheck.cpp // Purpose: Check a store for consistency // Created: 21/4/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include "BackupStoreCheck.h" #include "StoreStructure.h" #include "RaidFileRead.h" #include "RaidFileWrite.h" #include "autogen_BackupStoreException.h" #include "BackupStoreObjectMagic.h" #include "BackupStoreFile.h" #include "BackupStoreDirectory.h" #include "BackupStoreConstants.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::BackupStoreCheck(const std::string &, int, int32_t, bool, bool) // Purpose: Constructor // Created: 21/4/04 // // -------------------------------------------------------------------------- BackupStoreCheck::BackupStoreCheck(const std::string &rStoreRoot, int DiscSetNumber, int32_t AccountID, bool FixErrors, bool Quiet) : mStoreRoot(rStoreRoot), mDiscSetNumber(DiscSetNumber), mAccountID(AccountID), mFixErrors(FixErrors), mQuiet(Quiet), mNumberErrorsFound(0), mLastIDInInfo(0), mpInfoLastBlock(0), mInfoLastBlockEntries(0), mLostDirNameSerial(0), mLostAndFoundDirectoryID(0), mBlocksUsed(0), mBlocksInOldFiles(0), mBlocksInDeletedFiles(0), mBlocksInDirectories(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::~BackupStoreCheck() // Purpose: Destructor // Created: 21/4/04 // // -------------------------------------------------------------------------- BackupStoreCheck::~BackupStoreCheck() { // Clean up FreeInfo(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::Check() // Purpose: Perform the check on the given account // Created: 21/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::Check() { // Lock the account { std::string writeLockFilename; StoreStructure::MakeWriteLockFilename(mStoreRoot, mDiscSetNumber, writeLockFilename); bool gotLock = false; int triesLeft = 8; do { gotLock = mAccountLock.TryAndGetLock(writeLockFilename.c_str(), 0600 /* restrictive file permissions */); if(!gotLock) { --triesLeft; ::sleep(1); } } while(!gotLock && triesLeft > 0); if(!gotLock) { // Couldn't lock the account -- just stop now if(!mQuiet) { BOX_ERROR("Failed to lock the account -- did not check.\nTry again later after the client has disconnected.\nAlternatively, forcibly kill the server."); } THROW_EXCEPTION(BackupStoreException, CouldNotLockStoreAccount) } } if(!mQuiet && mFixErrors) { BOX_NOTICE("Will fix errors encountered during checking."); } // Phase 1, check objects if(!mQuiet) { BOX_INFO("Checking store account ID " << BOX_FORMAT_ACCOUNT(mAccountID) << "..."); BOX_INFO("Phase 1, check objects..."); } CheckObjects(); // Phase 2, check directories if(!mQuiet) { BOX_INFO("Phase 2, check directories..."); } CheckDirectories(); // Phase 3, check root if(!mQuiet) { BOX_INFO("Phase 3, check root..."); } CheckRoot(); // Phase 4, check unattached objects if(!mQuiet) { BOX_INFO("Phase 4, fix unattached objects..."); } CheckUnattachedObjects(); // Phase 5, fix bad info if(!mQuiet) { BOX_INFO("Phase 5, fix unrecovered inconsistencies..."); } FixDirsWithWrongContainerID(); FixDirsWithLostDirs(); // Phase 6, regenerate store info if(!mQuiet) { BOX_INFO("Phase 6, regenerate store info..."); } WriteNewStoreInfo(); // DUMP_OBJECT_INFO if(mNumberErrorsFound > 0) { BOX_WARNING("Finished checking store account ID " << BOX_FORMAT_ACCOUNT(mAccountID) << ": " << mNumberErrorsFound << " errors found"); if(!mFixErrors) { BOX_WARNING("No changes to the store account " "have been made."); } if(!mFixErrors && mNumberErrorsFound > 0) { BOX_WARNING("Run again with fix option to " "fix these errors"); } if(mFixErrors && mNumberErrorsFound > 0) { BOX_WARNING("You should now use bbackupquery " "on the client machine to examine the store."); if(mLostAndFoundDirectoryID != 0) { BOX_WARNING("A lost+found directory was " "created in the account root.\n" "This contains files and directories " "which could not be matched to " "existing directories.\n"\ "bbackupd will delete this directory " "in a few days time."); } } } else { BOX_NOTICE("Finished checking store account ID " << BOX_FORMAT_ACCOUNT(mAccountID) << ": " "no errors found"); } } // -------------------------------------------------------------------------- // // Function // Name: static TwoDigitHexToInt(const char *, int &) // Purpose: Convert a two digit hex string to an int, returning whether it's valid or not // Created: 21/4/04 // // -------------------------------------------------------------------------- static inline bool TwoDigitHexToInt(const char *String, int &rNumberOut) { int n = 0; // Char 0 if(String[0] >= '0' && String[0] <= '9') { n = (String[0] - '0') << 4; } else if(String[0] >= 'a' && String[0] <= 'f') { n = ((String[0] - 'a') + 0xa) << 4; } else { return false; } // Char 1 if(String[1] >= '0' && String[1] <= '9') { n |= String[1] - '0'; } else if(String[1] >= 'a' && String[1] <= 'f') { n |= (String[1] - 'a') + 0xa; } else { return false; } // Return a valid number rNumberOut = n; return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CheckObjects() // Purpose: Read in the contents of the directory, recurse to other levels, // checking objects for sanity and readability // Created: 21/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::CheckObjects() { // Maximum start ID of directories -- worked out by looking at disc contents, not trusting anything int64_t maxDir = 0; // Find the maximum directory starting ID { // Make sure the starting root dir doesn't end with '/'. std::string start(mStoreRoot); if(start.size() > 0 && start[start.size() - 1] == '/') { start.resize(start.size() - 1); } maxDir = CheckObjectsScanDir(0, 1, mStoreRoot); BOX_TRACE("Max dir starting ID is " << BOX_FORMAT_OBJECTID(maxDir)); } // Then go through and scan all the objects within those directories for(int64_t d = 0; d <= maxDir; d += (1< dirs; RaidFileRead::ReadDirectoryContents(mDiscSetNumber, rDirName, RaidFileRead::DirReadType_DirsOnly, dirs); for(std::vector::const_iterator i(dirs.begin()); i != dirs.end(); ++i) { // Check to see if it's the right name int n = 0; if((*i).size() == 2 && TwoDigitHexToInt((*i).c_str(), n) && n < (1< maxID) { maxID = mi; } } else { BOX_WARNING("Spurious or invalid directory " << rDirName << DIRECTORY_SEPARATOR << (*i) << " found, " << (mFixErrors?"deleting":"delete manually")); ++mNumberErrorsFound; } } } return maxID; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CheckObjectsDir(int64_t) // Purpose: Check all the files within this directory which has the given starting ID. // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::CheckObjectsDir(int64_t StartID) { // Make directory name -- first generate the filename of an entry in it std::string dirName; StoreStructure::MakeObjectFilename(StartID, mStoreRoot, mDiscSetNumber, dirName, false /* don't make sure the dir exists */); // Check expectations ASSERT(dirName.size() > 4 && dirName[dirName.size() - 4] == DIRECTORY_SEPARATOR_ASCHAR); // Remove the filename from it dirName.resize(dirName.size() - 4); // four chars for "/o00" // Check directory exists if(!RaidFileRead::DirectoryExists(mDiscSetNumber, dirName)) { BOX_WARNING("RaidFile dir " << dirName << " does not exist"); return; } // Read directory contents std::vector files; RaidFileRead::ReadDirectoryContents(mDiscSetNumber, dirName, RaidFileRead::DirReadType_FilesOnly, files); // Array of things present bool idsPresent[(1<::const_iterator i(files.begin()); i != files.end(); ++i) { bool fileOK = true; int n = 0; if((*i).size() == 3 && (*i)[0] == 'o' && TwoDigitHexToInt((*i).c_str() + 1, n) && n < (1< file(RaidFileRead::Open(mDiscSetNumber, rFilename)); size = file->GetDiscUsageInBlocks(); // Read in first four bytes -- don't have to worry about retrying if not all bytes read as is RaidFile uint32_t signature; if(file->Read(&signature, sizeof(signature)) != sizeof(signature)) { // Too short, can't read signature from it return false; } // Seek back to beginning file->Seek(0, IOStream::SeekType_Absolute); // Then... check depending on the type switch(ntohl(signature)) { case OBJECTMAGIC_FILE_MAGIC_VALUE_V1: #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE case OBJECTMAGIC_FILE_MAGIC_VALUE_V0: #endif // File... check containerID = CheckFile(ObjectID, *file); break; case OBJECTMAGIC_DIR_MAGIC_VALUE: isFile = false; containerID = CheckDirInitial(ObjectID, *file); break; default: // Unknown signature. Bad file. Very bad file. return false; break; } // Add to usage counts mBlocksUsed += size; if(!isFile) { mBlocksInDirectories += size; } } catch(...) { // Error caught, not a good file then, let it be deleted return false; } // Got a container ID? (ie check was successful) if(containerID == -1) { return false; } // Add to list of IDs known about AddID(ObjectID, containerID, size, isFile); // Report success return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CheckFile(int64_t, IOStream &) // Purpose: Do check on file, return original container ID if OK, or -1 on error // Created: 22/4/04 // // -------------------------------------------------------------------------- int64_t BackupStoreCheck::CheckFile(int64_t ObjectID, IOStream &rStream) { // Check that it's not the root directory ID. Having a file as the root directory would be bad. if(ObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID) { // Get that dodgy thing deleted! BOX_ERROR("Have file as root directory. This is bad."); return -1; } // Check the format of the file, and obtain the container ID int64_t originalContainerID = -1; if(!BackupStoreFile::VerifyEncodedFileFormat(rStream, 0 /* don't want diffing from ID */, &originalContainerID)) { // Didn't verify return -1; } return originalContainerID; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CheckDirInitial(int64_t, IOStream &) // Purpose: Do initial check on directory, return container ID if OK, or -1 on error // Created: 22/4/04 // // -------------------------------------------------------------------------- int64_t BackupStoreCheck::CheckDirInitial(int64_t ObjectID, IOStream &rStream) { // Simply attempt to read in the directory BackupStoreDirectory dir; dir.ReadFromStream(rStream, IOStream::TimeOutInfinite); // Check object ID if(dir.GetObjectID() != ObjectID) { // Wrong object ID return -1; } // Return container ID return dir.GetContainerID(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CheckDirectories() // Purpose: Check the directories // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::CheckDirectories() { // Phase 1 did this: // Checked that all the directories are readable // Built a list of all directories and files which exist on the store // // This phase will check all the files in the directories, make // a note of all directories which are missing, and do initial fixing. // Scan all objects for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) { IDBlock *pblock = i->second; int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; for(int e = 0; e < bentries; ++e) { uint8_t flags = GetFlags(pblock, e); if(flags & Flags_IsDir) { // Found a directory. Read it in. std::string filename; StoreStructure::MakeObjectFilename(pblock->mID[e], mStoreRoot, mDiscSetNumber, filename, false /* no dir creation */); BackupStoreDirectory dir; { std::auto_ptr file(RaidFileRead::Open(mDiscSetNumber, filename)); dir.ReadFromStream(*file, IOStream::TimeOutInfinite); } // Flag for modifications bool isModified = false; // Check for validity if(dir.CheckAndFix()) { // Wasn't quite right, and has been modified BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " has bad structure"); ++mNumberErrorsFound; isModified = true; } // Go through, and check that everything in that directory exists and is valid std::vector toDelete; BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { // Lookup the item int32_t iIndex; IDBlock *piBlock = LookupID(en->GetObjectID(), iIndex); bool badEntry = false; if(piBlock != 0) { // Found. Get flags uint8_t iflags = GetFlags(piBlock, iIndex); // Is the type the same? if(((iflags & Flags_IsDir) == Flags_IsDir) != ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == BackupStoreDirectory::Entry::Flags_Dir)) { // Entry is of wrong type BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " references object " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " which has a different type than expected."); badEntry = true; } else { // Check that the entry is not already contained. if(iflags & Flags_IsContained) { BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " references object " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " which is already contained."); badEntry = true; } else { // Not already contained -- mark as contained SetFlags(piBlock, iIndex, iflags | Flags_IsContained); // Check that the container ID of the object is correct if(piBlock->mContainer[iIndex] != pblock->mID[e]) { // Needs fixing... if(iflags & Flags_IsDir) { // Add to will fix later list BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " has wrong container ID."); mDirsWithWrongContainerID.push_back(en->GetObjectID()); } else { // This is OK for files, they might move BOX_WARNING("File ID " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " has different container ID, probably moved"); } // Fix entry for now piBlock->mContainer[iIndex] = pblock->mID[e]; } } } // Check the object size, if it's OK and a file if(!badEntry && !((iflags & Flags_IsDir) == Flags_IsDir)) { if(en->GetSizeInBlocks() != piBlock->mObjectSizeInBlocks[iIndex]) { // Correct en->SetSizeInBlocks(piBlock->mObjectSizeInBlocks[iIndex]); // Mark as changed isModified = true; // Tell user BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " has wrong size for object " << BOX_FORMAT_OBJECTID(en->GetObjectID())); } } } else { // Item can't be found. Is it a directory? if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) { // Store the directory for later attention mDirsWhichContainLostDirs[en->GetObjectID()] = pblock->mID[e]; } else { // Just remove the entry badEntry = true; BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " references object " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " which does not exist."); } } // Is this entry worth keeping? if(badEntry) { toDelete.push_back(en->GetObjectID()); } else { // Add to sizes? if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) { mBlocksInOldFiles += en->GetSizeInBlocks(); } if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) { mBlocksInDeletedFiles += en->GetSizeInBlocks(); } } } if(toDelete.size() > 0) { // Delete entries from directory for(std::vector::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d) { dir.DeleteEntry(*d); } // Mark as modified isModified = true; // Check the directory again, now that entries have been removed dir.CheckAndFix(); // Errors found ++mNumberErrorsFound; } if(isModified && mFixErrors) { BOX_WARNING("Fixing directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e])); // Save back to disc RaidFileWrite fixed(mDiscSetNumber, filename); fixed.Open(true /* allow overwriting */); dir.WriteToStream(fixed); // Commit it fixed.Commit(true /* convert to raid representation now */); } } } } } boxbackup/lib/backupstore/BackupStoreConfigVerify.cpp0000664000175000017500000000261310775523641023657 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreConfigVerify.h // Purpose: Configuration definition for the backup store server // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include "BackupStoreConfigVerify.h" #include "ServerTLS.h" #include "BoxPortsAndFiles.h" #include "MemLeakFindOn.h" static const ConfigurationVerifyKey verifyserverkeys[] = { SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses }; static const ConfigurationVerify verifyserver[] = { { "Server", 0, verifyserverkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; static const ConfigurationVerifyKey verifyrootkeys[] = { ConfigurationVerifyKey("AccountDatabase", ConfigTest_Exists), ConfigurationVerifyKey("TimeBetweenHousekeeping", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("ExtendedLogging", ConfigTest_IsBool, false), // make value "yes" to enable in config file #ifdef WIN32 ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry) #else ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry, BOX_FILE_RAIDFILE_DEFAULT_CONFIG) #endif }; const ConfigurationVerify BackupConfigFileVerify = { "root", verifyserver, verifyrootkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; boxbackup/lib/backupstore/BackupStoreAccountDatabase.h0000664000175000017500000000356011221401673023740 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreAccountDatabase.h // Purpose: Database of accounts for the backup store // Created: 2003/08/20 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREACCOUNTDATABASE__H #define BACKUPSTOREACCOUNTDATABASE__H #include #include #include "BoxTime.h" class _BackupStoreAccountDatabase; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreAccountDatabase // Purpose: Database of accounts for the backup store // Created: 2003/08/20 // // -------------------------------------------------------------------------- class BackupStoreAccountDatabase { public: friend class _BackupStoreAccountDatabase; // to stop compiler warnings ~BackupStoreAccountDatabase(); private: BackupStoreAccountDatabase(const char *Filename); BackupStoreAccountDatabase(const BackupStoreAccountDatabase &); public: static std::auto_ptr Read(const char *Filename); void Write(); class Entry { public: Entry(); Entry(int32_t ID, int DiscSet); Entry(const Entry &rEntry); ~Entry(); int32_t GetID() const {return mID;} int GetDiscSet() const {return mDiscSet;} private: int32_t mID; int mDiscSet; }; bool EntryExists(int32_t ID) const; Entry GetEntry(int32_t ID) const; Entry AddEntry(int32_t ID, int DiscSet); void DeleteEntry(int32_t ID); // This interface should change in the future. But for now it'll do. void GetAllAccountIDs(std::vector &rIDsOut); private: void ReadFile() const; // const in concept only void CheckUpToDate() const; // const in concept only box_time_t GetDBFileModificationTime() const; private: mutable _BackupStoreAccountDatabase *pImpl; }; #endif // BACKUPSTOREACCOUNTDATABASE__H boxbackup/lib/backupstore/BackupStoreConfigVerify.h0000664000175000017500000000075710347400657023327 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreConfigVerify.h // Purpose: Configuration definition for the backup store server // Created: 2003/08/20 // // -------------------------------------------------------------------------- #ifndef BACKUPSTORECONFIGVERIFY__H #define BACKUPSTORECONFIGVERIFY__H #include "Configuration.h" extern const ConfigurationVerify BackupConfigFileVerify; #endif // BACKUPSTORECONFIGVERIFY__H boxbackup/lib/backupstore/BackupStoreRefCountDatabase.cpp0000664000175000017500000002167111235016653024434 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreRefCountDatabase.cpp // Purpose: Backup store object reference count database storage // Created: 2009/06/01 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupStoreRefCountDatabase.h" #include "BackupStoreException.h" #include "BackupStoreAccountDatabase.h" #include "BackupStoreAccounts.h" #include "RaidFileController.h" #include "RaidFileUtil.h" #include "RaidFileException.h" #include "Utils.h" #include "MemLeakFindOn.h" #define REFCOUNT_MAGIC_VALUE 0x52656643 // RefC #define REFCOUNT_FILENAME "refcount" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreRefCountDatabase::BackupStoreRefCountDatabase() // Purpose: Default constructor // Created: 2003/08/28 // // -------------------------------------------------------------------------- BackupStoreRefCountDatabase::BackupStoreRefCountDatabase(const BackupStoreAccountDatabase::Entry& rAccount) : mAccount(rAccount), mFilename(GetFilename(rAccount)), mReadOnly(true), mIsModified(false) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreRefCountDatabase::~BackupStoreRefCountDatabase // Purpose: Destructor // Created: 2003/08/28 // // -------------------------------------------------------------------------- BackupStoreRefCountDatabase::~BackupStoreRefCountDatabase() { } std::string BackupStoreRefCountDatabase::GetFilename(const BackupStoreAccountDatabase::Entry& rAccount) { std::string RootDir = BackupStoreAccounts::GetAccountRoot(rAccount); ASSERT(RootDir[RootDir.size() - 1] == '/' || RootDir[RootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR); std::string fn(RootDir + "refcount.db"); RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(rAccount.GetDiscSet())); return RaidFileUtil::MakeWriteFileName(rdiscSet, fn); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreRefCountDatabase::Create(int32_t, // const std::string &, int, bool) // Purpose: Create a new database, overwriting an existing // one only if AllowOverwrite is true. // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreRefCountDatabase::Create(const BackupStoreAccountDatabase::Entry& rAccount, bool AllowOverwrite) { // Initial header refcount_StreamFormat hdr; hdr.mMagicValue = htonl(REFCOUNT_MAGIC_VALUE); hdr.mAccountID = htonl(rAccount.GetID()); // Generate the filename std::string Filename = GetFilename(rAccount); // Open the file for writing if (FileExists(Filename) && !AllowOverwrite) { BOX_ERROR("Attempted to overwrite refcount database file: " << Filename); THROW_EXCEPTION(RaidFileException, CannotOverwriteExistingFile); } int flags = O_CREAT | O_BINARY | O_RDWR; if (!AllowOverwrite) { flags |= O_EXCL; } std::auto_ptr DatabaseFile(new FileStream(Filename, flags)); // Write header DatabaseFile->Write(&hdr, sizeof(hdr)); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreRefCountDatabase::Load(int32_t AccountID, // BackupStoreAccountDatabase& rAccountDatabase, // bool ReadOnly); // Purpose: Loads the info from disc, given the root // information. Can be marked as read only. // Created: 2003/08/28 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreRefCountDatabase::Load( const BackupStoreAccountDatabase::Entry& rAccount, bool ReadOnly) { // Generate the filename std::string filename = GetFilename(rAccount); int flags = ReadOnly ? O_RDONLY : O_RDWR; // Open the file for read/write std::auto_ptr dbfile(new FileStream(filename, flags | O_BINARY)); // Read in a header refcount_StreamFormat hdr; if(!dbfile->ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo) } // Check it if(ntohl(hdr.mMagicValue) != REFCOUNT_MAGIC_VALUE || (int32_t)ntohl(hdr.mAccountID) != rAccount.GetID()) { THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad) } // Make new object std::auto_ptr refcount(new BackupStoreRefCountDatabase(rAccount)); // Put in basic location info refcount->mReadOnly = ReadOnly; refcount->mapDatabaseFile = dbfile; // return it to caller return refcount; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreRefCountDatabase::Save() // Purpose: Save modified info back to disc // Created: 2003/08/28 // // -------------------------------------------------------------------------- /* void BackupStoreRefCountDatabase::Save() { // Make sure we're initialised (although should never come to this) if(mFilename.empty() || mAccount.GetID() == 0) { THROW_EXCEPTION(BackupStoreException, Internal) } // Can we do this? if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } // Then... open a write file RaidFileWrite rf(mAccount.GetDiscSet(), mFilename); rf.Open(true); // allow overwriting // Make header info_StreamFormat hdr; hdr.mMagicValue = htonl(INFO_MAGIC_VALUE); hdr.mAccountID = htonl(mAccountID); hdr.mClientStoreMarker = box_hton64(mClientStoreMarker); hdr.mLastObjectIDUsed = box_hton64(mLastObjectIDUsed); hdr.mBlocksUsed = box_hton64(mBlocksUsed); hdr.mBlocksInOldFiles = box_hton64(mBlocksInOldFiles); hdr.mBlocksInDeletedFiles = box_hton64(mBlocksInDeletedFiles); hdr.mBlocksInDirectories = box_hton64(mBlocksInDirectories); hdr.mBlocksSoftLimit = box_hton64(mBlocksSoftLimit); hdr.mBlocksHardLimit = box_hton64(mBlocksHardLimit); hdr.mCurrentMarkNumber = 0; hdr.mOptionsPresent = 0; hdr.mNumberDeletedDirectories = box_hton64(mDeletedDirectories.size()); // Write header rf.Write(&hdr, sizeof(hdr)); // Write the deleted object list if(mDeletedDirectories.size() > 0) { int64_t objs[NUM_DELETED_DIRS_BLOCK]; int tosave = mDeletedDirectories.size(); std::vector::iterator i(mDeletedDirectories.begin()); while(tosave > 0) { // How many in this one? int b = (tosave > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(tosave)); // Add them for(int t = 0; t < b; ++t) { ASSERT(i != mDeletedDirectories.end()); objs[t] = box_hton64((*i)); i++; } // Write rf.Write(objs, b * sizeof(int64_t)); // Number saved tosave -= b; } } // Commit it to disc, converting it to RAID now rf.Commit(true); // Mark is as not modified mIsModified = false; } */ // -------------------------------------------------------------------------- // // Function // Name: BackupStoreRefCountDatabase::GetRefCount(int64_t // ObjectID) // Purpose: Get the number of references to the specified object // out of the database // Created: 2009/06/01 // // -------------------------------------------------------------------------- BackupStoreRefCountDatabase::refcount_t BackupStoreRefCountDatabase::GetRefCount(int64_t ObjectID) const { IOStream::pos_type offset = GetOffset(ObjectID); if (GetSize() < offset + GetEntrySize()) { BOX_ERROR("attempted read of unknown refcount for object " << BOX_FORMAT_OBJECTID(ObjectID)); THROW_EXCEPTION(BackupStoreException, UnknownObjectRefCountRequested); } mapDatabaseFile->Seek(offset, SEEK_SET); refcount_t refcount; if (mapDatabaseFile->Read(&refcount, sizeof(refcount)) != sizeof(refcount)) { BOX_LOG_SYS_ERROR("short read on refcount database: " << mFilename); THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo); } return ntohl(refcount); } int64_t BackupStoreRefCountDatabase::GetLastObjectIDUsed() const { return (GetSize() - sizeof(refcount_StreamFormat)) / sizeof(refcount_t); } void BackupStoreRefCountDatabase::AddReference(int64_t ObjectID) { refcount_t refcount; if (ObjectID > GetLastObjectIDUsed()) { // new object, assume no previous references refcount = 0; } else { // read previous value from database refcount = GetRefCount(ObjectID); } refcount++; SetRefCount(ObjectID, refcount); } void BackupStoreRefCountDatabase::SetRefCount(int64_t ObjectID, refcount_t NewRefCount) { IOStream::pos_type offset = GetOffset(ObjectID); mapDatabaseFile->Seek(offset, SEEK_SET); refcount_t RefCountNetOrder = htonl(NewRefCount); mapDatabaseFile->Write(&RefCountNetOrder, sizeof(RefCountNetOrder)); } bool BackupStoreRefCountDatabase::RemoveReference(int64_t ObjectID) { refcount_t refcount = GetRefCount(ObjectID); // must exist in database ASSERT(refcount > 0); refcount--; SetRefCount(ObjectID, refcount); return (refcount > 0); } boxbackup/lib/backupstore/BackupStoreRefCountDatabase.h0000664000175000017500000000661611224217440024076 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreRefCountDatabase.h // Purpose: Main backup store information storage // Created: 2003/08/28 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREREFCOUNTDATABASE__H #define BACKUPSTOREREFCOUNTDATABASE__H #include #include #include #include "BackupStoreAccountDatabase.h" #include "FileStream.h" class BackupStoreCheck; class BackupStoreContext; // set packing to one byte #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "BeginStructPackForWire.h" #else BEGIN_STRUCTURE_PACKING_FOR_WIRE #endif typedef struct { uint32_t mMagicValue; // also the version number uint32_t mAccountID; } refcount_StreamFormat; // Use default packing #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "EndStructPackForWire.h" #else END_STRUCTURE_PACKING_FOR_WIRE #endif // -------------------------------------------------------------------------- // // Class // Name: BackupStoreRefCountDatabase // Purpose: Backup store reference count database storage // Created: 2009/06/01 // // -------------------------------------------------------------------------- class BackupStoreRefCountDatabase { friend class BackupStoreCheck; friend class BackupStoreContext; friend class HousekeepStoreAccount; public: ~BackupStoreRefCountDatabase(); private: // Creation through static functions only BackupStoreRefCountDatabase(const BackupStoreAccountDatabase::Entry& rAccount); // No copying allowed BackupStoreRefCountDatabase(const BackupStoreRefCountDatabase &); public: // Create a new database for a new account. This method will refuse // to overwrite any existing file. static void CreateNew(const BackupStoreAccountDatabase::Entry& rAccount) { Create(rAccount, false); } // Load it from the store static std::auto_ptr Load(const BackupStoreAccountDatabase::Entry& rAccount, bool ReadOnly); typedef uint32_t refcount_t; // Data access functions refcount_t GetRefCount(int64_t ObjectID) const; int64_t GetLastObjectIDUsed() const; // Data modification functions void AddReference(int64_t ObjectID); // RemoveReference returns false if refcount drops to zero bool RemoveReference(int64_t ObjectID); private: // Create a new database for an existing account. Used during // account checking if opening the old database throws an exception. // This method will overwrite any existing file. static void CreateForRegeneration(const BackupStoreAccountDatabase::Entry& rAccount) { Create(rAccount, true); } static void Create(const BackupStoreAccountDatabase::Entry& rAccount, bool AllowOverwrite); static std::string GetFilename(const BackupStoreAccountDatabase::Entry& rAccount); IOStream::pos_type GetSize() const { return mapDatabaseFile->GetPosition() + mapDatabaseFile->BytesLeftToRead(); } IOStream::pos_type GetEntrySize() const { return sizeof(refcount_t); } IOStream::pos_type GetOffset(int64_t ObjectID) const { return ((ObjectID - 1) * GetEntrySize()) + sizeof(refcount_StreamFormat); } void SetRefCount(int64_t ObjectID, refcount_t NewRefCount); // Location information BackupStoreAccountDatabase::Entry mAccount; std::string mFilename; bool mReadOnly; bool mIsModified; std::auto_ptr mapDatabaseFile; }; #endif // BACKUPSTOREREFCOUNTDATABASE__H boxbackup/lib/backupstore/BackupStoreInfo.h0000664000175000017500000000704010702536354021617 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreInfo.h // Purpose: Main backup store information storage // Created: 2003/08/28 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREINFO__H #define BACKUPSTOREINFO__H #include #include #include class BackupStoreCheck; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreInfo // Purpose: Main backup store information storage // Created: 2003/08/28 // // -------------------------------------------------------------------------- class BackupStoreInfo { friend class BackupStoreCheck; public: ~BackupStoreInfo(); private: // Creation through static functions only BackupStoreInfo(); // No copying allowed BackupStoreInfo(const BackupStoreInfo &); public: // Create a New account, saving a blank info object to the disc static void CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t BlockSoftLimit, int64_t BlockHardLimit); // Load it from the store static std::auto_ptr Load(int32_t AccountID, const std::string &rRootDir, int DiscSet, bool ReadOnly, int64_t *pRevisionID = 0); // Has info been modified? bool IsModified() const {return mIsModified;} // Save modified infomation back to store void Save(); // Data access functions int32_t GetAccountID() const {return mAccountID;} int64_t GetLastObjectIDUsed() const {return mLastObjectIDUsed;} int64_t GetBlocksUsed() const {return mBlocksUsed;} int64_t GetBlocksInOldFiles() const {return mBlocksInOldFiles;} int64_t GetBlocksInDeletedFiles() const {return mBlocksInDeletedFiles;} int64_t GetBlocksInDirectories() const {return mBlocksInDirectories;} const std::vector &GetDeletedDirectories() const {return mDeletedDirectories;} int64_t GetBlocksSoftLimit() const {return mBlocksSoftLimit;} int64_t GetBlocksHardLimit() const {return mBlocksHardLimit;} bool IsReadOnly() const {return mReadOnly;} int GetDiscSetNumber() const {return mDiscSet;} // Data modification functions void ChangeBlocksUsed(int64_t Delta); void ChangeBlocksInOldFiles(int64_t Delta); void ChangeBlocksInDeletedFiles(int64_t Delta); void ChangeBlocksInDirectories(int64_t Delta); void CorrectAllUsedValues(int64_t Used, int64_t InOldFiles, int64_t InDeletedFiles, int64_t InDirectories); void AddDeletedDirectory(int64_t DirID); void RemovedDeletedDirectory(int64_t DirID); void ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit); // Object IDs int64_t AllocateObjectID(); // Client marker set and get int64_t GetClientStoreMarker() {return mClientStoreMarker;} void SetClientStoreMarker(int64_t ClientStoreMarker); private: static std::auto_ptr CreateForRegeneration(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t LastObjectID, int64_t BlocksUsed, int64_t BlocksInOldFiles, int64_t BlocksInDeletedFiles, int64_t BlocksInDirectories, int64_t BlockSoftLimit, int64_t BlockHardLimit); private: // Location information int32_t mAccountID; int mDiscSet; std::string mFilename; bool mReadOnly; bool mIsModified; // Client infomation int64_t mClientStoreMarker; // Account information int64_t mLastObjectIDUsed; int64_t mBlocksUsed; int64_t mBlocksInOldFiles; int64_t mBlocksInDeletedFiles; int64_t mBlocksInDirectories; int64_t mBlocksSoftLimit; int64_t mBlocksHardLimit; std::vector mDeletedDirectories; }; #endif // BACKUPSTOREINFO__H boxbackup/lib/backupstore/BackupStoreAccountDatabase.cpp0000664000175000017500000002377711221401673024307 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreAccountDatabase.cpp // Purpose: Database of accounts for the backup store // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include "BackupStoreAccountDatabase.h" #include "Guards.h" #include "FdGetLine.h" #include "BackupStoreException.h" #include "CommonException.h" #include "FileModificationTime.h" #include "MemLeakFindOn.h" class _BackupStoreAccountDatabase { public: std::string mFilename; std::map mDatabase; box_time_t mModificationTime; }; // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::BackupStoreAccountDatabase(const char *) // Purpose: Constructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::BackupStoreAccountDatabase(const char *Filename) : pImpl(new _BackupStoreAccountDatabase) { pImpl->mFilename = Filename; pImpl->mModificationTime = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::~BackupStoreAccountDatabase() // Purpose: Destructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::~BackupStoreAccountDatabase() { delete pImpl; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::Entry::Entry() // Purpose: Default constructor // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::Entry::Entry() : mID(-1), mDiscSet(-1) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::Entry::Entry(int32_t, int) // Purpose: Constructor // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::Entry::Entry(int32_t ID, int DiscSet) : mID(ID), mDiscSet(DiscSet) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::Entry::Entry(const Entry &) // Purpose: Copy constructor // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::Entry::Entry(const Entry &rEntry) : mID(rEntry.mID), mDiscSet(rEntry.mDiscSet) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::Entry::~Entry() // Purpose: Destructor // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::Entry::~Entry() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::Read(const char *) // Purpose: Read in a database from disc // Created: 2003/08/21 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreAccountDatabase::Read(const char *Filename) { // Database object to use std::auto_ptr db(new BackupStoreAccountDatabase(Filename)); // Read in the file db->ReadFile(); // Return to called return db; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::ReadFile() // Purpose: Read the file off disc // Created: 21/1/04 // // -------------------------------------------------------------------------- void BackupStoreAccountDatabase::ReadFile() const { // Open file FileHandleGuard<> file(pImpl->mFilename.c_str()); // Clear existing entries pImpl->mDatabase.clear(); // Read in lines FdGetLine getLine(file); while(!getLine.IsEOF()) { // Read and split up line std::string l(getLine.GetLine(true)); if(!l.empty()) { // Check... int32_t id; int discSet; if(::sscanf(l.c_str(), "%x:%d", &id, &discSet) != 2) { THROW_EXCEPTION(BackupStoreException, BadAccountDatabaseFile) } // Make a new entry pImpl->mDatabase[id] = Entry(id, discSet); } } // Store the modification time of the file pImpl->mModificationTime = GetDBFileModificationTime(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::CheckUpToDate() // Purpose: Private. Ensure that the in memory database matches the one on disc // Created: 21/1/04 // // -------------------------------------------------------------------------- void BackupStoreAccountDatabase::CheckUpToDate() const { if(pImpl->mModificationTime != GetDBFileModificationTime()) { // File has changed -- load it in again ReadFile(); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::GetDBFileModificationTime() // Purpose: Get the current modification time of the database // Created: 21/1/04 // // -------------------------------------------------------------------------- box_time_t BackupStoreAccountDatabase::GetDBFileModificationTime() const { EMU_STRUCT_STAT st; if(EMU_STAT(pImpl->mFilename.c_str(), &st) == -1) { THROW_EXCEPTION(CommonException, OSFileError) } return FileModificationTime(st); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::Write() // Purpose: Write the database back to disc after modifying it // Created: 2003/08/21 // // -------------------------------------------------------------------------- void BackupStoreAccountDatabase::Write() { // Open file for writing // Would use this... // FileHandleGuard file(pImpl->mFilename.c_str()); // but gcc fails randomly on it on some platforms. Weird. int file = ::open(pImpl->mFilename.c_str(), O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if(file == -1) { THROW_EXCEPTION(CommonException, OSFileOpenError) } try { // Then write each entry for(std::map::const_iterator i(pImpl->mDatabase.begin()); i != pImpl->mDatabase.end(); ++i) { // Write out the entry char line[256]; // more than enough for a couple of integers in string form int s = ::sprintf(line, "%x:%d\n", i->second.GetID(), i->second.GetDiscSet()); if(::write(file, line, s) != s) { THROW_EXCEPTION(CommonException, OSFileError) } } ::close(file); } catch(...) { ::close(file); throw; } // Done. } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::EntryExists(int32_t) // Purpose: Does an entry exist in the database? // Created: 2003/08/21 // // -------------------------------------------------------------------------- bool BackupStoreAccountDatabase::EntryExists(int32_t ID) const { // Check that we're using the latest version of the database CheckUpToDate(); return pImpl->mDatabase.find(ID) != pImpl->mDatabase.end(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::GetEntry(int32_t) // Purpose: Retrieve an entry // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::Entry BackupStoreAccountDatabase::GetEntry( int32_t ID) const { // Check that we're using the latest version of the database CheckUpToDate(); std::map::const_iterator i(pImpl->mDatabase.find(ID)); if(i == pImpl->mDatabase.end()) { THROW_EXCEPTION(BackupStoreException, AccountDatabaseNoSuchEntry) } return i->second; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::AddEntry(int32_t, int) // Purpose: Add a new entry to the database // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccountDatabase::Entry BackupStoreAccountDatabase::AddEntry( int32_t ID, int DiscSet) { // Check that we're using the latest version of the database CheckUpToDate(); pImpl->mDatabase[ID] = Entry(ID, DiscSet); return pImpl->mDatabase[ID]; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::DeleteEntry(int32_t) // Purpose: Delete an entry from the database // Created: 2003/08/21 // // -------------------------------------------------------------------------- void BackupStoreAccountDatabase::DeleteEntry(int32_t ID) { // Check that we're using the latest version of the database CheckUpToDate(); std::map::iterator i(pImpl->mDatabase.find(ID)); if(i == pImpl->mDatabase.end()) { THROW_EXCEPTION(BackupStoreException, AccountDatabaseNoSuchEntry) } pImpl->mDatabase.erase(i); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccountDatabase::GetAllAccountIDs(std::vector) // Purpose: // Created: 11/12/03 // // -------------------------------------------------------------------------- void BackupStoreAccountDatabase::GetAllAccountIDs(std::vector &rIDsOut) { // Check that we're using the latest version of the database CheckUpToDate(); // Delete everything in the output list rIDsOut.clear(); std::map::iterator i(pImpl->mDatabase.begin()); for(; i != pImpl->mDatabase.end(); ++i) { rIDsOut.push_back(i->first); } } boxbackup/lib/backupstore/BackupStoreAccounts.h0000664000175000017500000000265011221402314022466 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreAccounts.h // Purpose: Account management for backup store server // Created: 2003/08/21 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREACCOUNTS__H #define BACKUPSTOREACCOUNTS__H #include #include "BackupStoreAccountDatabase.h" // -------------------------------------------------------------------------- // // Class // Name: BackupStoreAccounts // Purpose: Account management for backup store server // Created: 2003/08/21 // // -------------------------------------------------------------------------- class BackupStoreAccounts { public: BackupStoreAccounts(BackupStoreAccountDatabase &rDatabase); ~BackupStoreAccounts(); private: BackupStoreAccounts(const BackupStoreAccounts &rToCopy); public: void Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, int64_t SizeHardLimit, const std::string &rAsUsername); bool AccountExists(int32_t ID); void GetAccountRoot(int32_t ID, std::string &rRootDirOut, int &rDiscSetOut) const; static std::string GetAccountRoot(const BackupStoreAccountDatabase::Entry &rEntry) { return MakeAccountRootDir(rEntry.GetID(), rEntry.GetDiscSet()); } private: static std::string MakeAccountRootDir(int32_t ID, int DiscSet); private: BackupStoreAccountDatabase &mrDatabase; }; #endif // BACKUPSTOREACCOUNTS__H boxbackup/lib/backupstore/StoreStructure.cpp0000664000175000017500000000547010347400657022137 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: StoreStructure.cpp // Purpose: // Created: 11/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include "StoreStructure.h" #include "RaidFileRead.h" #include "RaidFileWrite.h" #include "RaidFileController.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: StoreStructure::MakeObjectFilename(int64_t, const std::string &, int, std::string &, bool) // Purpose: Builds the object filename for a given object, given a root. Optionally ensure that the // directory exists. // Created: 11/12/03 // // -------------------------------------------------------------------------- void StoreStructure::MakeObjectFilename(int64_t ObjectID, const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut, bool EnsureDirectoryExists) { const static char *hex = "0123456789abcdef"; // Set output to root string rFilenameOut = rStoreRoot; // get the id value from the stored object ID so we can do // bitwise operations on it. uint64_t id = (uint64_t)ObjectID; // get leafname, shift the bits which make up the leafname off unsigned int leafname(id & STORE_ID_SEGMENT_MASK); id >>= STORE_ID_SEGMENT_LENGTH; // build pathname while(id != 0) { // assumes that the segments are no bigger than 8 bits int v = id & STORE_ID_SEGMENT_MASK; rFilenameOut += hex[(v & 0xf0) >> 4]; rFilenameOut += hex[v & 0xf]; rFilenameOut += DIRECTORY_SEPARATOR_ASCHAR; // shift the bits we used off the pathname id >>= STORE_ID_SEGMENT_LENGTH; } // Want to make sure this exists? if(EnsureDirectoryExists) { if(!RaidFileRead::DirectoryExists(DiscSet, rFilenameOut)) { // Create it RaidFileWrite::CreateDirectory(DiscSet, rFilenameOut, true /* recusive */); } } // append the filename rFilenameOut += 'o'; rFilenameOut += hex[(leafname & 0xf0) >> 4]; rFilenameOut += hex[leafname & 0xf]; } // -------------------------------------------------------------------------- // // Function // Name: StoreStructure::MakeWriteLockFilename(const std::string &, int, std::string &) // Purpose: Generate the on disc filename of the write lock file // Created: 15/12/03 // // -------------------------------------------------------------------------- void StoreStructure::MakeWriteLockFilename(const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut) { // Find the disc set RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet &rdiscSet(rcontroller.GetDiscSet(DiscSet)); // Make the filename std::string writeLockFile(rdiscSet[0] + DIRECTORY_SEPARATOR + rStoreRoot + "write.lock"); // Return it to the caller rFilenameOut = writeLockFile; } boxbackup/lib/backupstore/StoreStructure.h0000664000175000017500000000161011126433077021572 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: StoreStructure.h // Purpose: Functions for placing files in the store // Created: 11/12/03 // // -------------------------------------------------------------------------- #ifndef STORESTRUCTURE__H #define STORESTRUCTURE__H #include #ifdef BOX_RELEASE_BUILD #define STORE_ID_SEGMENT_LENGTH 8 #define STORE_ID_SEGMENT_MASK 0xff #else // Debug we'll use lots and lots of directories to stress things #define STORE_ID_SEGMENT_LENGTH 2 #define STORE_ID_SEGMENT_MASK 0x03 #endif namespace StoreStructure { void MakeObjectFilename(int64_t ObjectID, const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut, bool EnsureDirectoryExists); void MakeWriteLockFilename(const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut); }; #endif // STORESTRUCTURE__H boxbackup/lib/backupstore/BackupStoreCheckData.cpp0000664000175000017500000001227511126433077023073 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreCheckData.cpp // Purpose: Data handling for store checking // Created: 21/4/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "BackupStoreCheck.h" #include "autogen_BackupStoreException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::FreeInfo() // Purpose: Free all the data stored // Created: 21/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::FreeInfo() { // Free all the blocks for(Info_t::iterator i(mInfo.begin()); i != mInfo.end(); ++i) { ::free(i->second); } // Clear the contents of the map mInfo.clear(); // Reset the last ID, just in case mpInfoLastBlock = 0; mInfoLastBlockEntries = 0; mLastIDInInfo = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::AddID(BackupStoreCheck_ID_t, BackupStoreCheck_ID_t, bool) // Purpose: Add an ID to the list // Created: 21/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::AddID(BackupStoreCheck_ID_t ID, BackupStoreCheck_ID_t Container, BackupStoreCheck_Size_t ObjectSize, bool IsFile) { // Check ID is OK. if(ID <= mLastIDInInfo) { THROW_EXCEPTION(BackupStoreException, InternalAlgorithmErrorCheckIDNotMonotonicallyIncreasing) } // Can this go in the current block? if(mpInfoLastBlock == 0 || mInfoLastBlockEntries >= BACKUPSTORECHECK_BLOCK_SIZE) { // No. Allocate a new one IDBlock *pblk = (IDBlock*)::malloc(sizeof(IDBlock)); if(pblk == 0) { throw std::bad_alloc(); } // Zero all the flags entries for(int z = 0; z < (BACKUPSTORECHECK_BLOCK_SIZE * Flags__NumFlags / Flags__NumItemsPerEntry); ++z) { pblk->mFlags[z] = 0; } // Store in map mInfo[ID] = pblk; // Allocated and stored OK, setup for use mpInfoLastBlock = pblk; mInfoLastBlockEntries = 0; } ASSERT(mpInfoLastBlock != 0 && mInfoLastBlockEntries < BACKUPSTORECHECK_BLOCK_SIZE); // Add to block mpInfoLastBlock->mID[mInfoLastBlockEntries] = ID; mpInfoLastBlock->mContainer[mInfoLastBlockEntries] = Container; mpInfoLastBlock->mObjectSizeInBlocks[mInfoLastBlockEntries] = ObjectSize; SetFlags(mpInfoLastBlock, mInfoLastBlockEntries, IsFile?(0):(Flags_IsDir)); // Increment size ++mInfoLastBlockEntries; // Store last ID mLastIDInInfo = ID; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::LookupID(BackupStoreCheck_ID_t, int32_t // Purpose: Look up an ID. Return the block it's in, or zero if not found, and the // index within that block if the thing is found. // Created: 21/4/04 // // -------------------------------------------------------------------------- BackupStoreCheck::IDBlock *BackupStoreCheck::LookupID(BackupStoreCheck_ID_t ID, int32_t &rIndexOut) { IDBlock *pblock = 0; // Find the lower matching block who's first entry is not less than ID Info_t::const_iterator ib(mInfo.lower_bound(ID)); // Was there a block if(ib == mInfo.end()) { // Block wasn't found... could be in last block pblock = mpInfoLastBlock; } else { // Found it as first entry? if(ib->first == ID) { rIndexOut = 0; return ib->second; } // Go back one block as it's not the first entry in this one if(ib == mInfo.begin()) { // Was first block, can't go back return 0; } // Go back... --ib; // So, the ID will be in this block, if it's in anything pblock = ib->second; } ASSERT(pblock != 0); if(pblock == 0) return 0; // How many entries are there in the block int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; // Do binary search within block int high = bentries; int low = -1; while(high - low > 1) { int i = (high + low) / 2; if(ID <= pblock->mID[i]) { high = i; } else { low = i; } } if(ID == pblock->mID[high]) { // Found rIndexOut = high; return pblock; } // Not found return 0; } #ifndef BOX_RELEASE_BUILD // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::DumpObjectInfo() // Purpose: Debug only. Trace out all object info. // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::DumpObjectInfo() { for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) { IDBlock *pblock = i->second; int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; BOX_TRACE("BLOCK @ " << BOX_FORMAT_HEX32(pblock) << ", " << bentries << " entries"); for(int e = 0; e < bentries; ++e) { uint8_t flags = GetFlags(pblock, e); BOX_TRACE(std::hex << "id " << pblock->mID[e] << ", c " << pblock->mContainer[e] << ", " << ((flags & Flags_IsDir)?"dir":"file") << ", " << ((flags & Flags_IsContained) ? "contained":"unattached")); } } } #endif boxbackup/lib/backupstore/BackupStoreAccounts.cpp0000664000175000017500000001234511221402314023023 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreAccounts.cpp // Purpose: Account management for backup store server // Created: 2003/08/21 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BoxPortsAndFiles.h" #include "BackupStoreAccounts.h" #include "BackupStoreAccountDatabase.h" #include "BackupStoreRefCountDatabase.h" #include "RaidFileWrite.h" #include "BackupStoreInfo.h" #include "BackupStoreDirectory.h" #include "BackupStoreConstants.h" #include "UnixUser.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccounts::BackupStoreAccounts(BackupStoreAccountDatabase &) // Purpose: Constructor // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccounts::BackupStoreAccounts(BackupStoreAccountDatabase &rDatabase) : mrDatabase(rDatabase) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccounts::~BackupStoreAccounts() // Purpose: Destructor // Created: 2003/08/21 // // -------------------------------------------------------------------------- BackupStoreAccounts::~BackupStoreAccounts() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccounts::Create(int32_t, int, int64_t, int64_t, const std::string &) // Purpose: Create a new account on the specified disc set. // If rAsUsername is not empty, then the account information will be written under the // username specified. // Created: 2003/08/21 // // -------------------------------------------------------------------------- void BackupStoreAccounts::Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, int64_t SizeHardLimit, const std::string &rAsUsername) { // Create the entry in the database BackupStoreAccountDatabase::Entry Entry(mrDatabase.AddEntry(ID, DiscSet)); { // Become the user specified in the config file? std::auto_ptr user; if(!rAsUsername.empty()) { // Username specified, change... user.reset(new UnixUser(rAsUsername.c_str())); user->ChangeProcessUser(true /* temporary */); // Change will be undone at the end of this function } // Get directory name std::string dirName(MakeAccountRootDir(ID, DiscSet)); // Create a directory on disc RaidFileWrite::CreateDirectory(DiscSet, dirName, true /* recursive */); // Create an info file BackupStoreInfo::CreateNew(ID, dirName, DiscSet, SizeSoftLimit, SizeHardLimit); // And an empty directory BackupStoreDirectory rootDir(BACKUPSTORE_ROOT_DIRECTORY_ID, BACKUPSTORE_ROOT_DIRECTORY_ID); int64_t rootDirSize = 0; // Write it, knowing the directory scheme { RaidFileWrite rf(DiscSet, dirName + "o01"); rf.Open(); rootDir.WriteToStream(rf); rootDirSize = rf.GetDiscUsageInBlocks(); rf.Commit(true); } // Update the store info to reflect the size of the root directory std::auto_ptr info(BackupStoreInfo::Load(ID, dirName, DiscSet, false /* ReadWrite */)); info->ChangeBlocksUsed(rootDirSize); info->ChangeBlocksInDirectories(rootDirSize); // Save it back info->Save(); // Create the refcount database BackupStoreRefCountDatabase::CreateNew(Entry); std::auto_ptr refcount( BackupStoreRefCountDatabase::Load(Entry, false)); refcount->AddReference(BACKUPSTORE_ROOT_DIRECTORY_ID); } // As the original user... // Write the database back mrDatabase.Write(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccounts::GetAccountRoot(int32_t, std::string &, int &) // Purpose: Gets the root of an account, returning the info via references // Created: 2003/08/21 // // -------------------------------------------------------------------------- void BackupStoreAccounts::GetAccountRoot(int32_t ID, std::string &rRootDirOut, int &rDiscSetOut) const { // Find the account const BackupStoreAccountDatabase::Entry &en(mrDatabase.GetEntry(ID)); rRootDirOut = MakeAccountRootDir(ID, en.GetDiscSet()); rDiscSetOut = en.GetDiscSet(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccounts::MakeAccountRootDir(int32_t, int) // Purpose: Private. Generates a root directory name for the account // Created: 2003/08/21 // // -------------------------------------------------------------------------- std::string BackupStoreAccounts::MakeAccountRootDir(int32_t ID, int DiscSet) { char accid[64]; // big enough! ::sprintf(accid, "%08x" DIRECTORY_SEPARATOR, ID); return std::string(std::string(BOX_RAIDFILE_ROOT_BBSTORED DIRECTORY_SEPARATOR) + accid); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreAccounts::AccountExists(int32_t) // Purpose: Does an account exist? // Created: 2003/08/21 // // -------------------------------------------------------------------------- bool BackupStoreAccounts::AccountExists(int32_t ID) { return mrDatabase.EntryExists(ID); } boxbackup/lib/backupstore/BackupStoreCheck.h0000664000175000017500000001331411126433077021741 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreCheck.h // Purpose: Check a store for consistency // Created: 21/4/04 // // -------------------------------------------------------------------------- #ifndef BACKUPSTORECHECK__H #define BACKUPSTORECHECK__H #include #include #include #include #include "NamedLock.h" class IOStream; class BackupStoreFilename; /* The following problems can be fixed: * Spurious files deleted * Corrupted files deleted * Root ID as file, deleted * Dirs with wrong object id inside, deleted * Direcetory entries pointing to non-existant files, deleted * Doubly references files have second reference deleted * Wrong directory container IDs fixed * Missing root recreated * Reattach files which exist, but aren't referenced - files go into directory original directory, if it still exists - missing directories are inferred, and recreated - or if all else fails, go into lost+found - file dir entries take the original name and mod time - directories go into lost+found * Container IDs on directories corrected * Inside directories, - only one object per name has old version clear - IDs aren't duplicated * Bad store info files regenerated * Bad sizes of files in directories fixed */ // Size of blocks in the list of IDs #ifdef BOX_RELEASE_BUILD #define BACKUPSTORECHECK_BLOCK_SIZE (64*1024) #else #define BACKUPSTORECHECK_BLOCK_SIZE 8 #endif // The object ID type -- can redefine to uint32_t to produce a lower memory version for smaller stores typedef int64_t BackupStoreCheck_ID_t; // Can redefine the size type for lower memory usage too typedef int64_t BackupStoreCheck_Size_t; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreCheck // Purpose: Check a store for consistency // Created: 21/4/04 // // -------------------------------------------------------------------------- class BackupStoreCheck { public: BackupStoreCheck(const std::string &rStoreRoot, int DiscSetNumber, int32_t AccountID, bool FixErrors, bool Quiet); ~BackupStoreCheck(); private: // no copying BackupStoreCheck(const BackupStoreCheck &); BackupStoreCheck &operator=(const BackupStoreCheck &); public: // Do the exciting things void Check(); bool ErrorsFound() {return mNumberErrorsFound > 0;} private: enum { // Bit mask Flags_IsDir = 1, Flags_IsContained = 2, // Mask Flags__MASK = 3, // Number of bits Flags__NumFlags = 2, // Items per uint8_t Flags__NumItemsPerEntry = 4 // ie 8 / 2 }; typedef struct { // Note use arrays within the block, rather than the more obvious array of // objects, to be more memory efficient -- think alignment of the byte values. uint8_t mFlags[BACKUPSTORECHECK_BLOCK_SIZE * Flags__NumFlags / Flags__NumItemsPerEntry]; BackupStoreCheck_ID_t mID[BACKUPSTORECHECK_BLOCK_SIZE]; BackupStoreCheck_ID_t mContainer[BACKUPSTORECHECK_BLOCK_SIZE]; BackupStoreCheck_Size_t mObjectSizeInBlocks[BACKUPSTORECHECK_BLOCK_SIZE]; } IDBlock; // Phases of the check void CheckObjects(); void CheckDirectories(); void CheckRoot(); void CheckUnattachedObjects(); void FixDirsWithWrongContainerID(); void FixDirsWithLostDirs(); void WriteNewStoreInfo(); // Checking functions int64_t CheckObjectsScanDir(int64_t StartID, int Level, const std::string &rDirName); void CheckObjectsDir(int64_t StartID); bool CheckAndAddObject(int64_t ObjectID, const std::string &rFilename); int64_t CheckFile(int64_t ObjectID, IOStream &rStream); int64_t CheckDirInitial(int64_t ObjectID, IOStream &rStream); // Fixing functions bool TryToRecreateDirectory(int64_t MissingDirectoryID); void InsertObjectIntoDirectory(int64_t ObjectID, int64_t DirectoryID, bool IsDirectory); int64_t GetLostAndFoundDirID(); void CreateBlankDirectory(int64_t DirectoryID, int64_t ContainingDirID); // Data handling void FreeInfo(); void AddID(BackupStoreCheck_ID_t ID, BackupStoreCheck_ID_t Container, BackupStoreCheck_Size_t ObjectSize, bool IsFile); IDBlock *LookupID(BackupStoreCheck_ID_t ID, int32_t &rIndexOut); inline void SetFlags(IDBlock *pBlock, int32_t Index, uint8_t Flags) { ASSERT(pBlock != 0); ASSERT(Index < BACKUPSTORECHECK_BLOCK_SIZE); ASSERT(Flags < (1 << Flags__NumFlags)); pBlock->mFlags[Index / Flags__NumItemsPerEntry] |= (Flags << ((Index % Flags__NumItemsPerEntry) * Flags__NumFlags)); } inline uint8_t GetFlags(IDBlock *pBlock, int32_t Index) { ASSERT(pBlock != 0); ASSERT(Index < BACKUPSTORECHECK_BLOCK_SIZE); return (pBlock->mFlags[Index / Flags__NumItemsPerEntry] >> ((Index % Flags__NumItemsPerEntry) * Flags__NumFlags)) & Flags__MASK; } #ifndef BOX_RELEASE_BUILD void DumpObjectInfo(); #define DUMP_OBJECT_INFO DumpObjectInfo(); #else #define DUMP_OBJECT_INFO #endif private: std::string mStoreRoot; int mDiscSetNumber; int32_t mAccountID; bool mFixErrors; bool mQuiet; int64_t mNumberErrorsFound; // Lock for the store account NamedLock mAccountLock; // Storage for ID data typedef std::map Info_t; Info_t mInfo; BackupStoreCheck_ID_t mLastIDInInfo; IDBlock *mpInfoLastBlock; int32_t mInfoLastBlockEntries; // List of stuff to fix std::vector mDirsWithWrongContainerID; // This is a map of lost dir ID -> existing dir ID std::map mDirsWhichContainLostDirs; // Set of extra directories added std::set mDirsAdded; // Misc stuff int32_t mLostDirNameSerial; int64_t mLostAndFoundDirectoryID; // Usage int64_t mBlocksUsed; int64_t mBlocksInOldFiles; int64_t mBlocksInDeletedFiles; int64_t mBlocksInDirectories; }; #endif // BACKUPSTORECHECK__H boxbackup/lib/backupstore/BackupStoreInfo.cpp0000664000175000017500000004046111126433077022155 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreInfo.cpp // Purpose: Main backup store information storage // Created: 2003/08/28 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupStoreInfo.h" #include "BackupStoreException.h" #include "RaidFileWrite.h" #include "RaidFileRead.h" #include "MemLeakFindOn.h" // set packing to one byte #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "BeginStructPackForWire.h" #else BEGIN_STRUCTURE_PACKING_FOR_WIRE #endif // ****************** // make sure the defaults in CreateNew are modified! // ****************** typedef struct { int32_t mMagicValue; // also the version number int32_t mAccountID; int64_t mClientStoreMarker; int64_t mLastObjectIDUsed; int64_t mBlocksUsed; int64_t mBlocksInOldFiles; int64_t mBlocksInDeletedFiles; int64_t mBlocksInDirectories; int64_t mBlocksSoftLimit; int64_t mBlocksHardLimit; uint32_t mCurrentMarkNumber; uint32_t mOptionsPresent; // bit mask of optional elements present int64_t mNumberDeletedDirectories; // Then loads of int64_t IDs for the deleted directories } info_StreamFormat; #define INFO_MAGIC_VALUE 0x34832476 // Use default packing #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "EndStructPackForWire.h" #else END_STRUCTURE_PACKING_FOR_WIRE #endif #ifdef BOX_RELEASE_BUILD #define NUM_DELETED_DIRS_BLOCK 256 #else #define NUM_DELETED_DIRS_BLOCK 2 #endif #define INFO_FILENAME "info" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::BackupStoreInfo() // Purpose: Default constructor // Created: 2003/08/28 // // -------------------------------------------------------------------------- BackupStoreInfo::BackupStoreInfo() : mAccountID(-1), mDiscSet(-1), mReadOnly(true), mIsModified(false), mClientStoreMarker(0), mLastObjectIDUsed(-1), mBlocksUsed(0), mBlocksInOldFiles(0), mBlocksInDeletedFiles(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::~BackupStoreInfo // Purpose: Destructor // Created: 2003/08/28 // // -------------------------------------------------------------------------- BackupStoreInfo::~BackupStoreInfo() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::CreateNew(int32_t, const std::string &, int) // Purpose: Create a new info file on disc // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t BlockSoftLimit, int64_t BlockHardLimit) { // Initial header (is entire file) info_StreamFormat hdr = { htonl(INFO_MAGIC_VALUE), // mMagicValue htonl(AccountID), // mAccountID 0, // mClientStoreMarker box_hton64(1), // mLastObjectIDUsed (which is the root directory) 0, // mBlocksUsed 0, // mBlocksInOldFiles 0, // mBlocksInDeletedFiles 0, // mBlocksInDirectories box_hton64(BlockSoftLimit), // mBlocksSoftLimit box_hton64(BlockHardLimit), // mBlocksHardLimit 0, // mCurrentMarkNumber 0, // mOptionsPresent 0 // mNumberDeletedDirectories }; // Generate the filename ASSERT(rRootDir[rRootDir.size() - 1] == '/' || rRootDir[rRootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR); std::string fn(rRootDir + INFO_FILENAME); // Open the file for writing RaidFileWrite rf(DiscSet, fn); rf.Open(false); // no overwriting, as this is a new file // Write header rf.Write(&hdr, sizeof(hdr)); // Commit it to disc, converting it to RAID now rf.Commit(true); // Done. } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::Load(int32_t, const std::string &, int, bool) // Purpose: Loads the info from disc, given the root information. Can be marked as read only. // Created: 2003/08/28 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreInfo::Load(int32_t AccountID, const std::string &rRootDir, int DiscSet, bool ReadOnly, int64_t *pRevisionID) { // Generate the filename std::string fn(rRootDir + DIRECTORY_SEPARATOR INFO_FILENAME); // Open the file for reading (passing on optional request for revision ID) std::auto_ptr rf(RaidFileRead::Open(DiscSet, fn, pRevisionID)); // Read in a header info_StreamFormat hdr; if(!rf->ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo) } // Check it if(ntohl(hdr.mMagicValue) != INFO_MAGIC_VALUE || (int32_t)ntohl(hdr.mAccountID) != AccountID) { THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad) } // Make new object std::auto_ptr info(new BackupStoreInfo); // Put in basic location info info->mAccountID = AccountID; info->mDiscSet = DiscSet; info->mFilename = fn; info->mReadOnly = ReadOnly; // Insert info from file info->mClientStoreMarker = box_ntoh64(hdr.mClientStoreMarker); info->mLastObjectIDUsed = box_ntoh64(hdr.mLastObjectIDUsed); info->mBlocksUsed = box_ntoh64(hdr.mBlocksUsed); info->mBlocksInOldFiles = box_ntoh64(hdr.mBlocksInOldFiles); info->mBlocksInDeletedFiles = box_ntoh64(hdr.mBlocksInDeletedFiles); info->mBlocksInDirectories = box_ntoh64(hdr.mBlocksInDirectories); info->mBlocksSoftLimit = box_ntoh64(hdr.mBlocksSoftLimit); info->mBlocksHardLimit = box_ntoh64(hdr.mBlocksHardLimit); // Load up array of deleted objects int64_t numDelObj = box_ntoh64(hdr.mNumberDeletedDirectories); // Then load them in if(numDelObj > 0) { int64_t objs[NUM_DELETED_DIRS_BLOCK]; int64_t toload = numDelObj; while(toload > 0) { // How many in this one? int b = (toload > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(toload)); if(!rf->ReadFullBuffer(objs, b * sizeof(int64_t), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo) } // Add them for(int t = 0; t < b; ++t) { info->mDeletedDirectories.push_back(box_ntoh64(objs[t])); } // Number loaded toload -= b; } } // Final check if(static_cast(info->mDeletedDirectories.size()) != numDelObj) { THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad) } // return it to caller return info; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::CreateForRegeneration(...) // Purpose: Return an object which can be used to save for regeneration. // Created: 23/4/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreInfo::CreateForRegeneration(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t LastObjectID, int64_t BlocksUsed, int64_t BlocksInOldFiles, int64_t BlocksInDeletedFiles, int64_t BlocksInDirectories, int64_t BlockSoftLimit, int64_t BlockHardLimit) { // Generate the filename std::string fn(rRootDir + DIRECTORY_SEPARATOR INFO_FILENAME); // Make new object std::auto_ptr info(new BackupStoreInfo); // Put in basic info info->mAccountID = AccountID; info->mDiscSet = DiscSet; info->mFilename = fn; info->mReadOnly = false; // Insert info starting info info->mClientStoreMarker = 0; info->mLastObjectIDUsed = LastObjectID; info->mBlocksUsed = BlocksUsed; info->mBlocksInOldFiles = BlocksInOldFiles; info->mBlocksInDeletedFiles = BlocksInDeletedFiles; info->mBlocksInDirectories = BlocksInDirectories; info->mBlocksSoftLimit = BlockSoftLimit; info->mBlocksHardLimit = BlockHardLimit; // return it to caller return info; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::Save() // Purpose: Save modified info back to disc // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::Save() { // Make sure we're initialised (although should never come to this) if(mFilename.empty() || mAccountID == -1 || mDiscSet == -1) { THROW_EXCEPTION(BackupStoreException, Internal) } // Can we do this? if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } // Then... open a write file RaidFileWrite rf(mDiscSet, mFilename); rf.Open(true); // allow overwriting // Make header info_StreamFormat hdr; hdr.mMagicValue = htonl(INFO_MAGIC_VALUE); hdr.mAccountID = htonl(mAccountID); hdr.mClientStoreMarker = box_hton64(mClientStoreMarker); hdr.mLastObjectIDUsed = box_hton64(mLastObjectIDUsed); hdr.mBlocksUsed = box_hton64(mBlocksUsed); hdr.mBlocksInOldFiles = box_hton64(mBlocksInOldFiles); hdr.mBlocksInDeletedFiles = box_hton64(mBlocksInDeletedFiles); hdr.mBlocksInDirectories = box_hton64(mBlocksInDirectories); hdr.mBlocksSoftLimit = box_hton64(mBlocksSoftLimit); hdr.mBlocksHardLimit = box_hton64(mBlocksHardLimit); hdr.mCurrentMarkNumber = 0; hdr.mOptionsPresent = 0; hdr.mNumberDeletedDirectories = box_hton64(mDeletedDirectories.size()); // Write header rf.Write(&hdr, sizeof(hdr)); // Write the deleted object list if(mDeletedDirectories.size() > 0) { int64_t objs[NUM_DELETED_DIRS_BLOCK]; int tosave = mDeletedDirectories.size(); std::vector::iterator i(mDeletedDirectories.begin()); while(tosave > 0) { // How many in this one? int b = (tosave > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(tosave)); // Add them for(int t = 0; t < b; ++t) { ASSERT(i != mDeletedDirectories.end()); objs[t] = box_hton64((*i)); i++; } // Write rf.Write(objs, b * sizeof(int64_t)); // Number saved tosave -= b; } } // Commit it to disc, converting it to RAID now rf.Commit(true); // Mark is as not modified mIsModified = false; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::ChangeBlocksUsed(int32_t) // Purpose: Change number of blocks used, by a delta amount // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::ChangeBlocksUsed(int64_t Delta) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } if((mBlocksUsed + Delta) < 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) } mBlocksUsed += Delta; mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::ChangeBlocksInOldFiles(int32_t) // Purpose: Change number of blocks in old files, by a delta amount // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::ChangeBlocksInOldFiles(int64_t Delta) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } if((mBlocksInOldFiles + Delta) < 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) } mBlocksInOldFiles += Delta; mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::ChangeBlocksInDeletedFiles(int32_t) // Purpose: Change number of blocks in deleted files, by a delta amount // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::ChangeBlocksInDeletedFiles(int64_t Delta) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } if((mBlocksInDeletedFiles + Delta) < 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) } mBlocksInDeletedFiles += Delta; mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::ChangeBlocksInDirectories(int32_t) // Purpose: Change number of blocks in directories, by a delta amount // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::ChangeBlocksInDirectories(int64_t Delta) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } if((mBlocksInDirectories + Delta) < 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) } mBlocksInDirectories += Delta; mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::CorrectAllUsedValues(int64_t, int64_t, int64_t, int64_t) // Purpose: Set all the usage counts to specific values -- use for correcting in housekeeping // if something when wrong during the backup connection, and the store info wasn't // saved back to disc. // Created: 15/12/03 // // -------------------------------------------------------------------------- void BackupStoreInfo::CorrectAllUsedValues(int64_t Used, int64_t InOldFiles, int64_t InDeletedFiles, int64_t InDirectories) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } // Set the values mBlocksUsed = Used; mBlocksInOldFiles = InOldFiles; mBlocksInDeletedFiles = InDeletedFiles; mBlocksInDirectories = InDirectories; mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::AddDeletedDirectory(int64_t) // Purpose: Add a directory ID to the deleted list // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::AddDeletedDirectory(int64_t DirID) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } mDeletedDirectories.push_back(DirID); mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::RemovedDeletedDirectory(int64_t) // Purpose: Remove a directory from the deleted list // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreInfo::RemovedDeletedDirectory(int64_t DirID) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } std::vector::iterator i(std::find(mDeletedDirectories.begin(), mDeletedDirectories.end(), DirID)); if(i == mDeletedDirectories.end()) { THROW_EXCEPTION(BackupStoreException, StoreInfoDirNotInList) } mDeletedDirectories.erase(i); mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::ChangeLimits(int64_t, int64_t) // Purpose: Change the soft and hard limits // Created: 15/12/03 // // -------------------------------------------------------------------------- void BackupStoreInfo::ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } mBlocksSoftLimit = BlockSoftLimit; mBlocksHardLimit = BlockHardLimit; mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::AllocateObjectID() // Purpose: Allocate an ID for a new object in the store. // Created: 2003/09/03 // // -------------------------------------------------------------------------- int64_t BackupStoreInfo::AllocateObjectID() { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } if(mLastObjectIDUsed < 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotInitialised) } // Return the next object ID return ++mLastObjectIDUsed; mIsModified = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreInfo::SetClientStoreMarker(int64_t) // Purpose: Sets the client store marker // Created: 2003/10/29 // // -------------------------------------------------------------------------- void BackupStoreInfo::SetClientStoreMarker(int64_t ClientStoreMarker) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) } mClientStoreMarker = ClientStoreMarker; mIsModified = true; } boxbackup/lib/backupstore/BackupStoreCheck2.cpp0000664000175000017500000006260511163676334022373 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreCheck2.cpp // Purpose: More backup store checking // Created: 22/4/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "BackupStoreCheck.h" #include "StoreStructure.h" #include "RaidFileRead.h" #include "RaidFileWrite.h" #include "autogen_BackupStoreException.h" #include "BackupStoreObjectMagic.h" #include "BackupStoreFile.h" #include "BackupStoreFileWire.h" #include "BackupStoreDirectory.h" #include "BackupStoreConstants.h" #include "BackupStoreInfo.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CheckRoot() // Purpose: Check the root directory exists. // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::CheckRoot() { int32_t index = 0; IDBlock *pblock = LookupID(BACKUPSTORE_ROOT_DIRECTORY_ID, index); if(pblock != 0) { // Found it. Which is lucky. Mark it as contained. SetFlags(pblock, index, Flags_IsContained); } else { BOX_WARNING("Root directory doesn't exist"); ++mNumberErrorsFound; if(mFixErrors) { // Create a new root directory CreateBlankDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID, BACKUPSTORE_ROOT_DIRECTORY_ID); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CreateBlankDirectory(int64_t, int64_t) // Purpose: Creates a blank directory // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::CreateBlankDirectory(int64_t DirectoryID, int64_t ContainingDirID) { if(!mFixErrors) { // Don't do anything if we're not supposed to fix errors return; } BackupStoreDirectory dir(DirectoryID, ContainingDirID); // Serialise to disc std::string filename; StoreStructure::MakeObjectFilename(DirectoryID, mStoreRoot, mDiscSetNumber, filename, true /* make sure the dir exists */); RaidFileWrite obj(mDiscSetNumber, filename); obj.Open(false /* don't allow overwriting */); dir.WriteToStream(obj); int64_t size = obj.GetDiscUsageInBlocks(); obj.Commit(true /* convert to raid now */); // Record the fact we've done this mDirsAdded.insert(DirectoryID); // Add to sizes mBlocksUsed += size; mBlocksInDirectories += size; } class BackupStoreDirectoryFixer { private: BackupStoreDirectory mDirectory; std::string mFilename; std::string mStoreRoot; int mDiscSetNumber; public: BackupStoreDirectoryFixer(std::string storeRoot, int discSetNumber, int64_t ID); void InsertObject(int64_t ObjectID, bool IsDirectory, int32_t lostDirNameSerial); ~BackupStoreDirectoryFixer(); }; // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::CheckUnattachedObjects() // Purpose: Check for objects which aren't attached to anything // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::CheckUnattachedObjects() { typedef std::map fixers_t; typedef std::pair fixer_pair_t; fixers_t fixers; // Scan all objects, finding ones which have no container for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) { IDBlock *pblock = i->second; int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; for(int e = 0; e < bentries; ++e) { uint8_t flags = GetFlags(pblock, e); if((flags & Flags_IsContained) == 0) { // Unattached object... BOX_WARNING("Object " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " is unattached."); ++mNumberErrorsFound; // What's to be done? int64_t putIntoDirectoryID = 0; if((flags & Flags_IsDir) == Flags_IsDir) { // Directory. Just put into lost and found. putIntoDirectoryID = GetLostAndFoundDirID(); } else { // File. Only attempt to attach it somewhere if it isn't a patch { int64_t diffFromObjectID = 0; std::string filename; StoreStructure::MakeObjectFilename(pblock->mID[e], mStoreRoot, mDiscSetNumber, filename, false /* don't attempt to make sure the dir exists */); // The easiest way to do this is to verify it again. Not such a bad penalty, because // this really shouldn't be done very often. { std::auto_ptr file(RaidFileRead::Open(mDiscSetNumber, filename)); BackupStoreFile::VerifyEncodedFileFormat(*file, &diffFromObjectID); } // If not zero, then it depends on another file, which may or may not be available. // Just delete it to be safe. if(diffFromObjectID != 0) { BOX_WARNING("Object " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " is unattached, and is a patch. Deleting, cannot reliably recover."); // Delete this object instead if(mFixErrors) { RaidFileWrite del(mDiscSetNumber, filename); del.Delete(); } // Move on to next item continue; } } // Files contain their original filename, so perhaps the orginal directory still exists, // or we can infer the existance of a directory? // Look for a matching entry in the mDirsWhichContainLostDirs map. // Can't do this with a directory, because the name just wouldn't be known, which is // pretty useless as bbackupd would just delete it. So better to put it in lost+found // where the admin can do something about it. int32_t dirindex; IDBlock *pdirblock = LookupID(pblock->mContainer[e], dirindex); if(pdirblock != 0) { // Something with that ID has been found. Is it a directory? if(GetFlags(pdirblock, dirindex) & Flags_IsDir) { // Directory exists, add to that one putIntoDirectoryID = pblock->mContainer[e]; } else { // Not a directory. Use lost and found dir putIntoDirectoryID = GetLostAndFoundDirID(); } } else if(mDirsAdded.find(pblock->mContainer[e]) != mDirsAdded.end() || TryToRecreateDirectory(pblock->mContainer[e])) { // The directory reappeared, or was created somehow elsewhere putIntoDirectoryID = pblock->mContainer[e]; } else { putIntoDirectoryID = GetLostAndFoundDirID(); } } ASSERT(putIntoDirectoryID != 0); if (!mFixErrors) { continue; } BackupStoreDirectoryFixer* pFixer; fixers_t::iterator fi = fixers.find(putIntoDirectoryID); if (fi == fixers.end()) { // no match, create a new one pFixer = new BackupStoreDirectoryFixer( mStoreRoot, mDiscSetNumber, putIntoDirectoryID); fixers.insert(fixer_pair_t( putIntoDirectoryID, pFixer)); } else { pFixer = fi->second; } int32_t lostDirNameSerial = 0; if(flags & Flags_IsDir) { lostDirNameSerial = mLostDirNameSerial++; } // Add it to the directory pFixer->InsertObject(pblock->mID[e], ((flags & Flags_IsDir) == Flags_IsDir), lostDirNameSerial); } } } // clean up all the fixers. Deleting them commits them automatically. for (fixers_t::iterator i = fixers.begin(); i != fixers.end(); i++) { BackupStoreDirectoryFixer* pFixer = i->second; delete pFixer; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::TryToRecreateDirectory(int64_t) // Purpose: Recreate a missing directory // Created: 22/4/04 // // -------------------------------------------------------------------------- bool BackupStoreCheck::TryToRecreateDirectory(int64_t MissingDirectoryID) { // During the directory checking phase, a map of "missing directory" to // containing directory was built. If we can find it here, then it's // something which can be recreated! std::map::iterator missing( mDirsWhichContainLostDirs.find(MissingDirectoryID)); if(missing == mDirsWhichContainLostDirs.end()) { // Not a missing directory, can't recreate. return false; } // Can recreate this! Wooo! if(!mFixErrors) { BOX_WARNING("Missing directory " << BOX_FORMAT_OBJECTID(MissingDirectoryID) << " could be recreated."); mDirsAdded.insert(MissingDirectoryID); return true; } BOX_WARNING("Recreating missing directory " << BOX_FORMAT_OBJECTID(MissingDirectoryID)); // Create a blank directory BackupStoreDirectory dir(MissingDirectoryID, missing->second /* containing dir ID */); // Note that this directory already contains a directory entry pointing to // this dir, so it doesn't have to be added. // Serialise to disc std::string filename; StoreStructure::MakeObjectFilename(MissingDirectoryID, mStoreRoot, mDiscSetNumber, filename, true /* make sure the dir exists */); RaidFileWrite root(mDiscSetNumber, filename); root.Open(false /* don't allow overwriting */); dir.WriteToStream(root); root.Commit(true /* convert to raid now */); // Record the fact we've done this mDirsAdded.insert(MissingDirectoryID); // Remove the entry from the map, so this doesn't happen again mDirsWhichContainLostDirs.erase(missing); return true; } BackupStoreDirectoryFixer::BackupStoreDirectoryFixer(std::string storeRoot, int discSetNumber, int64_t ID) : mStoreRoot(storeRoot), mDiscSetNumber(discSetNumber) { // Generate filename StoreStructure::MakeObjectFilename(ID, mStoreRoot, mDiscSetNumber, mFilename, false /* don't make sure the dir exists */); // Read it in std::auto_ptr file( RaidFileRead::Open(mDiscSetNumber, mFilename)); mDirectory.ReadFromStream(*file, IOStream::TimeOutInfinite); } void BackupStoreDirectoryFixer::InsertObject(int64_t ObjectID, bool IsDirectory, int32_t lostDirNameSerial) { // Data for the object BackupStoreFilename objectStoreFilename; int64_t modTime = 100; // something which isn't zero or a special time int32_t sizeInBlocks = 0; // suitable for directories if(IsDirectory) { // Directory -- simply generate a name for it. char name[32]; ::sprintf(name, "dir%08x", lostDirNameSerial); objectStoreFilename.SetAsClearFilename(name); } else { // Files require a little more work... // Open file std::string fileFilename; StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, mDiscSetNumber, fileFilename, false /* don't make sure the dir exists */); std::auto_ptr file( RaidFileRead::Open(mDiscSetNumber, fileFilename)); // Fill in size information sizeInBlocks = file->GetDiscUsageInBlocks(); // Read in header file_StreamFormat hdr; if(file->Read(&hdr, sizeof(hdr)) != sizeof(hdr) || (ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1 #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0 #endif )) { // This should never happen, everything has been // checked before. THROW_EXCEPTION(BackupStoreException, Internal) } // This tells us nice things modTime = box_ntoh64(hdr.mModificationTime); // And the filename comes next objectStoreFilename.ReadFromStream(*file, IOStream::TimeOutInfinite); } // Add a new entry in an appropriate place mDirectory.AddUnattactedObject(objectStoreFilename, modTime, ObjectID, sizeInBlocks, IsDirectory?(BackupStoreDirectory::Entry::Flags_Dir):(BackupStoreDirectory::Entry::Flags_File)); } BackupStoreDirectoryFixer::~BackupStoreDirectoryFixer() { // Fix any flags which have been broken, which there's a good chance of doing mDirectory.CheckAndFix(); // Write it out RaidFileWrite root(mDiscSetNumber, mFilename); root.Open(true /* allow overwriting */); mDirectory.WriteToStream(root); root.Commit(true /* convert to raid now */); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::GetLostAndFoundDirID() // Purpose: Returns the ID of the lost and found directory, creating it if necessary // Created: 22/4/04 // // -------------------------------------------------------------------------- int64_t BackupStoreCheck::GetLostAndFoundDirID() { // Already allocated it? if(mLostAndFoundDirectoryID != 0) { return mLostAndFoundDirectoryID; } if(!mFixErrors) { // The result will never be used anyway if errors aren't being fixed return 1; } // Load up the root directory BackupStoreDirectory dir; std::string filename; StoreStructure::MakeObjectFilename(BACKUPSTORE_ROOT_DIRECTORY_ID, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); { std::auto_ptr file(RaidFileRead::Open(mDiscSetNumber, filename)); dir.ReadFromStream(*file, IOStream::TimeOutInfinite); } // Find a suitable name BackupStoreFilename lostAndFound; int n = 0; while(true) { char name[32]; ::sprintf(name, "lost+found%d", n++); lostAndFound.SetAsClearFilename(name); if(!dir.NameInUse(lostAndFound)) { // Found a name which can be used BOX_WARNING("Lost and found dir has name " << name); break; } } // Allocate an ID int64_t id = mLastIDInInfo + 1; // Create a blank directory CreateBlankDirectory(id, BACKUPSTORE_ROOT_DIRECTORY_ID); // Add an entry for it dir.AddEntry(lostAndFound, 0, id, 0, BackupStoreDirectory::Entry::Flags_Dir, 0); // Write out root dir RaidFileWrite root(mDiscSetNumber, filename); root.Open(true /* allow overwriting */); dir.WriteToStream(root); root.Commit(true /* convert to raid now */); // Store mLostAndFoundDirectoryID = id; // Tell caller return mLostAndFoundDirectoryID; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::FixDirsWithWrongContainerID() // Purpose: Rewrites container IDs where required // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::FixDirsWithWrongContainerID() { if(!mFixErrors) { // Don't do anything if we're not supposed to fix errors return; } // Run through things which need fixing for(std::vector::iterator i(mDirsWithWrongContainerID.begin()); i != mDirsWithWrongContainerID.end(); ++i) { int32_t index = 0; IDBlock *pblock = LookupID(*i, index); if(pblock == 0) continue; // Load in BackupStoreDirectory dir; std::string filename; StoreStructure::MakeObjectFilename(*i, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); { std::auto_ptr file(RaidFileRead::Open(mDiscSetNumber, filename)); dir.ReadFromStream(*file, IOStream::TimeOutInfinite); } // Adjust container ID dir.SetContainerID(pblock->mContainer[index]); // Write it out RaidFileWrite root(mDiscSetNumber, filename); root.Open(true /* allow overwriting */); dir.WriteToStream(root); root.Commit(true /* convert to raid now */); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::FixDirsWithLostDirs() // Purpose: Fix directories // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::FixDirsWithLostDirs() { if(!mFixErrors) { // Don't do anything if we're not supposed to fix errors return; } // Run through things which need fixing for(std::map::iterator i(mDirsWhichContainLostDirs.begin()); i != mDirsWhichContainLostDirs.end(); ++i) { int32_t index = 0; IDBlock *pblock = LookupID(i->second, index); if(pblock == 0) continue; // Load in BackupStoreDirectory dir; std::string filename; StoreStructure::MakeObjectFilename(i->second, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); { std::auto_ptr file(RaidFileRead::Open(mDiscSetNumber, filename)); dir.ReadFromStream(*file, IOStream::TimeOutInfinite); } // Delete the dodgy entry dir.DeleteEntry(i->first); // Fix it up dir.CheckAndFix(); // Write it out RaidFileWrite root(mDiscSetNumber, filename); root.Open(true /* allow overwriting */); dir.WriteToStream(root); root.Commit(true /* convert to raid now */); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::WriteNewStoreInfo() // Purpose: Regenerate store info // Created: 23/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::WriteNewStoreInfo() { // Attempt to load the existing store info file std::auto_ptr poldInfo; try { poldInfo.reset(BackupStoreInfo::Load(mAccountID, mStoreRoot, mDiscSetNumber, true /* read only */).release()); } catch(...) { BOX_WARNING("Load of existing store info failed, regenerating."); ++mNumberErrorsFound; } // Minimum soft and hard limits int64_t minSoft = ((mBlocksUsed * 11) / 10) + 1024; int64_t minHard = ((minSoft * 11) / 10) + 1024; // Need to do anything? if(poldInfo.get() != 0 && mNumberErrorsFound == 0 && poldInfo->GetAccountID() == mAccountID) { // Leave the store info as it is, no need to alter it because nothing really changed, // and the only essential thing was that the account ID was correct, which is was. return; } // NOTE: We will always build a new store info, so the client store marker gets changed. // Work out the new limits int64_t softLimit = minSoft; int64_t hardLimit = minHard; if(poldInfo.get() != 0 && poldInfo->GetBlocksSoftLimit() > minSoft) { softLimit = poldInfo->GetBlocksSoftLimit(); } else { BOX_WARNING("Soft limit for account changed to ensure housekeeping doesn't delete files on next run."); } if(poldInfo.get() != 0 && poldInfo->GetBlocksHardLimit() > minHard) { hardLimit = poldInfo->GetBlocksHardLimit(); } else { BOX_WARNING("Hard limit for account changed to ensure housekeeping doesn't delete files on next run."); } // Object ID int64_t lastObjID = mLastIDInInfo; if(mLostAndFoundDirectoryID != 0) { mLastIDInInfo++; } // Build a new store info std::auto_ptr info(BackupStoreInfo::CreateForRegeneration( mAccountID, mStoreRoot, mDiscSetNumber, lastObjID, mBlocksUsed, mBlocksInOldFiles, mBlocksInDeletedFiles, mBlocksInDirectories, softLimit, hardLimit)); // Save to disc? if(mFixErrors) { info->Save(); BOX_NOTICE("New store info file written successfully."); } } #define FMT_OID(x) BOX_FORMAT_OBJECTID(x) #define FMT_i BOX_FORMAT_OBJECTID((*i)->GetObjectID()) // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::CheckAndFix() // Purpose: Check the directory for obvious logical problems, and fix them. // Return true if the directory was changed. // Created: 22/4/04 // // -------------------------------------------------------------------------- bool BackupStoreDirectory::CheckAndFix() { bool changed = false; // Check that if a file depends on a new version, that version is in this directory { std::vector::iterator i(mEntries.begin()); for(; i != mEntries.end(); ++i) { int64_t dependsNewer = (*i)->GetDependsNewer(); if(dependsNewer != 0) { BackupStoreDirectory::Entry *newerEn = FindEntryByID(dependsNewer); if(newerEn == 0) { // Depends on something, but it isn't there. BOX_TRACE("Entry id " << FMT_i << " removed because depends " "on newer version " << FMT_OID(dependsNewer) << " which doesn't exist"); // Remove delete *i; mEntries.erase(i); // Start again at the beginning of the vector, the iterator is now invalid i = mEntries.begin(); // Mark as changed changed = true; } else { // Check that newerEn has it marked if(newerEn->GetDependsOlder() != (*i)->GetObjectID()) { // Wrong entry BOX_TRACE("Entry id " << FMT_OID(dependsNewer) << ", correcting DependsOlder to " << FMT_i << ", was " << FMT_OID(newerEn->GetDependsOlder())); newerEn->SetDependsOlder((*i)->GetObjectID()); // Mark as changed changed = true; } } } } } // Check that if a file has a dependency marked, it exists, and remove it if it doesn't { std::vector::iterator i(mEntries.begin()); for(; i != mEntries.end(); ++i) { int64_t dependsOlder = (*i)->GetDependsOlder(); if(dependsOlder != 0 && FindEntryByID(dependsOlder) == 0) { // Has an older version marked, but this doesn't exist. Remove this mark BOX_TRACE("Entry id " << FMT_i << " was marked as depended on by " << FMT_OID(dependsOlder) << ", " "which doesn't exist, dependency " "info cleared"); (*i)->SetDependsOlder(0); // Mark as changed changed = true; } } } bool ch = false; do { // Reset change marker ch = false; // Search backwards -- so see newer versions first std::vector::iterator i(mEntries.end()); if(i == mEntries.begin()) { // Directory is empty, stop now return changed; // changed flag } // Records of things seen std::set idsEncountered; std::set filenamesEncountered; do { // Look at previous --i; bool removeEntry = false; if((*i) == 0) { BOX_TRACE("Remove because null pointer found"); removeEntry = true; } else { bool isDir = (((*i)->GetFlags() & Entry::Flags_Dir) == Entry::Flags_Dir); // Check mutually exclusive flags if(isDir && (((*i)->GetFlags() & Entry::Flags_File) == Entry::Flags_File)) { // Bad! Unset the file flag BOX_TRACE("Entry " << FMT_i << ": File flag and dir flag both set"); (*i)->RemoveFlags(Entry::Flags_File); changed = true; } // Check... if(idsEncountered.find((*i)->GetObjectID()) != idsEncountered.end()) { // ID already seen, or type doesn't match BOX_TRACE("Entry " << FMT_i << ": Remove because ID already seen"); removeEntry = true; } else { // Haven't already seen this ID, remember it idsEncountered.insert((*i)->GetObjectID()); // Check to see if the name has already been encountered -- if not, then it // needs to have the old version flag set if(filenamesEncountered.find((*i)->GetName().GetEncodedFilename()) != filenamesEncountered.end()) { // Seen before -- check old version flag set if(((*i)->GetFlags() & Entry::Flags_OldVersion) != Entry::Flags_OldVersion && ((*i)->GetFlags() & Entry::Flags_Deleted) == 0) { // Not set, set it BOX_TRACE("Entry " << FMT_i << ": Set old flag"); (*i)->AddFlags(Entry::Flags_OldVersion); changed = true; } } else { // Check old version flag NOT set if(((*i)->GetFlags() & Entry::Flags_OldVersion) == Entry::Flags_OldVersion) { // Set, unset it BOX_TRACE("Entry " << FMT_i << ": Old flag unset"); (*i)->RemoveFlags(Entry::Flags_OldVersion); changed = true; } // Remember filename filenamesEncountered.insert((*i)->GetName().GetEncodedFilename()); } } } if(removeEntry) { // Mark something as changed, in loop ch = true; // Mark something as globally changed changed = true; // erase the thing from the list Entry *pentry = (*i); mEntries.erase(i); // And delete the entry object delete pentry; // Stop going around this loop, as the iterator is now invalid break; } } while(i != mEntries.begin()); } while(ch != false); return changed; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::AddUnattactedObject(...) // Purpose: Adds an object which is currently unattached. Assume that CheckAndFix() will be called afterwards. // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreDirectory::AddUnattactedObject(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags) { Entry *pnew = new Entry(rName, ModificationTime, ObjectID, SizeInBlocks, Flags, ModificationTime /* use as attr mod time too */); try { // Want to order this just before the first object which has a higher ID, // which is the place it's most likely to be correct. std::vector::iterator i(mEntries.begin()); for(; i != mEntries.end(); ++i) { if((*i)->GetObjectID() > ObjectID) { // Found a good place to insert it break; } } if(i == mEntries.end()) { mEntries.push_back(pnew); } else { mEntries.insert(i, 1 /* just the one copy */, pnew); } } catch(...) { delete pnew; throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::NameInUse(const BackupStoreFilename &) // Purpose: Returns true if the name is currently in use in the directory // Created: 22/4/04 // // -------------------------------------------------------------------------- bool BackupStoreDirectory::NameInUse(const BackupStoreFilename &rName) { for(std::vector::iterator i(mEntries.begin()); i != mEntries.end(); ++i) { if((*i)->GetName() == rName) { return true; } } return false; } boxbackup/lib/common/0000775000175000017500000000000011652362374015361 5ustar siretartsiretartboxbackup/lib/common/BoxTime.h0000664000175000017500000000222111163700314017061 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxTime.h // Purpose: How time is represented // Created: 2003/10/08 // // -------------------------------------------------------------------------- #ifndef BOXTIME__H #define BOXTIME__H // Time is presented as an unsigned 64 bit integer, in microseconds typedef uint64_t box_time_t; #define NANO_SEC_IN_SEC (1000000000LL) #define NANO_SEC_IN_USEC (1000) #define NANO_SEC_IN_USEC_LL (1000LL) #define MICRO_SEC_IN_SEC (1000000) #define MICRO_SEC_IN_SEC_LL (1000000LL) #define MILLI_SEC_IN_NANO_SEC (1000) #define MILLI_SEC_IN_NANO_SEC_LL (1000LL) box_time_t GetCurrentBoxTime(); inline box_time_t SecondsToBoxTime(time_t Seconds) { return ((box_time_t)Seconds * MICRO_SEC_IN_SEC_LL); } inline time_t BoxTimeToSeconds(box_time_t Time) { return Time / MICRO_SEC_IN_SEC_LL; } inline uint64_t BoxTimeToMilliSeconds(box_time_t Time) { return Time / MILLI_SEC_IN_NANO_SEC_LL; } inline uint64_t BoxTimeToMicroSeconds(box_time_t Time) { return Time; } std::string FormatTime(box_time_t time, bool includeDate, bool showMicros = false); #endif // BOXTIME__H boxbackup/lib/common/Logging.h0000664000175000017500000002105311445744203017114 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Logging.h // Purpose: Generic logging core routines declarations and macros // Created: 2006/12/16 // // -------------------------------------------------------------------------- #ifndef LOGGING__H #define LOGGING__H #include #include #include #include #include #include "FileStream.h" #define BOX_LOG(level, stuff) \ { \ std::ostringstream _box_log_line; \ _box_log_line << stuff; \ Logging::Log(level, __FILE__, __LINE__, _box_log_line.str()); \ } #define BOX_SYSLOG(level, stuff) \ { \ std::ostringstream _box_log_line; \ _box_log_line << stuff; \ Logging::LogToSyslog(level, __FILE__, __LINE__, _box_log_line.str()); \ } #define BOX_FATAL(stuff) BOX_LOG(Log::FATAL, stuff) #define BOX_ERROR(stuff) BOX_LOG(Log::ERROR, stuff) #define BOX_WARNING(stuff) BOX_LOG(Log::WARNING, stuff) #define BOX_NOTICE(stuff) BOX_LOG(Log::NOTICE, stuff) #define BOX_INFO(stuff) BOX_LOG(Log::INFO, stuff) #define BOX_TRACE(stuff) \ if (Logging::IsEnabled(Log::TRACE)) \ { BOX_LOG(Log::TRACE, stuff) } #define BOX_SYS_ERROR(stuff) \ stuff << ": " << std::strerror(errno) << " (" << errno << ")" #define BOX_LOG_SYS_WARNING(stuff) \ BOX_WARNING(BOX_SYS_ERROR(stuff)) #define BOX_LOG_SYS_ERROR(stuff) \ BOX_ERROR(BOX_SYS_ERROR(stuff)) #define BOX_LOG_SYS_FATAL(stuff) \ BOX_FATAL(BOX_SYS_ERROR(stuff)) #define LOG_AND_THROW_ERROR(message, filename, exception, subtype) \ BOX_LOG_SYS_ERROR(message << ": " << filename); \ THROW_EXCEPTION_MESSAGE(exception, subtype, \ BOX_SYS_ERROR(message << ": " << filename)); inline std::string GetNativeErrorMessage() { #ifdef WIN32 return GetErrorMessage(GetLastError()); #else std::ostringstream _box_log_line; _box_log_line << std::strerror(errno) << " (" << errno << ")"; return _box_log_line.str(); #endif } #ifdef WIN32 #define BOX_LOG_WIN_ERROR(stuff) \ BOX_ERROR(stuff << ": " << GetErrorMessage(GetLastError())) #define BOX_LOG_WIN_WARNING(stuff) \ BOX_WARNING(stuff << ": " << GetErrorMessage(GetLastError())) #define BOX_LOG_WIN_ERROR_NUMBER(stuff, number) \ BOX_ERROR(stuff << ": " << GetErrorMessage(number)) #define BOX_LOG_WIN_WARNING_NUMBER(stuff, number) \ BOX_WARNING(stuff << ": " << GetErrorMessage(number)) #define BOX_LOG_NATIVE_ERROR(stuff) BOX_LOG_WIN_ERROR(stuff) #define BOX_LOG_NATIVE_WARNING(stuff) BOX_LOG_WIN_WARNING(stuff) #else #define BOX_LOG_NATIVE_ERROR(stuff) BOX_LOG_SYS_ERROR(stuff) #define BOX_LOG_NATIVE_WARNING(stuff) BOX_LOG_SYS_WARNING(stuff) #endif #define BOX_LOG_SOCKET_ERROR(_type, _name, _port, stuff) \ BOX_LOG_NATIVE_ERROR(stuff << " (type " << _type << ", name " << \ _name << ", port " << _port << ")") #define BOX_FORMAT_HEX32(number) \ std::hex << \ std::showbase << \ std::internal << \ std::setw(10) << \ std::setfill('0') << \ (number) << \ std::dec #define BOX_FORMAT_ACCOUNT(accno) \ BOX_FORMAT_HEX32(accno) #define BOX_FORMAT_OBJECTID(objectid) \ std::hex << \ std::showbase << \ (objectid) << \ std::dec #define BOX_FORMAT_TIMESPEC(timespec) \ timespec.tv_sec << \ std::setw(6) << \ timespec.tv_usec #undef ERROR namespace Log { enum Level { NOTHING = 1, FATAL, ERROR, WARNING, NOTICE, INFO, TRACE, EVERYTHING, INVALID = -1 }; } // -------------------------------------------------------------------------- // // Class // Name: Logger // Purpose: Abstract base class for log targets // Created: 2006/12/16 // // -------------------------------------------------------------------------- class Logger { private: Log::Level mCurrentLevel; public: Logger(); Logger(Log::Level level); virtual ~Logger(); virtual bool Log(Log::Level level, const std::string& rFile, int line, std::string& rMessage) = 0; void Filter(Log::Level level) { mCurrentLevel = level; } virtual const char* GetType() = 0; Log::Level GetLevel() { return mCurrentLevel; } virtual void SetProgramName(const std::string& rProgramName) = 0; }; // -------------------------------------------------------------------------- // // Class // Name: Console // Purpose: Console logging target // Created: 2006/12/16 // // -------------------------------------------------------------------------- class Console : public Logger { private: static bool sShowTag; static bool sShowTime; static bool sShowTimeMicros; static bool sShowPID; static std::string sTag; public: virtual bool Log(Log::Level level, const std::string& rFile, int line, std::string& rMessage); virtual const char* GetType() { return "Console"; } virtual void SetProgramName(const std::string& rProgramName); static void SetShowTag(bool enabled); static void SetShowTime(bool enabled); static void SetShowTimeMicros(bool enabled); static void SetShowPID(bool enabled); }; // -------------------------------------------------------------------------- // // Class // Name: Syslog // Purpose: Syslog (or Windows Event Viewer) logging target // Created: 2006/12/16 // // -------------------------------------------------------------------------- class Syslog : public Logger { private: std::string mName; int mFacility; public: Syslog(); virtual ~Syslog(); virtual bool Log(Log::Level level, const std::string& rFile, int line, std::string& rMessage); virtual const char* GetType() { return "Syslog"; } virtual void SetProgramName(const std::string& rProgramName); virtual void SetFacility(int facility); static int GetNamedFacility(const std::string& rFacility); }; // -------------------------------------------------------------------------- // // Class // Name: Logging // Purpose: Static logging helper, keeps track of enabled loggers // and distributes log messages to them. // Created: 2006/12/16 // // -------------------------------------------------------------------------- class Logging { private: static std::vector sLoggers; static bool sLogToSyslog, sLogToConsole; static std::string sContext; static bool sContextSet; static Console* spConsole; static Syslog* spSyslog; static Log::Level sGlobalLevel; static Logging sGlobalLogging; static std::string sProgramName; public: Logging (); ~Logging(); static void ToSyslog (bool enabled); static void ToConsole (bool enabled); static void FilterSyslog (Log::Level level); static void FilterConsole (Log::Level level); static void Add (Logger* pNewLogger); static void Remove (Logger* pOldLogger); static void Log(Log::Level level, const std::string& rFile, int line, const std::string& rMessage); static void LogToSyslog(Log::Level level, const std::string& rFile, int line, const std::string& rMessage); static void SetContext(std::string context); static void ClearContext(); static void SetGlobalLevel(Log::Level level) { sGlobalLevel = level; } static Log::Level GetGlobalLevel() { return sGlobalLevel; } static Log::Level GetNamedLevel(const std::string& rName); static bool IsEnabled(Log::Level level) { return (int)sGlobalLevel >= (int)level; } static void SetProgramName(const std::string& rProgramName); static std::string GetProgramName() { return sProgramName; } static void SetFacility(int facility); class Guard { private: Log::Level mOldLevel; public: Guard(Log::Level newLevel) { mOldLevel = Logging::GetGlobalLevel(); Logging::SetGlobalLevel(newLevel); } ~Guard() { Logging::SetGlobalLevel(mOldLevel); } }; class Tagger { private: std::string mOldTag; public: Tagger(const std::string& rTempTag) { mOldTag = Logging::GetProgramName(); Logging::SetProgramName(mOldTag + " " + rTempTag); } ~Tagger() { Logging::SetProgramName(mOldTag); } }; }; class FileLogger : public Logger { private: FileStream mLogFile; FileLogger(const FileLogger& forbidden) : mLogFile("") { /* do not call */ } public: FileLogger(const std::string& rFileName, Log::Level Level) : Logger(Level), mLogFile(rFileName, O_WRONLY | O_CREAT | O_APPEND) { } virtual bool Log(Log::Level Level, const std::string& rFile, int Line, std::string& rMessage); virtual const char* GetType() { return "FileLogger"; } virtual void SetProgramName(const std::string& rProgramName) { } }; class HideExceptionMessageGuard { public: HideExceptionMessageGuard() { mOldHiddenState = sHiddenState; sHiddenState = true; } ~HideExceptionMessageGuard() { sHiddenState = mOldHiddenState; } static bool ExceptionsHidden() { return sHiddenState; } private: static bool sHiddenState; bool mOldHiddenState; }; std::string PrintEscapedBinaryData(const std::string& rInput); #endif // LOGGING__H boxbackup/lib/common/ZeroStream.h0000664000175000017500000000165310614700110017607 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ZeroStream.h // Purpose: An IOStream which returns all zeroes up to a certain size // Created: 2007/04/28 // // -------------------------------------------------------------------------- #ifndef ZEROSTREAM__H #define ZEROSTREAM__H #include "IOStream.h" class ZeroStream : public IOStream { private: IOStream::pos_type mSize, mPosition; public: ZeroStream(IOStream::pos_type mSize); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(IOStream::pos_type Offset, int SeekType); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: ZeroStream(const ZeroStream &rToCopy); }; #endif // ZEROSTREAM__H boxbackup/lib/common/BoxPlatform.h0000664000175000017500000001142011175071224017754 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxPlatform.h // Purpose: Specifies what each platform supports in more detail, and includes // extra files to get basic support for types. // Created: 2003/09/06 // // -------------------------------------------------------------------------- #ifndef BOXPLATFORM__H #define BOXPLATFORM__H #ifdef WIN32 #define DIRECTORY_SEPARATOR "\\" #define DIRECTORY_SEPARATOR_ASCHAR '\\' #else #define DIRECTORY_SEPARATOR "/" #define DIRECTORY_SEPARATOR_ASCHAR '/' #endif #define PLATFORM_DEV_NULL "/dev/null" #ifdef _MSC_VER #include "BoxConfig-MSVC.h" #include "BoxVersion.h" #else #include "BoxConfig.h" #endif #ifdef WIN32 #ifdef __MSVCRT_VERSION__ #if __MSVCRT_VERSION__ < 0x0601 #error Must include Box.h before sys/types.h #endif #else // need msvcrt version 6.1 or higher for _gmtime64() // must define this before importing #define __MSVCRT_VERSION__ 0x0601 #endif #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_INTTYPES_H #include #else #ifdef HAVE_STDINT_H #include #endif #endif // Slight hack; disable interception in raidfile test on Darwin and Windows #if defined __APPLE__ || defined WIN32 // TODO: Replace with autoconf test #define PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE #endif // Disable memory testing under Darwin, it just doesn't like it very much. #ifdef __APPLE__ // TODO: We really should get some decent leak detection code. #define PLATFORM_DISABLE_MEM_LEAK_TESTING #endif // Darwin also has a weird idea of permissions and dates on symlinks: // perms are fixed at creation time by your umask, and dates can't be // changed. This breaks unit tests if we try to compare these things. // See: http://lists.apple.com/archives/darwin-kernel/2006/Dec/msg00057.html #ifdef __APPLE__ #define PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE #endif // Find out if credentials on UNIX sockets can be obtained #ifdef HAVE_GETPEEREID // #elif HAVE_DECL_SO_PEERCRED // #elif defined HAVE_UCRED_H && HAVE_GETPEERUCRED // #else #define PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET #endif #ifdef HAVE_DEFINE_PRAGMA // set packing to one bytes (can't use push/pop on gcc) #define BEGIN_STRUCTURE_PACKING_FOR_WIRE #pragma pack(1) // Use default packing #define END_STRUCTURE_PACKING_FOR_WIRE #pragma pack() #else #define STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #endif // Handle differing xattr APIs #ifdef HAVE_SYS_XATTR_H #if !defined(HAVE_LLISTXATTR) && defined(HAVE_LISTXATTR) && HAVE_DECL_XATTR_NOFOLLOW #define llistxattr(a,b,c) listxattr(a,b,c,XATTR_NOFOLLOW) #endif #if !defined(HAVE_LGETXATTR) && defined(HAVE_GETXATTR) && HAVE_DECL_XATTR_NOFOLLOW #define lgetxattr(a,b,c,d) getxattr(a,b,c,d,0,XATTR_NOFOLLOW) #endif #if !defined(HAVE_LSETXATTR) && defined(HAVE_SETXATTR) && HAVE_DECL_XATTR_NOFOLLOW #define lsetxattr(a,b,c,d,e) setxattr(a,b,c,d,0,(e)|XATTR_NOFOLLOW) #endif #endif #if defined WIN32 && !defined __MINGW32__ typedef __int8 int8_t; typedef __int16 int16_t; typedef __int32 int32_t; typedef __int64 int64_t; typedef unsigned __int8 u_int8_t; typedef unsigned __int16 u_int16_t; typedef unsigned __int32 u_int32_t; typedef unsigned __int64 u_int64_t; #define HAVE_U_INT8_T #define HAVE_U_INT16_T #define HAVE_U_INT32_T #define HAVE_U_INT64_T #endif // WIN32 && !__MINGW32__ // Define missing types #ifndef HAVE_UINT8_T typedef u_int8_t uint8_t; #endif #ifndef HAVE_UINT16_T typedef u_int16_t uint16_t; #endif #ifndef HAVE_UINT32_T typedef u_int32_t uint32_t; #endif #ifndef HAVE_UINT64_T typedef u_int64_t uint64_t; #endif #ifndef HAVE_U_INT8_T typedef uint8_t u_int8_t; #endif #ifndef HAVE_U_INT16_T typedef uint16_t u_int16_t; #endif #ifndef HAVE_U_INT32_T typedef uint32_t u_int32_t; #endif #ifndef HAVE_U_INT64_T typedef uint64_t u_int64_t; #endif #if !HAVE_DECL_INFTIM #define INFTIM -1 #endif // for Unix compatibility with Windows :-) #ifndef O_BINARY #define O_BINARY 0 #endif #ifdef WIN32 typedef u_int64_t InodeRefType; #else typedef ino_t InodeRefType; #endif #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #endif #include "emu.h" #ifdef WIN32 #define INVALID_FILE INVALID_HANDLE_VALUE typedef HANDLE tOSFileHandle; #else #define INVALID_FILE -1 typedef int tOSFileHandle; #endif // Solaris has no dirfd(x) macro or function, and we need one for // intercept tests. We cannot define macros with arguments directly // using AC_DEFINE, so do it here instead of in configure.ac. #if ! defined PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE && ! HAVE_DECL_DIRFD #ifdef HAVE_DIR_D_FD #define dirfd(x) (x)->d_fd #elif defined HAVE_DIR_DD_FD #define dirfd(x) (x)->dd_fd #else #error No way to get file descriptor from DIR structure #endif #endif #endif // BOXPLATFORM__H boxbackup/lib/common/BoxTimeToText.cpp0000664000175000017500000000320710514176575020610 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxTimeToText.cpp // Purpose: Convert box time to text // Created: 2003/10/10 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include "BoxTimeToText.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BoxTimeToISO8601String(box_time_t, bool) // Purpose: Convert a 64 bit box time to a ISO 8601 compliant // string, either in local or UTC time // Created: 2003/10/10 // // -------------------------------------------------------------------------- std::string BoxTimeToISO8601String(box_time_t Time, bool localTime) { time_t timeInSecs = BoxTimeToSeconds(Time); char str[128]; // more than enough space #ifdef WIN32 struct tm *time; __time64_t winTime = timeInSecs; if(localTime) { time = _localtime64(&winTime); } else { time = _gmtime64(&winTime); } if(time == NULL) { // ::sprintf(str, "%016I64x ", bob); return std::string("unable to convert time"); } sprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); #else // ! WIN32 struct tm time; if(localTime) { localtime_r(&timeInSecs, &time); } else { gmtime_r(&timeInSecs, &time); } sprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d", time.tm_year + 1900, time.tm_mon + 1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); #endif // WIN32 return std::string(str); } boxbackup/lib/common/ConversionString.cpp0000664000175000017500000000565510670344522021406 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ConversionString.cpp // Purpose: Conversions to and from strings // Created: 9/4/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include "Conversion.h" #include "autogen_ConversionException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BoxConvert::_ConvertStringToInt(const char *, int) // Purpose: Convert from string to integer, with range checking. // Always does signed -- no point in unsigned as C++ type checking // isn't up to handling it properly. // If a null pointer is passed in, then returns 0. // Created: 9/4/04 // // -------------------------------------------------------------------------- int32_t BoxConvert::_ConvertStringToInt(const char *pString, int Size) { // Handle null strings gracefully. if(pString == 0) { return 0; } // Check for initial validity if(*pString == '\0') { THROW_EXCEPTION(ConversionException, CannotConvertEmptyStringToInt) } // Convert. char *numEnd = 0; errno = 0; // Some platforms don't reset it. long r = ::strtol(pString, &numEnd, 0); // Check that all the characters were used if(*numEnd != '\0') { THROW_EXCEPTION(ConversionException, BadStringRepresentationOfInt) } // Error check if(r == 0 && errno == EINVAL) { THROW_EXCEPTION(ConversionException, BadStringRepresentationOfInt) } // Range check from strtol if((r == LONG_MIN || r == LONG_MAX) && errno == ERANGE) { THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) } // Check range for size of integer switch(Size) { case 32: { // No extra checking needed if long is an int32 if(sizeof(long) > sizeof(int32_t)) { if(r <= (0 - 0x7fffffffL) || r > 0x7fffffffL) { THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) } } break; } case 16: { if(r <= (0 - 0x7fff) || r > 0x7fff) { THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) } break; } case 8: { if(r <= (0 - 0x7f) || r > 0x7f) { THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) } break; } default: { THROW_EXCEPTION(ConversionException, BadIntSize) break; } } // Return number return r; } // -------------------------------------------------------------------------- // // Function // Name: BoxConvert::_ConvertIntToString(std::string &, int32_t) // Purpose: Convert signed interger to a string // Created: 9/4/04 // // -------------------------------------------------------------------------- void BoxConvert::_ConvertIntToString(std::string &rTo, int32_t From) { char text[64]; // size more than enough ::sprintf(text, "%d", (int)From); rTo = text; } boxbackup/lib/common/ReadGatherStream.h0000664000175000017500000000335110347400657020713 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ReadGatherStream.h // Purpose: Build a stream (for reading only) out of a number of other streams. // Created: 10/12/03 // // -------------------------------------------------------------------------- #ifndef READGATHERSTREAM_H #define READGATHERSTREAM_H #include "IOStream.h" #include // -------------------------------------------------------------------------- // // Class // Name: ReadGatherStream // Purpose: Build a stream (for reading only) out of a number of other streams. // Created: 10/12/03 // // -------------------------------------------------------------------------- class ReadGatherStream : public IOStream { public: ReadGatherStream(bool DeleteComponentStreamsOnDestruction); ~ReadGatherStream(); private: ReadGatherStream(const ReadGatherStream &); ReadGatherStream &operator=(const ReadGatherStream &); public: int AddComponent(IOStream *pStream); void AddBlock(int Component, pos_type Length, bool Seek = false, pos_type SeekTo = 0); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual bool StreamDataLeft(); virtual bool StreamClosed(); virtual pos_type GetPosition() const; private: bool mDeleteComponentStreamsOnDestruction; std::vector mComponents; typedef struct { pos_type mLength; pos_type mSeekTo; int mComponent; bool mSeek; } Block; std::vector mBlocks; pos_type mCurrentPosition; pos_type mTotalSize; unsigned int mCurrentBlock; pos_type mPositionInCurrentBlock; bool mSeekDoneForCurrent; }; #endif // READGATHERSTREAM_H boxbackup/lib/common/UnixUser.cpp0000664000175000017500000000564310574603112017645 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: UnixUser.cpp // Purpose: Interface for managing the UNIX user of the current process // Created: 21/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include "UnixUser.h" #include "CommonException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: UnixUser::UnixUser(const char *) // Purpose: Constructor, initialises to info of given username // Created: 21/1/04 // // -------------------------------------------------------------------------- UnixUser::UnixUser(const char *Username) : mUID(0), mGID(0), mRevertOnDestruction(false) { // Get password info struct passwd *pwd = ::getpwnam(Username); if(pwd == 0) { THROW_EXCEPTION(CommonException, CouldNotLookUpUsername) } // Store UID and GID mUID = pwd->pw_uid; mGID = pwd->pw_gid; } // -------------------------------------------------------------------------- // // Function // Name: UnixUser::UnixUser(uid_t, gid_t) // Purpose: Construct from given UNIX user ID and group ID // Created: 15/3/04 // // -------------------------------------------------------------------------- UnixUser::UnixUser(uid_t UID, gid_t GID) : mUID(UID), mGID(GID), mRevertOnDestruction(false) { } // -------------------------------------------------------------------------- // // Function // Name: UnixUser::~UnixUser() // Purpose: Destructor -- reverts to previous user if the change wasn't perminant // Created: 21/1/04 // // -------------------------------------------------------------------------- UnixUser::~UnixUser() { if(mRevertOnDestruction) { // Revert to "real" user and group id of the process if(::setegid(::getgid()) != 0 || ::seteuid(::getuid()) != 0) { THROW_EXCEPTION(CommonException, CouldNotRestoreProcessUser) } } } // -------------------------------------------------------------------------- // // Function // Name: UnixUser::ChangeProcessUser(bool) // Purpose: Change the process user and group ID to the user. If Temporary == true // the process username will be changed back when the object is destructed. // Created: 21/1/04 // // -------------------------------------------------------------------------- void UnixUser::ChangeProcessUser(bool Temporary) { if(Temporary) { // Change temporarily (change effective only) if(::setegid(mGID) != 0 || ::seteuid(mUID) != 0) { THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser) } // Mark for change on destruction mRevertOnDestruction = true; } else { // Change permanently (change all UIDs and GIDs) if(::setgid(mGID) != 0 || ::setuid(mUID) != 0) { THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser) } } } boxbackup/lib/common/PartialReadStream.h0000664000175000017500000000227210614700276021073 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: PartialReadStream.h // Purpose: Read part of another stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- #ifndef PARTIALREADSTREAM__H #define PARTIALREADSTREAM__H #include "IOStream.h" // -------------------------------------------------------------------------- // // Class // Name: PartialReadStream // Purpose: Read part of another stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- class PartialReadStream : public IOStream { public: PartialReadStream(IOStream &rSource, pos_type BytesToRead); ~PartialReadStream(); private: // no copying allowed PartialReadStream(const IOStream &); PartialReadStream(const PartialReadStream &); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: IOStream &mrSource; pos_type mBytesLeft; }; #endif // PARTIALREADSTREAM__H boxbackup/lib/common/BufferedWriteStream.cpp0000664000175000017500000001164211443465445022003 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BufferedWriteStream.cpp // Purpose: Buffering write-only wrapper around IOStreams // Created: 2010/09/13 // // -------------------------------------------------------------------------- #include "Box.h" #include "BufferedWriteStream.h" #include "CommonException.h" #include #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::BufferedWriteStream(const char *, int, int) // Purpose: Constructor, set up buffer // Created: 2007/01/16 // // -------------------------------------------------------------------------- BufferedWriteStream::BufferedWriteStream(IOStream& rSink) : mrSink(rSink), mBufferPosition(0) { } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::Read(void *, int) // Purpose: Reads bytes from the file - throws exception // Created: 2007/01/16 // // -------------------------------------------------------------------------- int BufferedWriteStream::Read(void *pBuffer, int NBytes, int Timeout) { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::BytesLeftToRead() // Purpose: Returns number of bytes to read (may not be most efficient function ever) // Created: 2007/01/16 // // -------------------------------------------------------------------------- IOStream::pos_type BufferedWriteStream::BytesLeftToRead() { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::Write(void *, int) // Purpose: Writes bytes to the underlying stream (not supported) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void BufferedWriteStream::Write(const void *pBuffer, int NBytes) { int numBytesRemain = NBytes; do { int maxWritable = sizeof(mBuffer) - mBufferPosition; int numBytesToWrite = (numBytesRemain < maxWritable) ? numBytesRemain : maxWritable; if(numBytesToWrite > 0) { memcpy(mBuffer + mBufferPosition, pBuffer, numBytesToWrite); mBufferPosition += numBytesToWrite; pBuffer = ((const char *)pBuffer) + numBytesToWrite; numBytesRemain -= numBytesToWrite; } if(numBytesRemain > 0) { Flush(); } } while(numBytesRemain > 0); } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::GetPosition() // Purpose: Get position in stream // Created: 2003/08/21 // // -------------------------------------------------------------------------- IOStream::pos_type BufferedWriteStream::GetPosition() const { return mrSink.GetPosition() + mBufferPosition; } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::Seek(pos_type, int) // Purpose: Seeks within file, as lseek, invalidate buffer // Created: 2003/07/31 // // -------------------------------------------------------------------------- void BufferedWriteStream::Seek(IOStream::pos_type Offset, int SeekType) { // Always flush the buffer before seeking Flush(); mrSink.Seek(Offset, SeekType); } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::Flush(); // Purpose: Write out current buffer contents and invalidate // Created: 2010/09/13 // // -------------------------------------------------------------------------- void BufferedWriteStream::Flush(int Timeout) { if(mBufferPosition > 0) { mrSink.Write(mBuffer, mBufferPosition); } mBufferPosition = 0; } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::Close() // Purpose: Closes the underlying stream (not needed) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void BufferedWriteStream::Close() { Flush(); mrSink.Close(); } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::StreamDataLeft() // Purpose: Any data left to write? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool BufferedWriteStream::StreamDataLeft() { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: BufferedWriteStream::StreamClosed() // Purpose: Is the stream closed? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool BufferedWriteStream::StreamClosed() { return mrSink.StreamClosed(); } boxbackup/lib/common/StreamableMemBlock.cpp0000664000175000017500000002176311165365160021562 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: StreamableMemBlock.cpp // Purpose: Memory blocks which can be loaded and saved from streams // Created: 2003/09/05 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include "StreamableMemBlock.h" #include "IOStream.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::StreamableMemBlock() // Purpose: Constructor, making empty block // Created: 2003/09/05 // // -------------------------------------------------------------------------- StreamableMemBlock::StreamableMemBlock() : mpBuffer(0), mSize(0) { } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::StreamableMemBlock(void *, int) // Purpose: Create block, copying data from another bit of memory // Created: 2003/09/05 // // -------------------------------------------------------------------------- StreamableMemBlock::StreamableMemBlock(void *pBuffer, int Size) : mpBuffer(0), mSize(0) { AllocateBlock(Size); ::memcpy(mpBuffer, pBuffer, Size); } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::StreamableMemBlock(int) // Purpose: Create block, initialising it to all zeros // Created: 2003/09/05 // // -------------------------------------------------------------------------- StreamableMemBlock::StreamableMemBlock(int Size) : mpBuffer(0), mSize(0) { AllocateBlock(Size); ::memset(mpBuffer, 0, Size); } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::StreamableMemBlock(const StreamableMemBlock &) // Purpose: Copy constructor // Created: 2003/09/05 // // -------------------------------------------------------------------------- StreamableMemBlock::StreamableMemBlock(const StreamableMemBlock &rToCopy) : mpBuffer(0), mSize(0) { AllocateBlock(rToCopy.mSize); ::memcpy(mpBuffer, rToCopy.mpBuffer, mSize); } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::Set(void *, int) // Purpose: Set the contents of the block // Created: 2003/09/05 // // -------------------------------------------------------------------------- void StreamableMemBlock::Set(void *pBuffer, int Size) { FreeBlock(); AllocateBlock(Size); ::memcpy(mpBuffer, pBuffer, Size); } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::Set(IOStream &) // Purpose: Set from stream. Stream must support BytesLeftToRead() // Created: 2003/09/05 // // -------------------------------------------------------------------------- void StreamableMemBlock::Set(IOStream &rStream, int Timeout) { // Get size IOStream::pos_type size = rStream.BytesLeftToRead(); if(size == IOStream::SizeOfStreamUnknown) { THROW_EXCEPTION(CommonException, StreamDoesntHaveRequiredProperty) } // Allocate a new block (this way to be exception safe) char *pblock = (char*)malloc(size); if(pblock == 0) { throw std::bad_alloc(); } try { // Read in if(!rStream.ReadFullBuffer(pblock, size, 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) } // Free the block ready for replacement FreeBlock(); } catch(...) { ::free(pblock); throw; } // store... ASSERT(mpBuffer == 0); mpBuffer = pblock; mSize = size; } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::Set(const StreamableMemBlock &) // Purpose: Set from other block. // Created: 2003/09/06 // // -------------------------------------------------------------------------- void StreamableMemBlock::Set(const StreamableMemBlock &rBlock) { Set(rBlock.mpBuffer, rBlock.mSize); } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::~StreamableMemBlock() // Purpose: Destructor // Created: 2003/09/05 // // -------------------------------------------------------------------------- StreamableMemBlock::~StreamableMemBlock() { FreeBlock(); } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::FreeBlock() // Purpose: Protected. Frees block of memory // Created: 2003/09/05 // // -------------------------------------------------------------------------- void StreamableMemBlock::FreeBlock() { if(mpBuffer != 0) { ::free(mpBuffer); } mpBuffer = 0; mSize = 0; } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::AllocateBlock(int) // Purpose: Protected. Allocate the block of memory // Created: 2003/09/05 // // -------------------------------------------------------------------------- void StreamableMemBlock::AllocateBlock(int Size) { ASSERT(mpBuffer == 0); if(Size > 0) { mpBuffer = ::malloc(Size); if(mpBuffer == 0) { throw std::bad_alloc(); } } mSize = Size; } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::ResizeBlock(int) // Purpose: Protected. Resizes the allocated block. // Created: 3/12/03 // // -------------------------------------------------------------------------- void StreamableMemBlock::ResizeBlock(int Size) { ASSERT(Size > 0); if(Size > 0) { void *pnewBuffer = ::realloc(mpBuffer, Size); if(pnewBuffer == 0) { throw std::bad_alloc(); } mpBuffer = pnewBuffer; } mSize = Size; } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::ReadFromStream(IOStream &, int) // Purpose: Read the block in from a stream // Created: 2003/09/05 // // -------------------------------------------------------------------------- void StreamableMemBlock::ReadFromStream(IOStream &rStream, int Timeout) { // Get the size of the block int32_t size_s; if(!rStream.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) } int size = ntohl(size_s); // Allocate a new block (this way to be exception safe) char *pblock = (char*)malloc(size); if(pblock == 0) { throw std::bad_alloc(); } try { // Read in if(!rStream.ReadFullBuffer(pblock, size, 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) } // Free the block ready for replacement FreeBlock(); } catch(...) { ::free(pblock); throw; } // store... ASSERT(mpBuffer == 0); mpBuffer = pblock; mSize = size; } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::WriteToStream(IOStream &) // Purpose: Write the block to a stream // Created: 2003/09/05 // // -------------------------------------------------------------------------- void StreamableMemBlock::WriteToStream(IOStream &rStream) const { int32_t sizenbo = htonl(mSize); // Size rStream.Write(&sizenbo, sizeof(sizenbo)); // Buffer if(mSize > 0) { rStream.Write(mpBuffer, mSize); } } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::WriteEmptyBlockToStream(IOStream &) // Purpose: Writes an empty block to a stream. // Created: 2003/09/05 // // -------------------------------------------------------------------------- void StreamableMemBlock::WriteEmptyBlockToStream(IOStream &rStream) { int32_t sizenbo = htonl(0); rStream.Write(&sizenbo, sizeof(sizenbo)); } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::GetBuffer() // Purpose: Get pointer to buffer // Created: 2003/09/05 // // -------------------------------------------------------------------------- void *StreamableMemBlock::GetBuffer() const { if(mSize == 0) { // Return something which isn't a null pointer static const int validptr = 0; return (void*)&validptr; } // return the buffer ASSERT(mpBuffer != 0); return mpBuffer; } // -------------------------------------------------------------------------- // // Function // Name: StreamableMemBlock::operator==(const StreamableMemBlock &) // Purpose: Test for equality of memory blocks // Created: 2003/09/06 // // -------------------------------------------------------------------------- bool StreamableMemBlock::operator==(const StreamableMemBlock &rCompare) const { if(mSize != rCompare.mSize) return false; if(mSize == 0 && rCompare.mSize == 0) return true; // without memory comparison! return ::memcmp(mpBuffer, rCompare.mpBuffer, mSize) == 0; } boxbackup/lib/common/FileStream.h0000664000175000017500000000307511175071224017561 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: FileStream.h // Purpose: FileStream interface to files // Created: 2003/07/31 // // -------------------------------------------------------------------------- #ifndef FILESTREAM__H #define FILESTREAM__H #include "IOStream.h" #include #include #include #ifdef HAVE_UNISTD_H #include #endif class FileStream : public IOStream { public: FileStream(const std::string& rFilename, int flags = (O_RDONLY | O_BINARY), int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); // Ensure that const char * name doesn't end up as a handle // on Windows! FileStream(const char *pFilename, int flags = (O_RDONLY | O_BINARY), int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); FileStream(tOSFileHandle FileDescriptor); virtual ~FileStream(); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(IOStream::pos_type Offset, int SeekType); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); bool CompareWith(IOStream& rOther, int Timeout = IOStream::TimeOutInfinite); private: tOSFileHandle mOSFileHandle; bool mIsEOF; FileStream(const FileStream &rToCopy) { /* do not call */ } void AfterOpen(); // for debugging.. std::string mFileName; }; #endif // FILESTREAM__H boxbackup/lib/common/MainHelper.h0000664000175000017500000000234110526114531017543 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MainHelper.h // Purpose: Helper stuff for main() programs // Created: 2003/08/21 // // -------------------------------------------------------------------------- #ifndef MAINHELPER__H #define MAINHELPER__H #include #include "BoxException.h" #define MAINHELPER_START \ if(argc == 2 && ::strcmp(argv[1], "--version") == 0) \ { printf(BOX_VERSION "\n"); return 0; } \ MEMLEAKFINDER_INIT \ MEMLEAKFINDER_START \ try { #define MAINHELPER_END \ } catch(BoxException &e) { \ printf("Exception: %s (%d/%d)\n", e.what(), e.GetType(), e.GetSubType()); \ return 1; \ } catch(std::exception &e) { \ printf("Exception: %s\n", e.what()); \ return 1; \ } catch(...) { \ printf("Exception: \n"); \ return 1; } #ifdef BOX_MEMORY_LEAK_TESTING #define MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, marker) \ memleakfinder_setup_exit_report(file, marker); #else #define MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, marker) #endif // BOX_MEMORY_LEAK_TESTING #endif // MAINHELPER__H boxbackup/lib/common/FileStream.cpp0000664000175000017500000002434411205602257020116 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: FileStream.cpp // Purpose: IOStream interface to files // Created: 2003/07/31 // // -------------------------------------------------------------------------- #include "Box.h" #include "FileStream.h" #include "CommonException.h" #include "Logging.h" #include #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: FileStream::FileStream(const char *, int, int) // Purpose: Constructor, opens file // Created: 2003/07/31 // // -------------------------------------------------------------------------- FileStream::FileStream(const std::string& rFilename, int flags, int mode) #ifdef WIN32 : mOSFileHandle(::openfile(rFilename.c_str(), flags, mode)), #else : mOSFileHandle(::open(rFilename.c_str(), flags, mode)), #endif mIsEOF(false), mFileName(rFilename) { AfterOpen(); } // -------------------------------------------------------------------------- // // Function // Name: FileStream::FileStream(const char *, int, int) // Purpose: Alternative constructor, takes a const char *, // avoids const strings being interpreted as handles! // Created: 2003/07/31 // // -------------------------------------------------------------------------- FileStream::FileStream(const char *pFilename, int flags, int mode) #ifdef WIN32 : mOSFileHandle(::openfile(pFilename, flags, mode)), #else : mOSFileHandle(::open(pFilename, flags, mode)), #endif mIsEOF(false), mFileName(pFilename) { AfterOpen(); } void FileStream::AfterOpen() { #ifdef WIN32 if(mOSFileHandle == INVALID_HANDLE_VALUE) #else if(mOSFileHandle < 0) #endif { MEMLEAKFINDER_NOT_A_LEAK(this); #ifdef WIN32 BOX_LOG_WIN_WARNING_NUMBER("Failed to open file: " << mFileName, winerrno); #else BOX_LOG_SYS_WARNING("Failed to open file: " << mFileName); #endif if(errno == EACCES) { THROW_EXCEPTION(CommonException, AccessDenied) } else { THROW_EXCEPTION(CommonException, OSFileOpenError) } } } // -------------------------------------------------------------------------- // // Function // Name: FileStream::FileStream(tOSFileHandle) // Purpose: Constructor, using existing file descriptor // Created: 2003/08/28 // // -------------------------------------------------------------------------- FileStream::FileStream(tOSFileHandle FileDescriptor) : mOSFileHandle(FileDescriptor), mIsEOF(false), mFileName("HANDLE") { #ifdef WIN32 if(mOSFileHandle == INVALID_HANDLE_VALUE) #else if(mOSFileHandle < 0) #endif { MEMLEAKFINDER_NOT_A_LEAK(this); BOX_ERROR("FileStream: called with invalid file handle"); THROW_EXCEPTION(CommonException, OSFileOpenError) } } #if 0 // -------------------------------------------------------------------------- // // Function // Name: FileStream::FileStream(const FileStream &) // Purpose: Copy constructor, creates a duplicate of the file handle // Created: 2003/07/31 // // -------------------------------------------------------------------------- FileStream::FileStream(const FileStream &rToCopy) : mOSFileHandle(::dup(rToCopy.mOSFileHandle)), mIsEOF(rToCopy.mIsEOF) { #ifdef WIN32 if(mOSFileHandle == INVALID_HANDLE_VALUE) #else if(mOSFileHandle < 0) #endif { MEMLEAKFINDER_NOT_A_LEAK(this); BOX_ERROR("FileStream: copying unopened file"); THROW_EXCEPTION(CommonException, OSFileOpenError) } } #endif // 0 // -------------------------------------------------------------------------- // // Function // Name: FileStream::~FileStream() // Purpose: Destructor, closes file // Created: 2003/07/31 // // -------------------------------------------------------------------------- FileStream::~FileStream() { if(mOSFileHandle != INVALID_FILE) { Close(); } } // -------------------------------------------------------------------------- // // Function // Name: FileStream::Read(void *, int) // Purpose: Reads bytes from the file // Created: 2003/07/31 // // -------------------------------------------------------------------------- int FileStream::Read(void *pBuffer, int NBytes, int Timeout) { if(mOSFileHandle == INVALID_FILE) { THROW_EXCEPTION(CommonException, FileClosed) } #ifdef WIN32 int r; DWORD numBytesRead = 0; BOOL valid = ReadFile( this->mOSFileHandle, pBuffer, NBytes, &numBytesRead, NULL ); if(valid) { r = numBytesRead; } else if(GetLastError() == ERROR_BROKEN_PIPE) { r = 0; } else { BOX_LOG_WIN_ERROR("Failed to read from file: " << mFileName); r = -1; } #else int r = ::read(mOSFileHandle, pBuffer, NBytes); if(r == -1) { BOX_LOG_SYS_ERROR("Failed to read from file: " << mFileName); } #endif if(r == -1) { THROW_EXCEPTION(CommonException, OSFileReadError) } if(r == 0) { mIsEOF = true; } return r; } // -------------------------------------------------------------------------- // // Function // Name: FileStream::BytesLeftToRead() // Purpose: Returns number of bytes to read (may not be most efficient function ever) // Created: 2003/08/28 // // -------------------------------------------------------------------------- IOStream::pos_type FileStream::BytesLeftToRead() { EMU_STRUCT_STAT st; if(EMU_FSTAT(mOSFileHandle, &st) != 0) { THROW_EXCEPTION(CommonException, OSFileError) } return st.st_size - GetPosition(); } // -------------------------------------------------------------------------- // // Function // Name: FileStream::Write(void *, int) // Purpose: Writes bytes to the file // Created: 2003/07/31 // // -------------------------------------------------------------------------- void FileStream::Write(const void *pBuffer, int NBytes) { if(mOSFileHandle == INVALID_FILE) { THROW_EXCEPTION(CommonException, FileClosed) } #ifdef WIN32 DWORD numBytesWritten = 0; BOOL res = WriteFile( this->mOSFileHandle, pBuffer, NBytes, &numBytesWritten, NULL ); if ((res == 0) || (numBytesWritten != (DWORD)NBytes)) { // DWORD err = GetLastError(); THROW_EXCEPTION(CommonException, OSFileWriteError) } #else if(::write(mOSFileHandle, pBuffer, NBytes) != NBytes) { BOX_LOG_SYS_ERROR("Failed to write to file: " << mFileName); THROW_EXCEPTION(CommonException, OSFileWriteError) } #endif } // -------------------------------------------------------------------------- // // Function // Name: FileStream::GetPosition() // Purpose: Get position in stream // Created: 2003/08/21 // // -------------------------------------------------------------------------- IOStream::pos_type FileStream::GetPosition() const { if(mOSFileHandle == INVALID_FILE) { THROW_EXCEPTION(CommonException, FileClosed) } #ifdef WIN32 LARGE_INTEGER conv; conv.HighPart = 0; conv.LowPart = 0; conv.LowPart = SetFilePointer(this->mOSFileHandle, 0, &conv.HighPart, FILE_CURRENT); return (IOStream::pos_type)conv.QuadPart; #else // ! WIN32 off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); if(p == -1) { THROW_EXCEPTION(CommonException, OSFileError) } return (IOStream::pos_type)p; #endif // WIN32 } // -------------------------------------------------------------------------- // // Function // Name: FileStream::Seek(pos_type, int) // Purpose: Seeks within file, as lseek // Created: 2003/07/31 // // -------------------------------------------------------------------------- void FileStream::Seek(IOStream::pos_type Offset, int SeekType) { if(mOSFileHandle == INVALID_FILE) { THROW_EXCEPTION(CommonException, FileClosed) } #ifdef WIN32 LARGE_INTEGER conv; conv.QuadPart = Offset; DWORD retVal = SetFilePointer(this->mOSFileHandle, conv.LowPart, &conv.HighPart, ConvertSeekTypeToOSWhence(SeekType)); if(retVal == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { THROW_EXCEPTION(CommonException, OSFileError) } #else // ! WIN32 if(::lseek(mOSFileHandle, Offset, ConvertSeekTypeToOSWhence(SeekType)) == -1) { THROW_EXCEPTION(CommonException, OSFileError) } #endif // WIN32 // Not end of file any more! mIsEOF = false; } // -------------------------------------------------------------------------- // // Function // Name: FileStream::Close() // Purpose: Closes the underlying file // Created: 2003/07/31 // // -------------------------------------------------------------------------- void FileStream::Close() { if(mOSFileHandle == INVALID_FILE) { THROW_EXCEPTION(CommonException, FileAlreadyClosed) } #ifdef WIN32 if(::CloseHandle(mOSFileHandle) == 0) #else if(::close(mOSFileHandle) != 0) #endif { THROW_EXCEPTION(CommonException, OSFileCloseError) } mOSFileHandle = INVALID_FILE; mIsEOF = true; } // -------------------------------------------------------------------------- // // Function // Name: FileStream::StreamDataLeft() // Purpose: Any data left to write? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool FileStream::StreamDataLeft() { return !mIsEOF; } // -------------------------------------------------------------------------- // // Function // Name: FileStream::StreamClosed() // Purpose: Is the stream closed? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool FileStream::StreamClosed() { return mIsEOF; } // -------------------------------------------------------------------------- // // Function // Name: FileStream::CompareWith(IOStream&, int) // Purpose: Compare bytes in this file with other stream's data // Created: 2009/01/03 // // -------------------------------------------------------------------------- bool FileStream::CompareWith(IOStream& rOther, int Timeout) { // Size IOStream::pos_type mySize = BytesLeftToRead(); IOStream::pos_type otherSize = 0; // Test the contents char buf1[2048]; char buf2[2048]; while(StreamDataLeft() && rOther.StreamDataLeft()) { int readSize = rOther.Read(buf1, sizeof(buf1), Timeout); otherSize += readSize; if(Read(buf2, readSize) != readSize || ::memcmp(buf1, buf2, readSize) != 0) { return false; } } // Check read all the data from the server and file -- can't be // equal if local and remote aren't the same length. Can't use // StreamDataLeft() test on local file, because if it's the same // size, it won't know it's EOF yet. if(rOther.StreamDataLeft() || otherSize != mySize) { return false; } return true; } boxbackup/lib/common/PathUtils.cpp0000664000175000017500000000160310553516061017773 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: PathUtils.cpp // Purpose: Platform-independent path manipulation // Created: 2007/01/17 // // -------------------------------------------------------------------------- #include "Box.h" #include // -------------------------------------------------------------------------- // // Function // Name: MakeFullPath(const std::string& rDir, const std::string& rFile) // Purpose: Combine directory and file name // Created: 2006/08/10 // // -------------------------------------------------------------------------- std::string MakeFullPath(const std::string& rDir, const std::string& rEntry) { std::string result(rDir); if (result.size() > 0 && result[result.size()-1] != DIRECTORY_SEPARATOR_ASCHAR) { result += DIRECTORY_SEPARATOR; } result += rEntry; return result; } boxbackup/lib/common/BoxException.h0000664000175000017500000000147111117316124020130 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxException.h // Purpose: Exception // Created: 2003/07/10 // // -------------------------------------------------------------------------- #ifndef BOXEXCEPTION__H #define BOXEXCEPTION__H #include #include // -------------------------------------------------------------------------- // // Class // Name: BoxException // Purpose: Exception // Created: 2003/07/10 // // -------------------------------------------------------------------------- class BoxException : public std::exception { public: BoxException(); ~BoxException() throw (); virtual unsigned int GetType() const throw() = 0; virtual unsigned int GetSubType() const throw() = 0; private: }; #endif // BOXEXCEPTION__H boxbackup/lib/common/Timer.h0000664000175000017500000000346211043156076016611 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Timer.h // Purpose: Generic timers which execute arbitrary code when // they expire. // Created: 5/11/2006 // // -------------------------------------------------------------------------- #ifndef TIMER__H #define TIMER__H #ifdef HAVE_SYS_TIME_H #include #endif #include #include "BoxTime.h" #include "MemLeakFindOn.h" class Timer; // -------------------------------------------------------------------------- // // Class // Name: Timers // Purpose: Static class to manage all timers and arrange // efficient delivery of wakeup signals // Created: 19/3/04 // // -------------------------------------------------------------------------- class Timers { private: static std::vector* spTimers; static void Reschedule(); static bool sRescheduleNeeded; static void SignalHandler(int iUnused); public: static void Init(); static void Cleanup(); static void Add (Timer& rTimer); static void Remove(Timer& rTimer); static void RequestReschedule(); static void RescheduleIfNeeded(); }; class Timer { public: Timer(size_t timeoutSecs, const std::string& rName = ""); virtual ~Timer(); Timer(const Timer &); Timer &operator=(const Timer &); box_time_t GetExpiryTime() { return mExpires; } virtual void OnExpire(); bool HasExpired() { Timers::RescheduleIfNeeded(); return mExpired; } const std::string& GetName() const { return mName; } private: box_time_t mExpires; bool mExpired; std::string mName; void Start(); void Start(int64_t delayInMicros); void Stop(); #ifdef WIN32 HANDLE mTimerHandle; static VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired); #endif }; #include "MemLeakFindOff.h" #endif // TIMER__H boxbackup/lib/common/FdGetLine.h0000664000175000017500000000311311126433077017324 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: FdGetLine.h // Purpose: Line based file descriptor reading // Created: 2003/07/24 // // -------------------------------------------------------------------------- #ifndef FDGETLINE__H #define FDGETLINE__H #include #ifdef BOX_RELEASE_BUILD #define FDGETLINE_BUFFER_SIZE 1024 #elif defined WIN32 // need enough space for at least one unicode character // in UTF-8 when calling console_read() from bbackupquery #define FDGETLINE_BUFFER_SIZE 5 #else #define FDGETLINE_BUFFER_SIZE 4 #endif // Just a very large upper bound for line size to avoid // people sending lots of data over sockets and causing memory problems. #define FDGETLINE_MAX_LINE_SIZE (1024*256) // -------------------------------------------------------------------------- // // Class // Name: FdGetLine // Purpose: Line based file descriptor reading // Created: 2003/07/24 // // -------------------------------------------------------------------------- class FdGetLine { public: FdGetLine(int fd); ~FdGetLine(); private: FdGetLine(const FdGetLine &rToCopy); public: std::string GetLine(bool Preprocess = false); bool IsEOF() {return mEOF;} int GetLineNumber() {return mLineNumber;} // Call to detach, setting file pointer correctly to last bit read. // Only works for lseek-able file descriptors. void DetachFile(); private: char mBuffer[FDGETLINE_BUFFER_SIZE]; int mFileHandle; int mLineNumber; int mBufferBegin; int mBytesInBuffer; bool mPendingEOF; bool mEOF; }; #endif // FDGETLINE__H boxbackup/lib/common/DebugAssertFailed.cpp0000664000175000017500000000133411126433077021376 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: AssertFailed.cpp // Purpose: Assert failure code // Created: 2003/09/04 // // -------------------------------------------------------------------------- #ifndef BOX_RELEASE_BUILD #include "Box.h" #include #ifdef WIN32 #include "emu.h" #else #include #endif #include "MemLeakFindOn.h" bool AssertFailuresToSyslog = false; void BoxDebugAssertFailed(const char *cond, const char *file, int line) { printf("ASSERT FAILED: [%s] at %s(%d)\n", cond, file, line); if(AssertFailuresToSyslog) { ::syslog(LOG_ERR, "ASSERT FAILED: [%s] at %s(%d)\n", cond, file, line); } } #endif // BOX_RELEASE_BUILD boxbackup/lib/common/NamedLock.h0000664000175000017500000000170011221737343017357 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: NamedLock.h // Purpose: A global named lock, implemented as a lock file in file system // Created: 2003/08/28 // // -------------------------------------------------------------------------- #ifndef NAMEDLOCK__H #define NAMEDLOCK__H // -------------------------------------------------------------------------- // // Class // Name: NamedLock // Purpose: A global named lock, implemented as a lock file in file system // Created: 2003/08/28 // // -------------------------------------------------------------------------- class NamedLock { public: NamedLock(); ~NamedLock(); private: // No copying allowed NamedLock(const NamedLock &); public: bool TryAndGetLock(const std::string& rFilename, int mode = 0755); bool GotLock() {return mFileDescriptor != -1;} void ReleaseLock(); private: int mFileDescriptor; }; #endif // NAMEDLOCK__H boxbackup/lib/common/Test.cpp0000664000175000017500000002245111345265367017013 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Test.cpp // Purpose: Useful stuff for tests // Created: 2008/04/05 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include "Test.h" bool TestFileExists(const char *Filename) { EMU_STRUCT_STAT st; return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == 0; } bool TestFileNotEmpty(const char *Filename) { EMU_STRUCT_STAT st; return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == 0 && st.st_size > 0; } bool TestDirExists(const char *Filename) { EMU_STRUCT_STAT st; return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == S_IFDIR; } // -1 if doesn't exist int TestGetFileSize(const char *Filename) { EMU_STRUCT_STAT st; if(EMU_STAT(Filename, &st) == 0) { return st.st_size; } return -1; } std::string ConvertPaths(const std::string& rOriginal) { #ifdef WIN32 // convert UNIX paths to native std::string converted; for (size_t i = 0; i < rOriginal.size(); i++) { if (rOriginal[i] == '/') { converted += '\\'; } else { converted += rOriginal[i]; } } return converted; #else // !WIN32 return rOriginal; #endif } int RunCommand(const std::string& rCommandLine) { return ::system(ConvertPaths(rCommandLine).c_str()); } #ifdef WIN32 #include #endif bool ServerIsAlive(int pid) { #ifdef WIN32 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid); if (hProcess == NULL) { if (GetLastError() != ERROR_INVALID_PARAMETER) { BOX_ERROR("Failed to open process " << pid << ": " << GetErrorMessage(GetLastError())); } return false; } DWORD exitCode; BOOL result = GetExitCodeProcess(hProcess, &exitCode); CloseHandle(hProcess); if (result == 0) { BOX_ERROR("Failed to get exit code for process " << pid << ": " << GetErrorMessage(GetLastError())) return false; } if (exitCode == STILL_ACTIVE) { return true; } return false; #else // !WIN32 if(pid == 0) return false; return ::kill(pid, 0) != -1; #endif // WIN32 } int ReadPidFile(const char *pidFile) { if(!TestFileNotEmpty(pidFile)) { TEST_FAIL_WITH_MESSAGE("Server didn't save PID file " "(perhaps one was already running?)"); return -1; } int pid = -1; FILE *f = fopen(pidFile, "r"); if(f == NULL || fscanf(f, "%d", &pid) != 1) { TEST_FAIL_WITH_MESSAGE("Couldn't read PID file"); return -1; } fclose(f); return pid; } int LaunchServer(const std::string& rCommandLine, const char *pidFile) { ::fprintf(stdout, "Starting server: %s\n", rCommandLine.c_str()); #ifdef WIN32 PROCESS_INFORMATION procInfo; STARTUPINFO startInfo; startInfo.cb = sizeof(startInfo); startInfo.lpReserved = NULL; startInfo.lpDesktop = NULL; startInfo.lpTitle = NULL; startInfo.dwFlags = 0; startInfo.cbReserved2 = 0; startInfo.lpReserved2 = NULL; std::string cmd = ConvertPaths(rCommandLine); CHAR* tempCmd = strdup(cmd.c_str()); DWORD result = CreateProcess ( NULL, // lpApplicationName, naughty! tempCmd, // lpCommandLine NULL, // lpProcessAttributes NULL, // lpThreadAttributes false, // bInheritHandles 0, // dwCreationFlags NULL, // lpEnvironment NULL, // lpCurrentDirectory &startInfo, // lpStartupInfo &procInfo // lpProcessInformation ); free(tempCmd); if (result == 0) { DWORD err = GetLastError(); printf("Launch failed: %s: error %d\n", rCommandLine.c_str(), (int)err); TEST_FAIL_WITH_MESSAGE("Couldn't start server"); return -1; } CloseHandle(procInfo.hProcess); CloseHandle(procInfo.hThread); return WaitForServerStartup(pidFile, (int)procInfo.dwProcessId); #else // !WIN32 if(RunCommand(rCommandLine) != 0) { TEST_FAIL_WITH_MESSAGE("Couldn't start server"); return -1; } return WaitForServerStartup(pidFile, 0); #endif // WIN32 } int WaitForServerStartup(const char *pidFile, int pidIfKnown) { #ifdef WIN32 if (pidFile == NULL) { return pidIfKnown; } #else // on other platforms there is no other way to get // the PID, so a NULL pidFile doesn't make sense. ASSERT(pidFile != NULL); #endif // time for it to start up if (Logging::GetGlobalLevel() >= Log::TRACE) { BOX_TRACE("Waiting for server to start"); } else { ::fprintf(stdout, "Waiting for server to start: "); } for (int i = 0; i < 15; i++) { if (TestFileNotEmpty(pidFile)) { break; } if (pidIfKnown && !ServerIsAlive(pidIfKnown)) { break; } if (Logging::GetGlobalLevel() < Log::TRACE) { ::fprintf(stdout, "."); ::fflush(stdout); } ::sleep(1); } // on Win32 we can check whether the process is alive // without even checking the PID file if (pidIfKnown && !ServerIsAlive(pidIfKnown)) { if (Logging::GetGlobalLevel() >= Log::TRACE) { BOX_ERROR("server died!"); } else { ::fprintf(stdout, " server died!\n"); } TEST_FAIL_WITH_MESSAGE("Server died!"); return -1; } if (!TestFileNotEmpty(pidFile)) { if (Logging::GetGlobalLevel() >= Log::TRACE) { BOX_ERROR("timed out!"); } else { ::fprintf(stdout, " timed out!\n"); } TEST_FAIL_WITH_MESSAGE("Server didn't save PID file"); return -1; } if (Logging::GetGlobalLevel() >= Log::TRACE) { BOX_TRACE("Server started"); } else { ::fprintf(stdout, " done.\n"); } // wait a second for the pid to be written to the file ::sleep(1); // read pid file int pid = ReadPidFile(pidFile); // On Win32 we can check whether the PID in the pidFile matches // the one returned by the system, which it always should. if (pidIfKnown && pid != pidIfKnown) { BOX_ERROR("Server wrote wrong pid to file (" << pidFile << "): expected " << pidIfKnown << " but found " << pid); TEST_FAIL_WITH_MESSAGE("Server wrote wrong pid to file"); return -1; } return pid; } void TestRemoteProcessMemLeaksFunc(const char *filename, const char* file, int line) { #ifdef BOX_MEMORY_LEAK_TESTING // Does the file exist? if(!TestFileExists(filename)) { if (failures == 0) { first_fail_file = file; first_fail_line = line; } ++failures; printf("FAILURE: MemLeak report not available (file %s) " "at %s:%d\n", filename, file, line); } else { // Is it empty? if(TestGetFileSize(filename) > 0) { if (failures == 0) { first_fail_file = file; first_fail_line = line; } ++failures; printf("FAILURE: Memory leaks found in other process " "(file %s) at %s:%d\n==========\n", filename, file, line); FILE *f = fopen(filename, "r"); char linebuf[512]; while(::fgets(linebuf, sizeof(linebuf), f) != 0) { printf("%s", linebuf); } fclose(f); printf("==========\n"); } // Delete it ::unlink(filename); } #endif } void force_sync() { TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " "force-sync") == 0); TestRemoteProcessMemLeaks("bbackupctl.memleaks"); } void wait_for_sync_start() { TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " "wait-for-sync") == 0); TestRemoteProcessMemLeaks("bbackupctl.memleaks"); } void wait_for_sync_end() { TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " "wait-for-end") == 0); TestRemoteProcessMemLeaks("bbackupctl.memleaks"); } void sync_and_wait() { TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " "sync-and-wait") == 0); TestRemoteProcessMemLeaks("bbackupctl.memleaks"); } void terminate_bbackupd(int pid) { TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " "terminate") == 0); TestRemoteProcessMemLeaks("bbackupctl.memleaks"); for (int i = 0; i < 20; i++) { if (!ServerIsAlive(pid)) break; fprintf(stdout, "."); fflush(stdout); sleep(1); } TEST_THAT(!ServerIsAlive(pid)); TestRemoteProcessMemLeaks("bbackupd.memleaks"); } // Wait a given number of seconds for something to complete void wait_for_operation(int seconds, const char* message) { if (Logging::GetGlobalLevel() >= Log::TRACE) { BOX_TRACE("Waiting " << seconds << " seconds for " << message); } else { printf("Waiting for %s: ", message); fflush(stdout); } for(int l = 0; l < seconds; ++l) { sleep(1); if (Logging::GetGlobalLevel() < Log::TRACE) { printf("."); fflush(stdout); } } if (Logging::GetGlobalLevel() >= Log::TRACE) { BOX_TRACE("Finished waiting for " << message); } else { printf(" done.\n"); fflush(stdout); } } void safe_sleep(int seconds) { BOX_TRACE("sleeping for " << seconds << " seconds"); #ifdef WIN32 Sleep(seconds * 1000); #else struct timespec ts; memset(&ts, 0, sizeof(ts)); ts.tv_sec = seconds; ts.tv_nsec = 0; while (nanosleep(&ts, &ts) == -1 && errno == EINTR) { // FIXME evil hack for OSX, where ts.tv_sec contains // a negative number interpreted as unsigned 32-bit // when nanosleep() returns later than expected. int32_t secs = (int32_t) ts.tv_sec; int64_t remain_ns = (secs * 1000000000) + ts.tv_nsec; if (remain_ns < 0) { BOX_WARNING("nanosleep interrupted " << ((float)(0 - remain_ns) / 1000000000) << " secs late"); return; } BOX_TRACE("nanosleep interrupted with " << (remain_ns / 1000000000) << " secs remaining, " "sleeping again"); } #endif } boxbackup/lib/common/BufferedStream.h0000664000175000017500000000203111443463447020425 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BufferedStream.h // Purpose: Buffering read-only wrapper around IOStreams // Created: 2007/01/16 // // -------------------------------------------------------------------------- #ifndef BUFFEREDSTREAM__H #define BUFFEREDSTREAM__H #include "IOStream.h" class BufferedStream : public IOStream { private: IOStream& mrSource; char mBuffer[4096]; int mBufferSize; int mBufferPosition; public: BufferedStream(IOStream& rSource); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(IOStream::pos_type Offset, int SeekType); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: BufferedStream(const BufferedStream &rToCopy) : mrSource(rToCopy.mrSource) { /* do not call */ } }; #endif // BUFFEREDSTREAM__H boxbackup/lib/common/ReadLoggingStream.h0000664000175000017500000000272311053243116021057 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ReadLoggingStream.h // Purpose: Wrapper around IOStreams that logs read progress // Created: 2007/01/16 // // -------------------------------------------------------------------------- #ifndef READLOGGINGSTREAM__H #define READLOGGINGSTREAM__H #include "IOStream.h" #include "BoxTime.h" class ReadLoggingStream : public IOStream { public: class Logger { public: virtual ~Logger() { } virtual void Log(int64_t readSize, int64_t offset, int64_t length, box_time_t elapsed, box_time_t finish) = 0; virtual void Log(int64_t readSize, int64_t offset, int64_t length) = 0; virtual void Log(int64_t readSize, int64_t offset) = 0; }; private: IOStream& mrSource; IOStream::pos_type mOffset, mLength, mTotalRead; box_time_t mStartTime; Logger& mrLogger; public: ReadLoggingStream(IOStream& rSource, Logger& rLogger); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(IOStream::pos_type Offset, int SeekType); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: ReadLoggingStream(const ReadLoggingStream &rToCopy) : mrSource(rToCopy.mrSource), mrLogger(rToCopy.mrLogger) { /* do not call */ } }; #endif // READLOGGINGSTREAM__H boxbackup/lib/common/Configuration.h0000664000175000017500000001077011436002351020330 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Configuration // Purpose: Reading configuration files // Created: 2003/07/23 // // -------------------------------------------------------------------------- #ifndef CONFIGURATION__H #define CONFIGURATION__H #include #include #include #include #include // For defining tests enum { ConfigTest_LastEntry = 1, ConfigTest_Exists = 2, ConfigTest_IsInt = 4, ConfigTest_IsUint32 = 8, ConfigTest_MultiValueAllowed = 16, ConfigTest_IsBool = 32 }; class ConfigurationVerifyKey { public: typedef enum { NoDefaultValue = 1 } NoDefaultValue_t; ConfigurationVerifyKey(std::string name, int flags, void *testFunction = NULL); // to allow passing ConfigurationVerifyKey::NoDefaultValue // for default ListenAddresses ConfigurationVerifyKey(std::string name, int flags, NoDefaultValue_t t, void *testFunction = NULL); ConfigurationVerifyKey(std::string name, int flags, std::string defaultValue, void *testFunction = NULL); ConfigurationVerifyKey(std::string name, int flags, const char* defaultValue, void *testFunction = NULL); ConfigurationVerifyKey(std::string name, int flags, int defaultValue, void *testFunction = NULL); ConfigurationVerifyKey(std::string name, int flags, bool defaultValue, void *testFunction = NULL); const std::string& Name() const { return mName; } const std::string& DefaultValue() const { return mDefaultValue; } const bool HasDefaultValue() const { return mHasDefaultValue; } const int Flags() const { return mFlags; } const void* TestFunction() const { return mTestFunction; } ConfigurationVerifyKey(const ConfigurationVerifyKey& rToCopy); private: ConfigurationVerifyKey& operator=(const ConfigurationVerifyKey& noAssign); std::string mName; // "*" for all other keys (not implemented yet) std::string mDefaultValue; // default for when it's not present bool mHasDefaultValue; int mFlags; void *mTestFunction; // set to zero for now, will implement later }; class ConfigurationVerify { public: std::string mName; // "*" for all other sub config names const ConfigurationVerify *mpSubConfigurations; const ConfigurationVerifyKey *mpKeys; int Tests; void *TestFunction; // set to zero for now, will implement later }; class FdGetLine; // -------------------------------------------------------------------------- // // Class // Name: Configuration // Purpose: Loading, checking, and representing configuration files // Created: 2003/07/23 // // -------------------------------------------------------------------------- class Configuration { public: Configuration(const std::string &rName); Configuration(const Configuration &rToCopy); ~Configuration(); enum { // The character to separate multi-values MultiValueSeparator = '\x01' }; static std::auto_ptr LoadAndVerify( const std::string& rFilename, const ConfigurationVerify *pVerify, std::string &rErrorMsg); static std::auto_ptr Load( const std::string& rFilename, std::string &rErrorMsg) { return LoadAndVerify(rFilename, 0, rErrorMsg); } bool KeyExists(const std::string& rKeyName) const; const std::string &GetKeyValue(const std::string& rKeyName) const; int GetKeyValueInt(const std::string& rKeyName) const; uint32_t GetKeyValueUint32(const std::string& rKeyName) const; bool GetKeyValueBool(const std::string& rKeyName) const; std::vector GetKeyNames() const; bool SubConfigurationExists(const std::string& rSubName) const; const Configuration &GetSubConfiguration(const std::string& rSubName) const; Configuration &GetSubConfigurationEditable(const std::string& rSubName); std::vector GetSubConfigurationNames() const; void AddKeyValue(const std::string& rKey, const std::string& rValue); void AddSubConfig(const std::string& rName, const Configuration& rSubConfig); bool Verify(const ConfigurationVerify &rVerify, std::string &rErrorMsg) { return Verify(rVerify, std::string(), rErrorMsg); } private: std::string mName; // Order of keys not preserved std::map mKeys; // Order of sub blocks preserved typedef std::list > SubConfigListType; SubConfigListType mSubConfigurations; static bool LoadInto(Configuration &rConfig, FdGetLine &rGetLine, std::string &rErrorMsg, bool RootLevel); bool Verify(const ConfigurationVerify &rVerify, const std::string &rLevel, std::string &rErrorMsg); }; #endif // CONFIGURATION__H boxbackup/lib/common/PartialReadStream.cpp0000664000175000017500000000703110775523641021433 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: PartialReadStream.h // Purpose: Read part of another stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- #include "Box.h" #include "PartialReadStream.h" #include "CommonException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: PartialReadStream::PartialReadStream(IOStream &, // pos_type) // Purpose: Constructor, taking another stream and the number of // bytes to be read from it. // Created: 2003/08/26 // // -------------------------------------------------------------------------- PartialReadStream::PartialReadStream(IOStream &rSource, pos_type BytesToRead) : mrSource(rSource), mBytesLeft(BytesToRead) { ASSERT(BytesToRead > 0); } // -------------------------------------------------------------------------- // // Function // Name: PartialReadStream::~PartialReadStream() // Purpose: Destructor. Won't absorb any unread bytes. // Created: 2003/08/26 // // -------------------------------------------------------------------------- PartialReadStream::~PartialReadStream() { // Warn in debug mode if(mBytesLeft != 0) { BOX_TRACE("PartialReadStream destroyed with " << mBytesLeft << " bytes remaining"); } } // -------------------------------------------------------------------------- // // Function // Name: PartialReadStream::Read(void *, int, int) // Purpose: As interface. // Created: 2003/08/26 // // -------------------------------------------------------------------------- int PartialReadStream::Read(void *pBuffer, int NBytes, int Timeout) { // Finished? if(mBytesLeft <= 0) { return 0; } // Asking for more than is allowed? if(NBytes > mBytesLeft) { // Adjust downwards NBytes = mBytesLeft; } // Route the request to the source int read = mrSource.Read(pBuffer, NBytes, Timeout); ASSERT(read <= mBytesLeft); // Adjust the count mBytesLeft -= read; // Return the number read return read; } // -------------------------------------------------------------------------- // // Function // Name: PartialReadStream::BytesLeftToRead() // Purpose: As interface. // Created: 2003/08/26 // // -------------------------------------------------------------------------- IOStream::pos_type PartialReadStream::BytesLeftToRead() { return mBytesLeft; } // -------------------------------------------------------------------------- // // Function // Name: PartialReadStream::Write(const void *, int) // Purpose: As interface. But will exception. // Created: 2003/08/26 // // -------------------------------------------------------------------------- void PartialReadStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(CommonException, CantWriteToPartialReadStream) } // -------------------------------------------------------------------------- // // Function // Name: PartialReadStream::StreamDataLeft() // Purpose: As interface. // Created: 2003/08/26 // // -------------------------------------------------------------------------- bool PartialReadStream::StreamDataLeft() { return mBytesLeft != 0; } // -------------------------------------------------------------------------- // // Function // Name: PartialReadStream::StreamClosed() // Purpose: As interface. // Created: 2003/08/26 // // -------------------------------------------------------------------------- bool PartialReadStream::StreamClosed() { // always closed return true; } boxbackup/lib/common/makeexception.pl.in0000775000175000017500000001235111116307762021157 0ustar siretartsiretart#!@PERL@ # global exception list file my $global_list = '../../ExceptionCodes.txt'; my @exception; my @exception_desc; my $class; my $class_number; # read the description! open EXCEPTION_DESC,$ARGV[0] or die "Can't open $ARGV[0]"; while() { chomp; s/\A\s+//; s/#.+\Z//; s/\s+\Z//; s/\s+/ /g; next unless m/\S/; if(m/\AEXCEPTION\s+(.+)\s+(\d+)\Z/) { $class = $1; $class_number = $2; } else { my ($name,$number,$description) = split /\s+/,$_,3; if($name eq '' || $number =~ m/\D/) { die "Bad line '$_'"; } if($exception[$number] ne '') { die "Duplicate exception number $number"; } $exception[$number] = $name; $exception_desc[$number] = $description; } } die "Exception class and number not specified" unless $class ne '' && $class_number ne ''; close EXCEPTION_DESC; # write the code print "Generating $class exception...\n"; open CPP,">autogen_${class}Exception.cpp" or die "Can't open cpp file for writing"; open H,">autogen_${class}Exception.h" or die "Can't open h file for writing"; # write header file my $guardname = uc 'AUTOGEN_'.$class.'EXCEPTION_H'; print H <<__E; // Auto-generated file -- do not edit #ifndef $guardname #define $guardname #include "BoxException.h" // -------------------------------------------------------------------------- // // Class // Name: ${class}Exception // Purpose: Exception // Created: autogen // // -------------------------------------------------------------------------- class ${class}Exception : public BoxException { public: ${class}Exception(unsigned int SubType, const std::string& rMessage = "") : mSubType(SubType), mMessage(rMessage) { } ${class}Exception(const ${class}Exception &rToCopy) : mSubType(rToCopy.mSubType), mMessage(rToCopy.mMessage) { } ~${class}Exception() throw () { } enum { ExceptionType = $class_number }; enum { __E for(my $e = 0; $e <= $#exception; $e++) { if($exception[$e] ne '') { print H "\t\t".$exception[$e].' = '.$e.(($e==$#exception)?'':',')."\n" } } print H <<__E; }; virtual unsigned int GetType() const throw(); virtual unsigned int GetSubType() const throw(); virtual const char *what() const throw(); virtual const std::string& GetMessage() const { return mMessage; } private: unsigned int mSubType; std::string mMessage; }; #endif // $guardname __E # ----------------------------------------------------------------------------------------------------------- print CPP <<__E; // Auto-generated file -- do not edit #include "Box.h" #include "autogen_${class}Exception.h" #include "MemLeakFindOn.h" #ifdef EXCEPTION_CODENAMES_EXTENDED #ifdef EXCEPTION_CODENAMES_EXTENDED_WITH_DESCRIPTION static const char *whats[] = { __E my $last_seen = -1; for(my $e = 0; $e <= $#exception; $e++) { if($exception[$e] ne '') { for(my $s = $last_seen + 1; $s < $e; $s++) { print CPP "\t\"UNUSED\",\n" } my $ext = ($exception_desc[$e] ne '')?" ($exception_desc[$e])":''; print CPP "\t\"${class} ".$exception[$e].$ext.'"'.(($e==$#exception)?'':',')."\n"; $last_seen = $e; } } print CPP <<__E; }; #else static const char *whats[] = { __E $last_seen = -1; for(my $e = 0; $e <= $#exception; $e++) { if($exception[$e] ne '') { for(my $s = $last_seen + 1; $s < $e; $s++) { print CPP "\t\"UNUSED\",\n" } print CPP "\t\"${class} ".$exception[$e].'"'.(($e==$#exception)?'':',')."\n"; $last_seen = $e; } } print CPP <<__E; }; #endif #endif unsigned int ${class}Exception::GetType() const throw() { return ${class}Exception::ExceptionType; } unsigned int ${class}Exception::GetSubType() const throw() { return mSubType; } const char *${class}Exception::what() const throw() { #ifdef EXCEPTION_CODENAMES_EXTENDED if(mSubType < 0 || mSubType > (sizeof(whats) / sizeof(whats[0]))) { return "${class}"; } return whats[mSubType]; #else return "${class}"; #endif } __E close H; close CPP; # update the global exception list my $list_before; my $list_after; my $is_after = 0; if(open CURRENT,$global_list) { while() { next if m/\A#/; if(m/\AEXCEPTION TYPE (\w+) (\d+)/) { # check that the number isn't being reused if($2 == $class_number && $1 ne $class) { die "Class number $class_number is being used by $class and $1 -- correct this.\n"; } if($2 > $class_number) { # This class comes after the current one (ensures numerical ordering) $is_after = 1; } if($1 eq $class) { # skip this entry while() { last if m/\AEND TYPE/; } $_ = ''; } } if($is_after) { $list_after .= $_; } else { $list_before .= $_; } } close CURRENT; } open GLOBAL,">$global_list" or die "Can't open global exception code listing for writing"; print GLOBAL <<__E; # # automatically generated file, do not edit. # # This file lists all the exception codes used by the system. # Use to look up more detailed descriptions of meanings of errors. # __E print GLOBAL $list_before; print GLOBAL "EXCEPTION TYPE $class $class_number\n"; for(my $e = 0; $e <= $#exception; $e++) { if($exception[$e] ne '') { my $ext = ($exception_desc[$e] ne '')?" - $exception_desc[$e]":''; print GLOBAL "($class_number/$e) - ${class} ".$exception[$e].$ext."\n"; } } print GLOBAL "END TYPE\n"; print GLOBAL $list_after; close GLOBAL; boxbackup/lib/common/InvisibleTempFileStream.cpp0000664000175000017500000000207711205462225022607 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: InvisibleTempFileStream.cpp // Purpose: IOStream interface to temporary files that // delete themselves // Created: 2006/10/13 // // -------------------------------------------------------------------------- #include "Box.h" #include "InvisibleTempFileStream.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: InvisibleTempFileStream::InvisibleTempFileStream // (const char *, int, int) // Purpose: Constructor, opens invisible file // Created: 2006/10/13 // // -------------------------------------------------------------------------- InvisibleTempFileStream::InvisibleTempFileStream(const char *Filename, int flags, int mode) #ifdef WIN32 : FileStream(Filename, flags | O_TEMPORARY, mode) #else : FileStream(Filename, flags, mode) #endif { #ifndef WIN32 if(unlink(Filename) != 0) { MEMLEAKFINDER_NOT_A_LEAK(this); THROW_EXCEPTION(CommonException, OSFileOpenError) } #endif } boxbackup/lib/common/ExcludeList.h0000664000175000017500000000353110652215304017746 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ExcludeList.h // Purpose: General purpose exclusion list // Created: 28/1/04 // // -------------------------------------------------------------------------- #ifndef EXCLUDELIST__H #define EXCLUDELIST__H #include #include #include // avoid including regex.h in lots of places #ifndef EXCLUDELIST_IMPLEMENTATION_REGEX_T_DEFINED typedef int regex_t; #endif class Archive; // -------------------------------------------------------------------------- // // Class // Name: ExcludeList // Purpose: General purpose exclusion list // Created: 28/1/04 // // -------------------------------------------------------------------------- class ExcludeList { public: ExcludeList(); ~ExcludeList(); void Deserialize(Archive & rArchive); void Serialize(Archive & rArchive) const; void AddDefiniteEntries(const std::string &rEntries); void AddRegexEntries(const std::string &rEntries); // Add exceptions to the exclusions (takes ownership) void SetAlwaysIncludeList(ExcludeList *pAlwaysInclude); // Test function bool IsExcluded(const std::string &rTest) const; // Mainly for tests unsigned int SizeOfDefiniteList() const {return mDefinite.size();} unsigned int SizeOfRegexList() const #ifdef HAVE_REGEX_SUPPORT {return mRegex.size();} #else {return 0;} #endif private: std::set mDefinite; #ifdef HAVE_REGEX_SUPPORT std::vector mRegex; std::vector mRegexStr; // save original regular expression string-based source for Serialize #endif #ifdef WIN32 std::string ReplaceSlashesDefinite(const std::string& input) const; std::string ReplaceSlashesRegex (const std::string& input) const; #endif // For exceptions to the excludes ExcludeList *mpAlwaysInclude; }; #endif // EXCLUDELIST__H boxbackup/lib/common/Archive.h0000664000175000017500000000702510367470545017121 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Archive.h // Purpose: Backup daemon state archive // Created: 2005/04/11 // // -------------------------------------------------------------------------- #ifndef ARCHIVE__H #define ARCHIVE__H #include #include #include #include "IOStream.h" #include "Guards.h" #define ARCHIVE_GET_SIZE(hdr) (( ((uint8_t)((hdr)[0])) | ( ((uint8_t)((hdr)[1])) << 8)) >> 2) #define ARCHIVE_MAGIC_VALUE_RECURSE 0x4449525F #define ARCHIVE_MAGIC_VALUE_NOOP 0x5449525F class Archive { public: Archive(IOStream &Stream, int Timeout) : mrStream(Stream) { mTimeout = Timeout; } private: // no copying Archive(const Archive &); Archive & operator=(const Archive &); public: ~Archive() { } // // // void Write(bool Item) { Write((int) Item); } void Write(int Item) { int32_t privItem = htonl(Item); mrStream.Write(&privItem, sizeof(privItem)); } void Write(int64_t Item) { int64_t privItem = box_hton64(Item); mrStream.Write(&privItem, sizeof(privItem)); } void Write(uint64_t Item) { uint64_t privItem = box_hton64(Item); mrStream.Write(&privItem, sizeof(privItem)); } void Write(uint8_t Item) { int privItem = Item; Write(privItem); } void Write(const std::string &Item) { int size = Item.size(); Write(size); mrStream.Write(Item.c_str(), size); } // // // void Read(bool &rItemOut) { int privItem; Read(privItem); if (privItem) { rItemOut = true; } else { rItemOut = false; } } void Read(int &rItemOut) { int32_t privItem; if(!mrStream.ReadFullBuffer(&privItem, sizeof(privItem), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) } rItemOut = ntohl(privItem); } void Read(int64_t &rItemOut) { int64_t privItem; if(!mrStream.ReadFullBuffer(&privItem, sizeof(privItem), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) } rItemOut = box_ntoh64(privItem); } void Read(uint64_t &rItemOut) { uint64_t privItem; if(!mrStream.ReadFullBuffer(&privItem, sizeof(privItem), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) } rItemOut = box_ntoh64(privItem); } void Read(uint8_t &rItemOut) { int privItem; Read(privItem); rItemOut = privItem; } void Read(std::string &rItemOut) { int size; Read(size); // Assume most strings are relatively small char buf[256]; if(size < (int) sizeof(buf)) { // Fetch rest of pPayload, relying on the Protocol to error on stupidly large sizes for us if(!mrStream.ReadFullBuffer(buf, size, 0 /* not interested in bytes read if this fails */, mTimeout)) { THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) } // assign to this string, storing the header and the extra payload rItemOut.assign(buf, size); } else { // Block of memory to hold it MemoryBlockGuard dataB(size); char *ppayload = dataB; // Fetch rest of pPayload, relying on the Protocol to error on stupidly large sizes for us if(!mrStream.ReadFullBuffer(ppayload, size, 0 /* not interested in bytes read if this fails */, mTimeout)) { THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) } // assign to this string, storing the header and the extra pPayload rItemOut.assign(ppayload, size); } } private: IOStream &mrStream; int mTimeout; }; #endif // ARCHIVE__H boxbackup/lib/common/Timer.cpp0000664000175000017500000003633011163700314017135 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Timer.cpp // Purpose: Generic timers which execute arbitrary code when // they expire. // Created: 5/11/2006 // // -------------------------------------------------------------------------- #ifdef WIN32 #define _WIN32_WINNT 0x0500 #endif #include "Box.h" #include #include #include "Timer.h" #include "Logging.h" #include "MemLeakFindOn.h" std::vector* Timers::spTimers = NULL; bool Timers::sRescheduleNeeded = false; #define TIMER_ID "timer " << mName << " (" << this << ") " #define TIMER_ID_OF(t) "timer " << (t).GetName() << " (" << &(t) << ") " typedef void (*sighandler_t)(int); // -------------------------------------------------------------------------- // // Function // Name: static void Timers::Init() // Purpose: Initialise timers, prepare signal handler // Created: 5/11/2006 // // -------------------------------------------------------------------------- void Timers::Init() { ASSERT(!spTimers); #if defined WIN32 && ! defined PLATFORM_CYGWIN // no init needed #else struct sigaction newact, oldact; newact.sa_handler = Timers::SignalHandler; newact.sa_flags = SA_RESTART; sigemptyset(&newact.sa_mask); if (::sigaction(SIGALRM, &newact, &oldact) != 0) { BOX_ERROR("Failed to install signal handler"); THROW_EXCEPTION(CommonException, Internal); } ASSERT(oldact.sa_handler == 0); #endif // WIN32 && !PLATFORM_CYGWIN spTimers = new std::vector; } // -------------------------------------------------------------------------- // // Function // Name: static void Timers::Cleanup() // Purpose: Clean up timers, stop signal handler // Created: 6/11/2006 // // -------------------------------------------------------------------------- void Timers::Cleanup() { ASSERT(spTimers); if (!spTimers) { BOX_ERROR("Tried to clean up timers when not initialised!"); return; } #if defined WIN32 && ! defined PLATFORM_CYGWIN // no cleanup needed #else struct itimerval timeout; memset(&timeout, 0, sizeof(timeout)); int result = ::setitimer(ITIMER_REAL, &timeout, NULL); ASSERT(result == 0); struct sigaction newact, oldact; newact.sa_handler = SIG_DFL; newact.sa_flags = SA_RESTART; sigemptyset(&(newact.sa_mask)); if (::sigaction(SIGALRM, &newact, &oldact) != 0) { BOX_ERROR("Failed to remove signal handler"); THROW_EXCEPTION(CommonException, Internal); } ASSERT(oldact.sa_handler == Timers::SignalHandler); #endif // WIN32 && !PLATFORM_CYGWIN spTimers->clear(); delete spTimers; spTimers = NULL; } // -------------------------------------------------------------------------- // // Function // Name: static void Timers::Add(Timer&) // Purpose: Add a new timer to the set, and reschedule next wakeup // Created: 5/11/2006 // // -------------------------------------------------------------------------- void Timers::Add(Timer& rTimer) { ASSERT(spTimers); ASSERT(&rTimer); spTimers->push_back(&rTimer); Reschedule(); } // -------------------------------------------------------------------------- // // Function // Name: static void Timers::Remove(Timer&) // Purpose: Removes the timer from the set (preventing it from // being called) and reschedule next wakeup // Created: 5/11/2006 // // -------------------------------------------------------------------------- void Timers::Remove(Timer& rTimer) { ASSERT(spTimers); ASSERT(&rTimer); bool restart = true; while (restart) { restart = false; for (std::vector::iterator i = spTimers->begin(); i != spTimers->end(); i++) { if (&rTimer == *i) { spTimers->erase(i); restart = true; break; } } } Reschedule(); } void Timers::RequestReschedule() { sRescheduleNeeded = true; } void Timers::RescheduleIfNeeded() { if (sRescheduleNeeded) { Reschedule(); } } #define FORMAT_MICROSECONDS(t) \ (int)(t / 1000000) << "." << \ (int)(t % 1000000) << " seconds" // -------------------------------------------------------------------------- // // Function // Name: static void Timers::Reschedule() // Purpose: Recalculate when the next wakeup is due // Created: 5/11/2006 // // -------------------------------------------------------------------------- void Timers::Reschedule() { ASSERT(spTimers); if (spTimers == NULL) { THROW_EXCEPTION(CommonException, Internal) } #ifndef WIN32 struct sigaction oldact; if (::sigaction(SIGALRM, NULL, &oldact) != 0) { BOX_ERROR("Failed to check signal handler"); THROW_EXCEPTION(CommonException, Internal) } ASSERT(oldact.sa_handler == Timers::SignalHandler); if (oldact.sa_handler != Timers::SignalHandler) { BOX_ERROR("Signal handler was " << (void *)oldact.sa_handler << ", expected " << (void *)Timers::SignalHandler); THROW_EXCEPTION(CommonException, Internal) } #endif // Clear the reschedule-needed flag to false before we start. // If a timer event occurs while we are scheduling, then we // may or may not need to reschedule again, but this way // we will do it anyway. sRescheduleNeeded = false; #ifdef WIN32 // win32 timers need no management #else box_time_t timeNow = GetCurrentBoxTime(); // scan for, trigger and remove expired timers. Removal requires // us to restart the scan each time, due to std::vector semantics. bool restart = true; while (restart) { restart = false; for (std::vector::iterator i = spTimers->begin(); i != spTimers->end(); i++) { Timer& rTimer = **i; int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow; if (timeToExpiry <= 0) { /* BOX_TRACE("timer " << *i << " has expired, " "triggering it"); */ BOX_TRACE(TIMER_ID_OF(**i) "has expired, " "triggering " << FORMAT_MICROSECONDS(-timeToExpiry) << " late"); rTimer.OnExpire(); spTimers->erase(i); restart = true; break; } else { /* BOX_TRACE("timer " << *i << " has not " "expired, triggering in " << FORMAT_MICROSECONDS(timeToExpiry) << " seconds"); */ } } } // Now the only remaining timers should all be in the future. // Scan to find the next one to fire (earliest deadline). int64_t timeToNextEvent = 0; std::string nameOfNextEvent; for (std::vector::iterator i = spTimers->begin(); i != spTimers->end(); i++) { Timer& rTimer = **i; int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow; ASSERT(timeToExpiry > 0) if (timeToExpiry <= 0) { timeToExpiry = 1; } if (timeToNextEvent == 0 || timeToNextEvent > timeToExpiry) { timeToNextEvent = timeToExpiry; nameOfNextEvent = rTimer.GetName(); } } ASSERT(timeToNextEvent >= 0); if (timeToNextEvent == 0) { BOX_TRACE("timer: no more events, going to sleep."); } else { BOX_TRACE("timer: next event: " << nameOfNextEvent << " expires in " << FORMAT_MICROSECONDS(timeToNextEvent)); } struct itimerval timeout; memset(&timeout, 0, sizeof(timeout)); timeout.it_value.tv_sec = BoxTimeToSeconds(timeToNextEvent); timeout.it_value.tv_usec = (int) (BoxTimeToMicroSeconds(timeToNextEvent) % MICRO_SEC_IN_SEC); if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0) { BOX_ERROR("Failed to initialise system timer\n"); THROW_EXCEPTION(CommonException, Internal) } #endif } // -------------------------------------------------------------------------- // // Function // Name: static void Timers::SignalHandler(unused) // Purpose: Called as signal handler. Nothing is safe in a signal // handler, not even traversing the list of timers, so // just request a reschedule in future, which will do // that for us, and trigger any expired timers at that // time. // Created: 5/11/2006 // // -------------------------------------------------------------------------- void Timers::SignalHandler(int unused) { // ASSERT(spTimers); Timers::RequestReschedule(); } // -------------------------------------------------------------------------- // // Function // Name: Timer::Timer(size_t timeoutSecs, // const std::string& rName) // Purpose: Standard timer constructor, takes a timeout in // seconds from now, and an optional name for // logging purposes. // Created: 27/07/2008 // // -------------------------------------------------------------------------- Timer::Timer(size_t timeoutSecs, const std::string& rName) : mExpires(GetCurrentBoxTime() + SecondsToBoxTime(timeoutSecs)), mExpired(false), mName(rName) #ifdef WIN32 , mTimerHandle(INVALID_HANDLE_VALUE) #endif { #ifndef BOX_RELEASE_BUILD if (timeoutSecs == 0) { BOX_TRACE(TIMER_ID "initialised for " << timeoutSecs << " secs, will not fire"); } else { BOX_TRACE(TIMER_ID "initialised for " << timeoutSecs << " secs, to fire at " << FormatTime(mExpires, false, true)); } #endif if (timeoutSecs == 0) { mExpires = 0; } else { Timers::Add(*this); Start(timeoutSecs * MICRO_SEC_IN_SEC_LL); } } // -------------------------------------------------------------------------- // // Function // Name: Timer::Start() // Purpose: This internal function initialises an OS TimerQueue // timer on Windows, while on Unixes there is only a // single global timer, managed by the Timers class, // so this method does nothing. // Created: 27/07/2008 // // -------------------------------------------------------------------------- void Timer::Start() { #ifdef WIN32 box_time_t timeNow = GetCurrentBoxTime(); int64_t timeToExpiry = mExpires - timeNow; if (timeToExpiry <= 0) { BOX_WARNING(TIMER_ID << "fudging expiry from -" << FORMAT_MICROSECONDS(-timeToExpiry)) timeToExpiry = 1; } Start(timeToExpiry); #endif } // -------------------------------------------------------------------------- // // Function // Name: Timer::Start(int64_t delayInMicros) // Purpose: This internal function initialises an OS TimerQueue // timer on Windows, with a specified delay already // calculated to save us doing it again. Like // Timer::Start(), on Unixes it does nothing. // Created: 27/07/2008 // // -------------------------------------------------------------------------- void Timer::Start(int64_t delayInMicros) { #ifdef WIN32 // only call me once! ASSERT(mTimerHandle == INVALID_HANDLE_VALUE); int64_t delayInMillis = delayInMicros / 1000; // Windows XP always seems to fire timers up to 20 ms late, // at least on my test laptop. Not critical in practice, but our // tests are precise enough that they will fail if we don't // correct for it. delayInMillis -= 20; // Set a system timer to call our timer routine if (CreateTimerQueueTimer(&mTimerHandle, NULL, TimerRoutine, (PVOID)this, delayInMillis, 0, WT_EXECUTEINTIMERTHREAD) == FALSE) { BOX_ERROR(TIMER_ID "failed to create timer: " << GetErrorMessage(GetLastError())); mTimerHandle = INVALID_HANDLE_VALUE; } #endif } // -------------------------------------------------------------------------- // // Function // Name: Timer::Stop() // Purpose: This internal function deletes the associated OS // TimerQueue timer on Windows, and on Unixes does // nothing. // Created: 27/07/2008 // // -------------------------------------------------------------------------- void Timer::Stop() { #ifdef WIN32 if (mTimerHandle != INVALID_HANDLE_VALUE) { if (DeleteTimerQueueTimer(NULL, mTimerHandle, INVALID_HANDLE_VALUE) == FALSE) { BOX_ERROR(TIMER_ID "failed to delete timer: " << GetErrorMessage(GetLastError())); } mTimerHandle = INVALID_HANDLE_VALUE; } #endif } // -------------------------------------------------------------------------- // // Function // Name: Timer::~Timer() // Purpose: Destructor for Timer objects. // Created: 27/07/2008 // // -------------------------------------------------------------------------- Timer::~Timer() { #ifndef BOX_RELEASE_BUILD BOX_TRACE(TIMER_ID "destroyed"); #endif Timers::Remove(*this); Stop(); } // -------------------------------------------------------------------------- // // Function // Name: Timer::Timer(Timer& rToCopy) // Purpose: Copy constructor for Timer objects. Creates a new // timer that will trigger at the same time as the // original. The original will usually be discarded. // Created: 27/07/2008 // // -------------------------------------------------------------------------- Timer::Timer(const Timer& rToCopy) : mExpires(rToCopy.mExpires), mExpired(rToCopy.mExpired), mName(rToCopy.mName) #ifdef WIN32 , mTimerHandle(INVALID_HANDLE_VALUE) #endif { #ifndef BOX_RELEASE_BUILD if (mExpired) { BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " "already expired, will not fire"); } else if (mExpires == 0) { BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " "no expiry, will not fire"); } else { BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " "to fire at " << (int)(mExpires / 1000000) << "." << (int)(mExpires % 1000000)); } #endif if (!mExpired && mExpires != 0) { Timers::Add(*this); Start(); } } // -------------------------------------------------------------------------- // // Function // Name: Timer::operator=(const Timer& rToCopy) // Purpose: Assignment operator for Timer objects. Works // exactly the same as the copy constructor, except // that if the receiving timer is already running, // it is stopped first. // Created: 27/07/2008 // // -------------------------------------------------------------------------- Timer& Timer::operator=(const Timer& rToCopy) { #ifndef BOX_RELEASE_BUILD if (rToCopy.mExpired) { BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " "already expired, will not fire"); } else if (rToCopy.mExpires == 0) { BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " "no expiry, will not fire"); } else { BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " "to fire at " << (int)(rToCopy.mExpires / 1000000) << "." << (int)(rToCopy.mExpires % 1000000)); } #endif Timers::Remove(*this); Stop(); mExpires = rToCopy.mExpires; mExpired = rToCopy.mExpired; mName = rToCopy.mName; if (!mExpired && mExpires != 0) { Timers::Add(*this); Start(); } return *this; } // -------------------------------------------------------------------------- // // Function // Name: Timer::OnExpire() // Purpose: Method called by Timers::Reschedule (on Unixes) // on next poll after timer expires, or from // Timer::TimerRoutine (on Windows) from a separate // thread managed by the OS. Marks the timer as // expired for future reference. // Created: 27/07/2008 // // -------------------------------------------------------------------------- void Timer::OnExpire() { #ifndef BOX_RELEASE_BUILD BOX_TRACE(TIMER_ID "fired"); #endif mExpired = true; } // -------------------------------------------------------------------------- // // Function // Name: Timer::TimerRoutine(PVOID lpParam, // BOOLEAN TimerOrWaitFired) // Purpose: Static method called by the Windows OS when a // TimerQueue timer expires. // Created: 27/07/2008 // // -------------------------------------------------------------------------- #ifdef WIN32 VOID CALLBACK Timer::TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired) { Timer* pTimer = (Timer*)lpParam; pTimer->OnExpire(); // is it safe to write to write debug output from a timer? // e.g. to write to the Event Log? } #endif boxbackup/lib/common/BufferedStream.cpp0000664000175000017500000001245511443463447020773 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BufferedStream.cpp // Purpose: Buffering read-only wrapper around IOStreams // Created: 2007/01/16 // // -------------------------------------------------------------------------- #include "Box.h" #include "BufferedStream.h" #include "CommonException.h" #include #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::BufferedStream(const char *, int, int) // Purpose: Constructor, set up buffer // Created: 2007/01/16 // // -------------------------------------------------------------------------- BufferedStream::BufferedStream(IOStream& rSource) : mrSource(rSource), mBufferSize(0), mBufferPosition(0) { } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::Read(void *, int) // Purpose: Reads bytes from the file // Created: 2007/01/16 // // -------------------------------------------------------------------------- int BufferedStream::Read(void *pBuffer, int NBytes, int Timeout) { if (mBufferSize == mBufferPosition) { // buffer is empty, fill it. int numBytesRead = mrSource.Read(mBuffer, sizeof(mBuffer), Timeout); if (numBytesRead < 0) { return numBytesRead; } mBufferSize = numBytesRead; } int sizeToReturn = mBufferSize - mBufferPosition; if (sizeToReturn > NBytes) { sizeToReturn = NBytes; } memcpy(pBuffer, mBuffer + mBufferPosition, sizeToReturn); mBufferPosition += sizeToReturn; if (mBufferPosition == mBufferSize) { // clear out the buffer mBufferSize = 0; mBufferPosition = 0; } return sizeToReturn; } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::BytesLeftToRead() // Purpose: Returns number of bytes to read (may not be most efficient function ever) // Created: 2007/01/16 // // -------------------------------------------------------------------------- IOStream::pos_type BufferedStream::BytesLeftToRead() { return mrSource.BytesLeftToRead() + mBufferSize - mBufferPosition; } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::Write(void *, int) // Purpose: Writes bytes to the underlying stream (not supported) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void BufferedStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::GetPosition() // Purpose: Get position in stream // Created: 2003/08/21 // // -------------------------------------------------------------------------- IOStream::pos_type BufferedStream::GetPosition() const { return mrSource.GetPosition() - mBufferSize + mBufferPosition; } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::Seek(pos_type, int) // Purpose: Seeks within file, as lseek, invalidate buffer // Created: 2003/07/31 // // -------------------------------------------------------------------------- void BufferedStream::Seek(IOStream::pos_type Offset, int SeekType) { switch (SeekType) { case SeekType_Absolute: { // just go there mrSource.Seek(Offset, SeekType); } break; case SeekType_Relative: { // Actual underlying file position is // (mBufferSize - mBufferPosition) ahead of us. // Need to subtract that amount from the seek // to seek forward that much less, putting the // real pointer in the right place. mrSource.Seek(Offset - mBufferSize + mBufferPosition, SeekType); } break; case SeekType_End: { // Actual underlying file position is // (mBufferSize - mBufferPosition) ahead of us. // Need to add that amount to the seek // to seek backwards that much more, putting the // real pointer in the right place. mrSource.Seek(Offset + mBufferSize - mBufferPosition, SeekType); } } // always clear the buffer for now (may be slightly wasteful) mBufferSize = 0; mBufferPosition = 0; } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::Close() // Purpose: Closes the underlying stream (not needed) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void BufferedStream::Close() { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::StreamDataLeft() // Purpose: Any data left to write? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool BufferedStream::StreamDataLeft() { return mrSource.StreamDataLeft(); } // -------------------------------------------------------------------------- // // Function // Name: BufferedStream::StreamClosed() // Purpose: Is the stream closed? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool BufferedStream::StreamClosed() { return mrSource.StreamClosed(); } boxbackup/lib/common/BoxPortsAndFiles.h.in0000664000175000017500000000310111221741360021304 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxPortsAndFiles.h // Purpose: Central list of which tcp/ip ports and hardcoded file locations // Created: 2003/08/20 // // -------------------------------------------------------------------------- #ifndef BOXPORTSANDFILES__H #define BOXPORTSANDFILES__H #define BOX_PORT_BASE 2200 // Backup store daemon #define BOX_PORT_BBSTORED (BOX_PORT_BASE+1) #define BOX_PORT_BBSTORED_TEST 22011 // directory within the RAIDFILE root for the backup store daemon #define BOX_RAIDFILE_ROOT_BBSTORED "backup" // configuration file paths #ifdef WIN32 // no default config file path, use these macros to call // GetDefaultConfigFilePath() instead. #define BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE \ GetDefaultConfigFilePath("bbackupd.conf").c_str() #define BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE \ GetDefaultConfigFilePath("raidfile.conf").c_str() #define BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE \ GetDefaultConfigFilePath("bbstored.conf").c_str() #else #define BOX_FILE_BBACKUPD_DEFAULT_CONFIG "@sysconfdir_expanded@/boxbackup/bbackupd.conf" #define BOX_FILE_RAIDFILE_DEFAULT_CONFIG "@sysconfdir_expanded@/boxbackup/raidfile.conf" #define BOX_FILE_BBSTORED_DEFAULT_CONFIG "@sysconfdir_expanded@/boxbackup/bbstored.conf" #define BOX_FILE_BBACKUPD_OLD_CONFIG "@sysconfdir_expanded@/box/bbackupd.conf" #define BOX_FILE_RAIDFILE_OLD_CONFIG "@sysconfdir_expanded@/box/raidfile.conf" #define BOX_FILE_BBSTORED_OLD_CONFIG "@sysconfdir_expanded@/box/bbstored.conf" #endif #endif // BOXPORTSANDFILES__H boxbackup/lib/common/IOStream.h0000664000175000017500000000373711130254341017210 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: IOStream.h // Purpose: I/O Stream abstraction // Created: 2003/07/31 // // -------------------------------------------------------------------------- #ifndef IOSTREAM__H #define IOSTREAM__H // -------------------------------------------------------------------------- // // Class // Name: IOStream // Purpose: Abstract interface to streams of data // Created: 2003/07/31 // // -------------------------------------------------------------------------- class IOStream { public: IOStream(); virtual ~IOStream(); private: IOStream(const IOStream &rToCopy); /* forbidden */ IOStream& operator=(const IOStream &rToCopy); /* forbidden */ public: enum { TimeOutInfinite = -1, SizeOfStreamUnknown = -1 }; enum { SeekType_Absolute = 0, SeekType_Relative = 1, SeekType_End = 2 }; // Timeout in milliseconds // Read may return 0 -- does not mean end of stream. typedef int64_t pos_type; virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) = 0; virtual pos_type BytesLeftToRead(); // may return IOStream::SizeOfStreamUnknown (and will for most stream types) virtual void Write(const void *pBuffer, int NBytes) = 0; virtual void Write(const char *pBuffer); virtual void WriteAllBuffered(); virtual pos_type GetPosition() const; virtual void Seek(pos_type Offset, int SeekType); virtual void Close(); // Has all data that can be read been read? virtual bool StreamDataLeft() = 0; // Has the stream been closed (writing not possible) virtual bool StreamClosed() = 0; // Utility functions bool ReadFullBuffer(void *pBuffer, int NBytes, int *pNBytesRead, int Timeout = IOStream::TimeOutInfinite); bool CopyStreamTo(IOStream &rCopyTo, int Timeout = IOStream::TimeOutInfinite, int BufferSize = 1024); void Flush(int Timeout = IOStream::TimeOutInfinite); static int ConvertSeekTypeToOSWhence(int SeekType); }; #endif // IOSTREAM__H boxbackup/lib/common/Box.h0000664000175000017500000001234111345271627016262 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Box.h // Purpose: Main header file for the Box project // Created: 2003/07/08 // // -------------------------------------------------------------------------- #ifndef BOX__H #define BOX__H // Use the same changes as gcc3 for gcc4 #ifdef PLATFORM_GCC4 #define PLATFORM_GCC3 #endif #include "BoxPlatform.h" // uncomment this line to enable full memory leak finding on all // malloc-ed blocks (at least, ones used by the STL) //#define MEMLEAKFINDER_FULL_MALLOC_MONITORING // Show backtraces on exceptions in release builds until further notice // (they are only logged at TRACE level anyway) #ifdef HAVE_EXECINFO_H #define SHOW_BACKTRACE_ON_EXCEPTION #endif #ifdef SHOW_BACKTRACE_ON_EXCEPTION #include "Utils.h" #define OPTIONAL_DO_BACKTRACE DumpStackBacktrace(); #else #define OPTIONAL_DO_BACKTRACE #endif #include "CommonException.h" #include "Logging.h" #ifndef BOX_RELEASE_BUILD extern bool AssertFailuresToSyslog; #define ASSERT_FAILS_TO_SYSLOG_ON {AssertFailuresToSyslog = true;} void BoxDebugAssertFailed(const char *cond, const char *file, int line); #define ASSERT(cond) \ { \ if(!(cond)) \ { \ BoxDebugAssertFailed(#cond, __FILE__, __LINE__); \ THROW_EXCEPTION_MESSAGE(CommonException, \ AssertFailed, #cond); \ } \ } // Note that syslog tracing is independent of BoxDebugTraceOn, // but stdout tracing is not extern bool BoxDebugTraceToSyslog; #define TRACE_TO_SYSLOG(x) {BoxDebugTraceToSyslog = x;} extern bool BoxDebugTraceToStdout; #define TRACE_TO_STDOUT(x) {BoxDebugTraceToStdout = x;} extern bool BoxDebugTraceOn; int BoxDebug_printf(const char *format, ...); int BoxDebugTrace(const char *format, ...); #ifndef PLATFORM_DISABLE_MEM_LEAK_TESTING #define BOX_MEMORY_LEAK_TESTING #endif // Exception names #define EXCEPTION_CODENAMES_EXTENDED #else #define ASSERT_FAILS_TO_SYSLOG_ON #define ASSERT(cond) #define TRACE_TO_SYSLOG(x) {} #define TRACE_TO_STDOUT(x) {} // Box Backup builds release get extra information for exception logging #define EXCEPTION_CODENAMES_EXTENDED #define EXCEPTION_CODENAMES_EXTENDED_WITH_DESCRIPTION #endif #ifdef BOX_MEMORY_LEAK_TESTING // Memory leak testing #include "MemLeakFinder.h" #define DEBUG_NEW new(__FILE__,__LINE__) #define MEMLEAKFINDER_NOT_A_LEAK(x) memleakfinder_notaleak(x); #define MEMLEAKFINDER_NO_LEAKS MemLeakSuppressionGuard _guard; #define MEMLEAKFINDER_INIT memleakfinder_init(); #define MEMLEAKFINDER_START {memleakfinder_global_enable = true;} #define MEMLEAKFINDER_STOP {memleakfinder_global_enable = false;} #else #define DEBUG_NEW new #define MEMLEAKFINDER_NOT_A_LEAK(x) #define MEMLEAKFINDER_NO_LEAKS #define MEMLEAKFINDER_INIT #define MEMLEAKFINDER_START #define MEMLEAKFINDER_STOP #endif #define THROW_EXCEPTION(type, subtype) \ { \ if(!HideExceptionMessageGuard::ExceptionsHidden()) \ { \ OPTIONAL_DO_BACKTRACE \ BOX_WARNING("Exception thrown: " \ #type "(" #subtype ") " \ "at " __FILE__ "(" << __LINE__ << ")") \ } \ throw type(type::subtype); \ } #define THROW_EXCEPTION_MESSAGE(type, subtype, message) \ { \ std::ostringstream _box_throw_line; \ _box_throw_line << message; \ if(!HideExceptionMessageGuard::ExceptionsHidden()) \ { \ OPTIONAL_DO_BACKTRACE \ BOX_WARNING("Exception thrown: " \ #type "(" #subtype ") (" << message << \ ") at " __FILE__ "(" << __LINE__ << ")") \ } \ throw type(type::subtype, _box_throw_line.str()); \ } // extra macros for converting to network byte order #ifdef HAVE_NETINET_IN_H #include #endif // Always define a swap64 function, as it's useful. inline uint64_t box_swap64(uint64_t x) { return ((x & 0xff) << 56 | (x & 0xff00LL) << 40 | (x & 0xff0000LL) << 24 | (x & 0xff000000LL) << 8 | (x & 0xff00000000LL) >> 8 | (x & 0xff0000000000LL) >> 24 | (x & 0xff000000000000LL) >> 40 | (x & 0xff00000000000000LL) >> 56); } #ifdef WORDS_BIGENDIAN #define box_hton64(x) (x) #define box_ntoh64(x) (x) #elif defined(HAVE_BSWAP64) #ifdef HAVE_SYS_ENDIAN_H #include #endif #ifdef HAVE_ASM_BYTEORDER_H #include #endif #define box_hton64(x) BSWAP64(x) #define box_ntoh64(x) BSWAP64(x) #else #define box_hton64(x) box_swap64(x) #define box_ntoh64(x) box_swap64(x) #endif // overloaded auto-conversion functions inline uint64_t hton(uint64_t in) { return box_hton64(in); } inline uint32_t hton(uint32_t in) { return htonl(in); } inline uint16_t hton(uint16_t in) { return htons(in); } inline uint8_t hton(uint8_t in) { return in; } inline int64_t hton(int64_t in) { return box_hton64(in); } inline int32_t hton(int32_t in) { return htonl(in); } inline int16_t hton(int16_t in) { return htons(in); } inline int8_t hton(int8_t in) { return in; } inline uint64_t ntoh(uint64_t in) { return box_ntoh64(in); } inline uint32_t ntoh(uint32_t in) { return ntohl(in); } inline uint16_t ntoh(uint16_t in) { return ntohs(in); } inline uint8_t ntoh(uint8_t in) { return in; } inline int64_t ntoh(int64_t in) { return box_ntoh64(in); } inline int32_t ntoh(int32_t in) { return ntohl(in); } inline int16_t ntoh(int16_t in) { return ntohs(in); } inline int8_t ntoh(int8_t in) { return in; } #endif // BOX__H boxbackup/lib/common/BoxException.cpp0000664000175000017500000000061510347400657020473 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxException.cpp // Purpose: Exception // Created: 2003/07/10 // // -------------------------------------------------------------------------- #include "Box.h" #include "BoxException.h" #include "MemLeakFindOn.h" BoxException::BoxException() { } BoxException::~BoxException() throw () { } boxbackup/lib/common/Guards.h0000664000175000017500000000374410775523641016770 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Guards.h // Purpose: Classes which ensure things are closed/deleted properly when // going out of scope. Easy exception proof code, etc // Created: 2003/07/12 // // -------------------------------------------------------------------------- #ifndef GUARDS__H #define GUARDS__H #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include "CommonException.h" #include "Logging.h" #include "MemLeakFindOn.h" template class FileHandleGuard { public: FileHandleGuard(const std::string& rFilename) : mOSFileHandle(::open(rFilename.c_str(), flags, mode)) { if(mOSFileHandle < 0) { BOX_LOG_SYS_ERROR("FileHandleGuard: failed to open " "file '" << rFilename << "'"); THROW_EXCEPTION(CommonException, OSFileOpenError) } } ~FileHandleGuard() { if(mOSFileHandle >= 0) { Close(); } } void Close() { if(mOSFileHandle < 0) { THROW_EXCEPTION(CommonException, FileAlreadyClosed) } if(::close(mOSFileHandle) != 0) { THROW_EXCEPTION(CommonException, OSFileCloseError) } mOSFileHandle = -1; } operator int() const { return mOSFileHandle; } private: int mOSFileHandle; }; template class MemoryBlockGuard { public: MemoryBlockGuard(int BlockSize) : mpBlock(::malloc(BlockSize)) { if(mpBlock == 0) { throw std::bad_alloc(); } } ~MemoryBlockGuard() { free(mpBlock); } operator type() const { return (type)mpBlock; } type GetPtr() const { return (type)mpBlock; } void Resize(int NewSize) { void *ptrn = ::realloc(mpBlock, NewSize); if(ptrn == 0) { throw std::bad_alloc(); } mpBlock = ptrn; } private: void *mpBlock; }; #include "MemLeakFindOff.h" #endif // GUARDS__H boxbackup/lib/common/PathUtils.h0000664000175000017500000000136310553516061017443 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: PathUtils.h // Purpose: Platform-independent path manipulation // Created: 2007/01/17 // // -------------------------------------------------------------------------- #ifndef PATHUTILS_H #define PATHUTILS_H #include // -------------------------------------------------------------------------- // // Function // Name: MakeFullPath(const std::string& rDir, const std::string& rFile) // Purpose: Combine directory and file name // Created: 2006/08/10 // // -------------------------------------------------------------------------- std::string MakeFullPath(const std::string& rDir, const std::string& rEntry); #endif // !PATHUTILS_H boxbackup/lib/common/EndStructPackForWire.h0000664000175000017500000000070610347400657021542 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: EndStructPackForWire.h // Purpose: End structure packing for wire // Created: 25/11/03 // // -------------------------------------------------------------------------- // No header guard -- this is intentional #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #pragma pack() #else logical error -- check BoxPlatform.h and including file #endif boxbackup/lib/common/IOStreamGetLine.h0000664000175000017500000000365511126433077020471 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: IOStreamGetLine.h // Purpose: Line based file descriptor reading // Created: 2003/07/24 // // -------------------------------------------------------------------------- #ifndef IOSTREAMGETLINE__H #define IOSTREAMGETLINE__H #include #include "IOStream.h" #ifdef BOX_RELEASE_BUILD #define IOSTREAMGETLINE_BUFFER_SIZE 1024 #else #define IOSTREAMGETLINE_BUFFER_SIZE 4 #endif // Just a very large upper bound for line size to avoid // people sending lots of data over sockets and causing memory problems. #define IOSTREAMGETLINE_MAX_LINE_SIZE (1024*256) // -------------------------------------------------------------------------- // // Class // Name: IOStreamGetLine // Purpose: Line based stream reading // Created: 2003/07/24 // // -------------------------------------------------------------------------- class IOStreamGetLine { public: IOStreamGetLine(IOStream &Stream); ~IOStreamGetLine(); private: IOStreamGetLine(const IOStreamGetLine &rToCopy); public: bool GetLine(std::string &rOutput, bool Preprocess = false, int Timeout = IOStream::TimeOutInfinite); bool IsEOF() {return mEOF;} int GetLineNumber() {return mLineNumber;} // Call to detach, setting file pointer correctly to last bit read. // Only works for lseek-able file descriptors. void DetachFile(); // For doing interesting stuff with the remaining data... // Be careful with this! const void *GetBufferedData() const {return mBuffer + mBufferBegin;} int GetSizeOfBufferedData() const {return mBytesInBuffer - mBufferBegin;} void IgnoreBufferedData(int BytesToIgnore); IOStream &GetUnderlyingStream() {return mrStream;} private: char mBuffer[IOSTREAMGETLINE_BUFFER_SIZE]; IOStream &mrStream; int mLineNumber; int mBufferBegin; int mBytesInBuffer; bool mPendingEOF; bool mEOF; std::string mPendingString; }; #endif // IOSTREAMGETLINE__H boxbackup/lib/common/IOStream.cpp0000664000175000017500000001450311130254341017534 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: IOStream.cpp // Purpose: I/O Stream abstraction // Created: 2003/07/31 // // -------------------------------------------------------------------------- #include "Box.h" #include "IOStream.h" #include "CommonException.h" #include "Guards.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: IOStream::IOStream() // Purpose: Constructor // Created: 2003/07/31 // // -------------------------------------------------------------------------- IOStream::IOStream() { } // -------------------------------------------------------------------------- // // Function // Name: IOStream::~IOStream() // Purpose: Destructor // Created: 2003/07/31 // // -------------------------------------------------------------------------- IOStream::~IOStream() { } // -------------------------------------------------------------------------- // // Function // Name: IOStream::Close() // Purpose: Close the stream // Created: 2003/07/31 // // -------------------------------------------------------------------------- void IOStream::Close() { // Do nothing by default -- let the destructor clear everything up. } // -------------------------------------------------------------------------- // // Function // Name: IOStream::Seek(int, int) // Purpose: Seek in stream (if supported) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void IOStream::Seek(IOStream::pos_type Offset, int SeekType) { THROW_EXCEPTION(CommonException, NotSupported) } // -------------------------------------------------------------------------- // // Function // Name: IOStream::GetPosition() // Purpose: Returns current position in stream (if supported) // Created: 2003/08/21 // // -------------------------------------------------------------------------- IOStream::pos_type IOStream::GetPosition() const { THROW_EXCEPTION(CommonException, NotSupported) } // -------------------------------------------------------------------------- // // Function // Name: IOStream::ConvertSeekTypeToOSWhence(int) // Purpose: Return an whence arg for lseek given a IOStream seek type // Created: 2003/08/21 // // -------------------------------------------------------------------------- int IOStream::ConvertSeekTypeToOSWhence(int SeekType) { // Should be nicely optimised out as values are choosen in header file to match OS values. int ostype = SEEK_SET; switch(SeekType) { #ifdef WIN32 case SeekType_Absolute: ostype = FILE_BEGIN; break; case SeekType_Relative: ostype = FILE_CURRENT; break; case SeekType_End: ostype = FILE_END; break; #else // ! WIN32 case SeekType_Absolute: ostype = SEEK_SET; break; case SeekType_Relative: ostype = SEEK_CUR; break; case SeekType_End: ostype = SEEK_END; break; #endif // WIN32 default: THROW_EXCEPTION(CommonException, IOStreamBadSeekType) } return ostype; } // -------------------------------------------------------------------------- // // Function // Name: IOStream::ReadFullBuffer(void *, int, int) // Purpose: Reads bytes into buffer, returning whether or not it managed to // get all the bytes required. Exception and abort use of stream // if this returns false. // Created: 2003/08/26 // // -------------------------------------------------------------------------- bool IOStream::ReadFullBuffer(void *pBuffer, int NBytes, int *pNBytesRead, int Timeout) { int bytesToGo = NBytes; char *buffer = (char*)pBuffer; if(pNBytesRead) (*pNBytesRead) = 0; while(bytesToGo > 0) { int bytesRead = Read(buffer, bytesToGo, Timeout); if(bytesRead == 0) { // Timeout or something return false; } // Increment things bytesToGo -= bytesRead; buffer += bytesRead; if(pNBytesRead) (*pNBytesRead) += bytesRead; } // Got everything return true; } // -------------------------------------------------------------------------- // // Function // Name: IOStream::WriteAllBuffered() // Purpose: Ensures that any data which has been buffered is written to the stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- void IOStream::WriteAllBuffered() { } // -------------------------------------------------------------------------- // // Function // Name: IOStream::BytesLeftToRead() // Purpose: Numbers of bytes left to read in the stream, or // IOStream::SizeOfStreamUnknown if this isn't known. // Created: 2003/08/26 // // -------------------------------------------------------------------------- IOStream::pos_type IOStream::BytesLeftToRead() { return IOStream::SizeOfStreamUnknown; } // -------------------------------------------------------------------------- // // Function // Name: IOStream::CopyStreamTo(IOStream &, int Timeout) // Purpose: Copies the entire stream to another stream (reading from this, // writing to rCopyTo). Returns whether the copy completed (ie // StreamDataLeft() returns false) // Created: 2003/08/26 // // -------------------------------------------------------------------------- bool IOStream::CopyStreamTo(IOStream &rCopyTo, int Timeout, int BufferSize) { // Make sure there's something to do before allocating that buffer if(!StreamDataLeft()) { return true; // complete, even though nothing happened } // Buffer MemoryBlockGuard buffer(BufferSize); // Get copying! while(StreamDataLeft()) { // Read some data int bytes = Read(buffer, BufferSize, Timeout); if(bytes == 0 && StreamDataLeft()) { return false; // incomplete, timed out } // Write some data if(bytes != 0) { rCopyTo.Write(buffer, bytes); } } return true; // completed } // -------------------------------------------------------------------------- // // Function // Name: IOStream::Flush(int Timeout) // Purpose: Read and discard all remaining data in stream. // Useful for protocol streams which must be flushed // to avoid breaking the protocol. // Created: 2008/08/20 // // -------------------------------------------------------------------------- void IOStream::Flush(int Timeout) { char buffer[4096]; while(StreamDataLeft()) { Read(buffer, sizeof(buffer), Timeout); } } void IOStream::Write(const char *pBuffer) { Write(pBuffer, strlen(pBuffer)); } boxbackup/lib/common/BoxConfig-MSVC.h0000664000175000017500000002727210747473052020170 0ustar siretartsiretart/* lib/common/BoxConfig.h. Generated by configure. */ /* lib/common/BoxConfig.h.in. Generated from configure.ac by autoheader. */ /* Hacked by hand to work for MSVC by Chris Wilson */ /* Define to major version for BDB_VERSION */ /* #undef BDB_VERSION_MAJOR */ /* Define to minor version for BDB_VERSION */ /* #undef BDB_VERSION_MINOR */ /* Define to point version for BDB_VERSION */ /* #undef BDB_VERSION_POINT */ /* Name of the 64 bit endian swapping function */ /* #undef BSWAP64 */ /* Define to 1 if the `closedir' function returns void instead of `int'. */ #define CLOSEDIR_VOID 1 /* Define to 1 if non-aligned int16 access will fail */ /* #undef HAVE_ALIGNED_ONLY_INT16 */ /* Define to 1 if non-aligned int32 access will fail */ /* #undef HAVE_ALIGNED_ONLY_INT32 */ /* Define to 1 if non-aligned int64 access will fail */ /* #undef HAVE_ALIGNED_ONLY_INT64 */ /* Define to 1 if you have the header file. */ /* #undef HAVE_ASM_BYTEORDER_H */ /* Define to 1 if BSWAP64 is defined to the name of a valid 64 bit endian swapping function */ /* #undef HAVE_BSWAP64 */ /* Define to 1 if you have the header file. */ /* #undef HAVE_DB_H */ /* Define to 1 if you have the declaration of `F_SETLK', and to 0 if you don't. */ #define HAVE_DECL_F_SETLK 0 /* Define to 1 if you have the declaration of `INFTIM', and to 0 if you don't. */ #define HAVE_DECL_INFTIM 0 /* Define to 1 if you have the declaration of `O_EXLOCK', and to 0 if you don't. */ #define HAVE_DECL_O_EXLOCK 0 /* Define to 1 if you have the declaration of `SO_PEERCRED', and to 0 if you don't. */ #define HAVE_DECL_SO_PEERCRED 0 /* Define to 1 if you have the declaration of `XATTR_NOFOLLOW', and to 0 if you don't. */ #define HAVE_DECL_XATTR_NOFOLLOW 0 /* Define to 1 if you have the declaration of `O_BINARY', and to 0 if you don't. */ #define HAVE_DECL_O_BINARY 1 /* Define to 1 if #define of pragmas works */ /* #undef HAVE_DEFINE_PRAGMA */ /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_DIRENT_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_EDITLINE_READLINE_H */ /* define if the compiler supports exceptions */ #define HAVE_EXCEPTIONS /* Define to 1 if you have the header file. */ /* #undef HAVE_EXECINFO_H */ /* Define to 1 if you have the `flock' function. */ /* #undef HAVE_FLOCK */ /* Define to 1 if you have the `getmntent' function. */ /* #undef HAVE_GETMNTENT */ /* Define to 1 if you have the `getpeereid' function. */ /* #undef HAVE_GETPEEREID */ /* Define to 1 if you have the `getpid' function. */ // #define HAVE_GETPID 1 /* Define to 1 if you have the `getxattr' function. */ /* #undef HAVE_GETXATTR */ /* Define to 1 if you have the header file. */ /* #undef HAVE_HISTORY_H */ /* Define to 1 if you have the header file. */ // #define HAVE_INTTYPES_H 1 /* Define to 1 if you have the `kqueue' function. */ /* #undef HAVE_KQUEUE */ /* Define to 1 if you have the `lchown' function. */ /* #undef HAVE_LCHOWN */ /* Define to 1 if you have the `lgetxattr' function. */ /* #undef HAVE_LGETXATTR */ /* Define to 1 if you have the `crypto' library (-lcrypto). */ #define HAVE_LIBCRYPTO 1 /* Define if you have a readline compatible library */ /* #undef HAVE_LIBREADLINE */ /* Define to 1 if you have the `ssl' library (-lssl). */ #define HAVE_LIBSSL 1 /* Define to 1 if you have the `z' library (-lz). */ #define HAVE_LIBZ 1 /* Define to 1 if you have the `listxattr' function. */ /* #undef HAVE_LISTXATTR */ /* Define to 1 if you have the `llistxattr' function. */ /* #undef HAVE_LLISTXATTR */ /* Define to 1 if syscall lseek requires a dummy middle parameter */ /* #undef HAVE_LSEEK_DUMMY_PARAM */ /* Define to 1 if you have the `lsetxattr' function. */ /* #undef HAVE_LSETXATTR */ /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_MNTENT_H */ /* Define to 1 if this platform supports mounts */ /* #undef HAVE_MOUNTS */ /* define if the compiler implements namespaces */ #define HAVE_NAMESPACES /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_NDIR_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_NETINET_IN_H */ /* Define to 1 if SSL is pre-0.9.7 */ /* #undef HAVE_OLD_SSL */ /* Define to 1 if you have the header file. */ #define HAVE_OPENSSL_SSL_H 1 /* Define to 1 if you have the header file. */ #define HAVE_PROCESS_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_PWD_H */ /* Define to 1 (and set RANDOM_DEVICE) if a random device is available */ /* #undef HAVE_RANDOM_DEVICE */ /* Define to 1 if you have the header file. */ /* #undef HAVE_READLINE_H */ /* Define if your readline library has add_history */ /* #undef HAVE_READLINE_HISTORY */ /* Define to 1 if you have the header file. */ /* #undef HAVE_READLINE_HISTORY_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_READLINE_READLINE_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_REGEX_H */ #define HAVE_PCREPOSIX_H 1 #define HAVE_REGEX_SUPPORT 1 /* Define to 1 if you have the `setproctitle' function. */ /* #undef HAVE_SETPROCTITLE */ /* Define to 1 if you have the `setxattr' function. */ /* #undef HAVE_SETXATTR */ /* Define to 1 if you have the header file. */ #define HAVE_SIGNAL_H 1 /* Define to 1 if SSL is available */ #define HAVE_SSL 1 /* Define to 1 if you have the `statfs' function. */ /* #undef HAVE_STATFS */ /* Define to 1 if `stat' has the bug that it succeeds when given the zero-length file name argument. */ /* #undef HAVE_STAT_EMPTY_STRING_BUG */ /* Define to 1 if stdbool.h conforms to C99. */ #define HAVE_STDBOOL_H 1 /* Define to 1 if you have the header file. */ // #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if `d_type' is member of `struct dirent'. */ /* #undef HAVE_STRUCT_DIRENT_D_TYPE */ /* Define to 1 if `mnt_dir' is member of `struct mntent'. */ /* #undef HAVE_STRUCT_MNTENT_MNT_DIR */ /* Define to 1 if `mnt_mountp' is member of `struct mnttab'. */ /* #undef HAVE_STRUCT_MNTTAB_MNT_MOUNTP */ /* Define to 1 if `sin_len' is member of `struct sockaddr_in'. */ /* #undef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ /* Define to 1 if `f_mntonname' is member of `struct statfs'. */ /* #undef HAVE_STRUCT_STATFS_F_MNTONNAME */ /* Define to 1 if `st_flags' is member of `struct stat'. */ /* #undef HAVE_STRUCT_STAT_ST_FLAGS */ /* Define to 1 if `st_mtimespec' is member of `struct stat'. */ /* #undef HAVE_STRUCT_STAT_ST_MTIMESPEC */ /* Define to 1 if you have the `syscall' function. */ /* #undef HAVE_SYSCALL */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYSLOG_H */ /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_SYS_DIR_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_ENDIAN_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_MNTTAB_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_MOUNT_H */ /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_SYS_NDIR_H */ /* Define to 1 if you have the header file. */ // #define HAVE_SYS_PARAM_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_SOCKET_H */ /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_SYSCALL_H */ /* Define to 1 if you have the header file. */ // #define HAVE_SYS_TIME_H 1 /* Define to 1 if you have the header file. */ // #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_WAIT_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_XATTR_H */ /* Define to 1 if you have the header file. */ #define HAVE_TIME_H 1 /* Define to 1 if the system has the type `uint16_t'. */ #define HAVE_UINT16_T 1 /* Define to 1 if the system has the type `uint32_t'. */ #define HAVE_UINT32_T 1 /* Define to 1 if the system has the type `uint64_t'. */ #define HAVE_UINT64_T 1 /* Define to 1 if the system has the type `uint8_t'. */ #define HAVE_UINT8_T 1 /* Define to 1 if you have the header file. */ // #define HAVE_UNISTD_H 1 /* Define to 1 if the system has the type `u_int16_t'. */ /* #undef HAVE_U_INT16_T */ /* Define to 1 if the system has the type `u_int32_t'. */ /* #undef HAVE_U_INT32_T */ /* Define to 1 if the system has the type `u_int64_t'. */ /* #undef HAVE_U_INT64_T */ /* Define to 1 if the system has the type `u_int8_t'. */ /* #undef HAVE_U_INT8_T */ /* Define to 1 if struct dirent.d_type is valid */ /* #undef HAVE_VALID_DIRENT_D_TYPE */ /* Define to 1 if the system has the type `_Bool'. */ /* #undef HAVE__BOOL */ /* Define to 1 if you have the `__syscall' function. */ /* #undef HAVE___SYSCALL */ /* Define to 1 if __syscall is available but needs a definition */ /* #undef HAVE___SYSCALL_NEED_DEFN */ /* max value of long long calculated by configure */ /* #undef LLONG_MAX */ /* min value of long long calculated by configure */ /* #undef LLONG_MIN */ /* Define to 1 if `lstat' dereferences a symlink specified with a trailing slash. */ /* #undef LSTAT_FOLLOWS_SLASHED_SYMLINK */ /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "box@fluffy.co.uk" /* Define to the full name of this package. */ #define PACKAGE_NAME "Box Backup" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "Box Backup 0.11" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "box-backup" /* Define to the version of this package. */ #define PACKAGE_VERSION "0.09" /* Define to the filename of the random device (and set HAVE_RANDOM_DEVICE) */ /* #undef RANDOM_DEVICE */ /* Define as the return type of signal handlers (`int' or `void'). */ #define RETSIGTYPE void /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* TMP directory name */ #define TEMP_DIRECTORY_NAME "/tmp" /* Define to 1 if you can safely include both and . */ #define TIME_WITH_SYS_TIME 1 /* Define to 1 if your declares `struct tm'. */ /* #undef TM_IN_SYS_TIME */ /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ /* #undef WORDS_BIGENDIAN */ /* Number of bits in a file offset, on hosts where this is settable. */ /* #undef _FILE_OFFSET_BITS */ /* Define for large files, on AIX-style hosts. */ /* #undef _LARGE_FILES */ /* Define to 1 if __USE_MALLOC is required work around STL memory leaks */ /* #undef __USE_MALLOC */ /* Define to empty if `const' does not conform to ANSI C. */ /* #undef const */ /* Define to `int' if doesn't define. */ #define gid_t int /* Define to `int' if does not define. */ /* #undef mode_t */ /* Define to `long' if does not define. */ /* #undef off_t */ /* Define to `int' if does not define. */ /* #undef pid_t */ /* Define to `unsigned' if does not define. */ /* #undef size_t */ /* Define to `int' if doesn't define. */ #define uid_t int boxbackup/lib/common/CommonException.h0000664000175000017500000000066210347400657020642 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CommonException.h // Purpose: Exception // Created: 2003/07/08 // // -------------------------------------------------------------------------- #ifndef COMMONEXCEPTION__H #define COMMONEXCEPTION__H // Compatibility header with old non-autogen exception scheme #include "autogen_CommonException.h" #endif // COMMONEXCEPTION__H boxbackup/lib/common/MemLeakFindOn.h0000664000175000017500000000115310347400657020140 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MemLeakFindOn.h // Purpose: Switch memory leak finding on // Created: 13/1/04 // // -------------------------------------------------------------------------- // no header guard #ifdef BOX_MEMORY_LEAK_TESTING #define new DEBUG_NEW #ifndef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED #define malloc(X) memleakfinder_malloc(X, __FILE__, __LINE__) #define realloc memleakfinder_realloc #define free memleakfinder_free #define MEMLEAKFINDER_MALLOC_MONITORING_DEFINED #endif #define MEMLEAKFINDER_ENABLED #endif boxbackup/lib/common/BoxTimeToUnix.h0000664000175000017500000000154410347400657020251 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxTimeToUnix.h // Purpose: Convert times in 64 bit values to UNIX structures // Created: 2003/10/07 // // -------------------------------------------------------------------------- #ifndef FILEMODIFICATIONTIMETOTIMEVAL__H #define FILEMODIFICATIONTIMETOTIMEVAL__H #ifdef WIN32 #include #else #include #endif #include "BoxTime.h" inline void BoxTimeToTimeval(box_time_t Time, struct timeval &tv) { tv.tv_sec = (long)(Time / MICRO_SEC_IN_SEC_LL); tv.tv_usec = (long)(Time % MICRO_SEC_IN_SEC_LL); } inline void BoxTimeToTimespec(box_time_t Time, struct timespec &tv) { tv.tv_sec = (time_t)(Time / MICRO_SEC_IN_SEC_LL); tv.tv_nsec = ((long)(Time % MICRO_SEC_IN_SEC_LL)) * NANO_SEC_IN_USEC; } #endif // FILEMODIFICATIONTIMETOTIMEVAL__H boxbackup/lib/common/Utils.cpp0000664000175000017500000001467711245071337017176 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Utils.cpp // Purpose: Utility function // Created: 2003/07/31 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #ifdef SHOW_BACKTRACE_ON_EXCEPTION #include #include #endif #ifdef HAVE_CXXABI_H #include #endif #include "Utils.h" #include "CommonException.h" #include "Logging.h" #include "MemLeakFindOn.h" std::string GetBoxBackupVersion() { return BOX_VERSION; } // -------------------------------------------------------------------------- // // Function // Name: SplitString(const std::string &, char, std::vector &) // Purpose: Splits a string at a given character // Created: 2003/07/31 // // -------------------------------------------------------------------------- void SplitString(const std::string &String, char SplitOn, std::vector &rOutput) { // Split it up. std::string::size_type b = 0; std::string::size_type e = 0; while(e = String.find_first_of(SplitOn, b), e != String.npos) { // Get this string unsigned int len = e - b; if(len >= 1) { rOutput.push_back(String.substr(b, len)); } b = e + 1; } // Last string if(b < String.size()) { rOutput.push_back(String.substr(b)); } /*#ifndef BOX_RELEASE_BUILD BOX_TRACE("Splitting string '" << String << " on " << (char)SplitOn); for(unsigned int l = 0; l < rOutput.size(); ++l) { BOX_TRACE(l << " = '" << rOutput[l] << "'"); } #endif*/ } #ifdef SHOW_BACKTRACE_ON_EXCEPTION void DumpStackBacktrace() { void *array[10]; size_t size = backtrace (array, 10); char **strings = backtrace_symbols (array, size); BOX_TRACE("Obtained " << size << " stack frames."); for(size_t i = 0; i < size; i++) { // Demangling code copied from // cctbx_sources/boost_adaptbx/meta_ext.cpp, BSD license std::string mangled_frame = strings[i]; std::string output_frame = strings[i]; // default #ifdef HAVE_CXXABI_H int start = mangled_frame.find('('); int end = mangled_frame.find('+', start); std::string mangled_func = mangled_frame.substr(start + 1, end - start - 1); int status; #include "MemLeakFindOff.h" char* result = abi::__cxa_demangle(mangled_func.c_str(), NULL, NULL, &status); #include "MemLeakFindOn.h" if (result == NULL) { if (status == 0) { BOX_WARNING("Demangle failed but no error: " << mangled_func); } else if (status == -1) { BOX_WARNING("Demangle failed with " "memory allocation error: " << mangled_func); } else if (status == -2) { // Probably non-C++ name, don't demangle /* BOX_WARNING("Demangle failed with " "with invalid name: " << mangled_func); */ } else if (status == -3) { BOX_WARNING("Demangle failed with " "with invalid argument: " << mangled_func); } else { BOX_WARNING("Demangle failed with " "with unknown error " << status << ": " << mangled_func); } } else { output_frame = mangled_frame.substr(0, start + 1) + result + mangled_frame.substr(end); #include "MemLeakFindOff.h" std::free(result); #include "MemLeakFindOn.h" } #endif // HAVE_CXXABI_H BOX_TRACE("Stack frame " << i << ": " << output_frame); } #include "MemLeakFindOff.h" std::free (strings); #include "MemLeakFindOn.h" } #endif // -------------------------------------------------------------------------- // // Function // Name: FileExists(const std::string& rFilename) // Purpose: Does a file exist? // Created: 20/11/03 // // -------------------------------------------------------------------------- bool FileExists(const std::string& rFilename, int64_t *pFileSize, bool TreatLinksAsNotExisting) { EMU_STRUCT_STAT st; if(EMU_LSTAT(rFilename.c_str(), &st) != 0) { if(errno == ENOENT) { return false; } else { THROW_EXCEPTION(CommonException, OSFileError); } } // is it a file? if((st.st_mode & S_IFDIR) == 0) { if(TreatLinksAsNotExisting && ((st.st_mode & S_IFLNK) != 0)) { return false; } // Yes. Tell caller the size? if(pFileSize != 0) { *pFileSize = st.st_size; } return true; } else { return false; } } // -------------------------------------------------------------------------- // // Function // Name: ObjectExists(const std::string& rFilename) // Purpose: Does a object exist, and if so, is it a file or a directory? // Created: 23/11/03 // // -------------------------------------------------------------------------- int ObjectExists(const std::string& rFilename) { EMU_STRUCT_STAT st; if(EMU_STAT(rFilename.c_str(), &st) != 0) { if(errno == ENOENT) { return ObjectExists_NoObject; } else { THROW_EXCEPTION(CommonException, OSFileError); } } // is it a file or a dir? return ((st.st_mode & S_IFDIR) == 0)?ObjectExists_File:ObjectExists_Dir; } std::string HumanReadableSize(int64_t Bytes) { double readableValue = Bytes; std::string units = " B"; if (readableValue > 1024) { readableValue /= 1024; units = "kB"; } if (readableValue > 1024) { readableValue /= 1024; units = "MB"; } if (readableValue > 1024) { readableValue /= 1024; units = "GB"; } std::ostringstream result; result << std::fixed << std::setprecision(2) << readableValue << " " << units; return result.str(); } std::string FormatUsageBar(int64_t Blocks, int64_t Bytes, int64_t Max, bool MachineReadable) { std::ostringstream result; if (MachineReadable) { result << (Bytes >> 10) << " kB, " << std::setprecision(0) << ((Bytes*100)/Max) << "%"; } else { // Bar graph char bar[17]; unsigned int b = (int)((Bytes * (sizeof(bar)-1)) / Max); if(b > sizeof(bar)-1) {b = sizeof(bar)-1;} for(unsigned int l = 0; l < b; l++) { bar[l] = '*'; } for(unsigned int l = b; l < sizeof(bar) - 1; l++) { bar[l] = ' '; } bar[sizeof(bar)-1] = '\0'; result << std::fixed << std::setw(10) << Blocks << " blocks, " << std::setw(10) << HumanReadableSize(Bytes) << ", " << std::setw(3) << std::setprecision(0) << ((Bytes*100)/Max) << "% |" << bar << "|"; } return result.str(); } std::string FormatUsageLineStart(const std::string& rName, bool MachineReadable) { std::ostringstream result; if (MachineReadable) { result << rName << ": "; } else { result << std::setw(20) << std::right << rName << ": "; } return result.str(); } boxbackup/lib/common/UnixUser.h0000664000175000017500000000135410347400657017314 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: UnixUser.h // Purpose: Interface for managing the UNIX user of the current process // Created: 21/1/04 // // -------------------------------------------------------------------------- #ifndef UNIXUSER__H #define UNIXUSER__H class UnixUser { public: UnixUser(const char *Username); UnixUser(uid_t UID, gid_t GID); ~UnixUser(); private: // no copying allowed UnixUser(const UnixUser &); UnixUser &operator=(const UnixUser &); public: void ChangeProcessUser(bool Temporary = false); uid_t GetUID() {return mUID;} gid_t GetGID() {return mGID;} private: uid_t mUID; gid_t mGID; bool mRevertOnDestruction; }; #endif // UNIXUSER__H boxbackup/lib/common/EventWatchFilesystemObject.h0000664000175000017500000000241610347400657022776 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: EventWatchFilesystemObject.h // Purpose: WaitForEvent compatible object for watching directories // Created: 12/3/04 // // -------------------------------------------------------------------------- #ifndef EVENTWATCHFILESYSTEMOBJECT__H #define EVENTWATCHFILESYSTEMOBJECT__H #ifdef HAVE_KQUEUE #include #endif // -------------------------------------------------------------------------- // // Class // Name: EventWatchFilesystemObject // Purpose: WaitForEvent compatible object for watching files and directories // Created: 12/3/04 // // -------------------------------------------------------------------------- class EventWatchFilesystemObject { public: EventWatchFilesystemObject(const char *Filename); ~EventWatchFilesystemObject(); EventWatchFilesystemObject(const EventWatchFilesystemObject &rToCopy); private: // Assignment not allowed EventWatchFilesystemObject &operator=(const EventWatchFilesystemObject &); public: #ifdef HAVE_KQUEUE void FillInKEvent(struct kevent &rEvent, int Flags = 0) const; #else void FillInPoll(int &fd, short &events, int Flags = 0) const; #endif private: int mDescriptor; }; #endif // EventWatchFilesystemObject__H boxbackup/lib/common/DebugPrintf.cpp0000664000175000017500000000310411126433077020267 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: DebugPrintf.cpp // Purpose: Implementation of a printf function, to avoid a stdio.h include in Box.h // Created: 2003/09/06 // // -------------------------------------------------------------------------- #ifndef BOX_RELEASE_BUILD #include "Box.h" #include #include #ifdef WIN32 #include "emu.h" #else #include #endif #include "MemLeakFindOn.h" // Use this apparently superflous printf function to avoid having to // include stdio.h in every file in the project. int BoxDebug_printf(const char *format, ...) { va_list ap; va_start(ap, format); int r = vprintf(format, ap); va_end(ap); return r; } bool BoxDebugTraceOn = true; bool BoxDebugTraceToStdout = true; bool BoxDebugTraceToSyslog = false; int BoxDebugTrace(const char *format, ...) { char text[512]; int r = 0; if(BoxDebugTraceOn || BoxDebugTraceToSyslog) { va_list ap; va_start(ap, format); r = vsnprintf(text, sizeof(text), format, ap); va_end(ap); } // Send to stdout if trace is on and std out is enabled if(BoxDebugTraceOn && BoxDebugTraceToStdout) { printf("%s", text); } // But tracing to syslog is independent of tracing being on or not if(BoxDebugTraceToSyslog) { #ifdef WIN32 // Remove trailing '\n', if it's there if(r > 0 && text[r-1] == '\n') { text[r-1] = '\0'; #else if(r > 0 && text[r] == '\n') { text[r] = '\0'; #endif --r; } // Log it ::syslog(LOG_INFO, "TRACE: %s", text); } return r; } #endif // BOX_RELEASE_BUILD boxbackup/lib/common/MemBlockStream.cpp0000664000175000017500000001451510347400657020735 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MemBlockStream.cpp // Purpose: Stream out data from any memory block // Created: 2003/09/05 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "MemBlockStream.h" #include "CommonException.h" #include "StreamableMemBlock.h" #include "CollectInBufferStream.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::MemBlockStream() // Purpose: Constructor (doesn't copy block, careful with lifetimes) // Created: 2003/09/05 // // -------------------------------------------------------------------------- MemBlockStream::MemBlockStream(const void *pBuffer, int Size) : mpBuffer((char*)pBuffer), mBytesInBuffer(Size), mReadPosition(0) { ASSERT(pBuffer != 0); ASSERT(Size >= 0); } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::MemBlockStream(const StreamableMemBlock &) // Purpose: Constructor (doesn't copy block, careful with lifetimes) // Created: 2003/09/05 // // -------------------------------------------------------------------------- MemBlockStream::MemBlockStream(const StreamableMemBlock &rBlock) : mpBuffer((char*)rBlock.GetBuffer()), mBytesInBuffer(rBlock.GetSize()), mReadPosition(0) { ASSERT(mpBuffer != 0); ASSERT(mBytesInBuffer >= 0); } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::MemBlockStream(const StreamableMemBlock &) // Purpose: Constructor (doesn't copy block, careful with lifetimes) // Created: 2003/09/05 // // -------------------------------------------------------------------------- MemBlockStream::MemBlockStream(const CollectInBufferStream &rBuffer) : mpBuffer((char*)rBuffer.GetBuffer()), mBytesInBuffer(rBuffer.GetSize()), mReadPosition(0) { ASSERT(mpBuffer != 0); ASSERT(mBytesInBuffer >= 0); } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::MemBlockStream(const MemBlockStream &) // Purpose: Copy constructor // Created: 2003/09/05 // // -------------------------------------------------------------------------- MemBlockStream::MemBlockStream(const MemBlockStream &rToCopy) : mpBuffer(rToCopy.mpBuffer), mBytesInBuffer(rToCopy.mBytesInBuffer), mReadPosition(0) { ASSERT(mpBuffer != 0); ASSERT(mBytesInBuffer >= 0); } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::~MemBlockStream() // Purpose: Destructor // Created: 2003/09/05 // // -------------------------------------------------------------------------- MemBlockStream::~MemBlockStream() { } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::Read(void *, int, int) // Purpose: As interface. But only works in read phase // Created: 2003/09/05 // // -------------------------------------------------------------------------- int MemBlockStream::Read(void *pBuffer, int NBytes, int Timeout) { // Adjust to number of bytes left if(NBytes > (mBytesInBuffer - mReadPosition)) { NBytes = (mBytesInBuffer - mReadPosition); } ASSERT(NBytes >= 0); if(NBytes <= 0) return 0; // careful now // Copy in the requested number of bytes and adjust the read pointer ::memcpy(pBuffer, mpBuffer + mReadPosition, NBytes); mReadPosition += NBytes; return NBytes; } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::BytesLeftToRead() // Purpose: As interface. But only works in read phase // Created: 2003/09/05 // // -------------------------------------------------------------------------- IOStream::pos_type MemBlockStream::BytesLeftToRead() { return (mBytesInBuffer - mReadPosition); } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::Write(void *, int) // Purpose: As interface. But only works in write phase // Created: 2003/09/05 // // -------------------------------------------------------------------------- void MemBlockStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(CommonException, MemBlockStreamNotSupported) } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::GetPosition() // Purpose: In write phase, returns the number of bytes written, in read // phase, the number of bytes to go // Created: 2003/09/05 // // -------------------------------------------------------------------------- IOStream::pos_type MemBlockStream::GetPosition() const { return mReadPosition; } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::Seek(pos_type, int) // Purpose: As interface. // Created: 2003/09/05 // // -------------------------------------------------------------------------- void MemBlockStream::Seek(pos_type Offset, int SeekType) { int newPos = 0; switch(SeekType) { case IOStream::SeekType_Absolute: newPos = Offset; break; case IOStream::SeekType_Relative: newPos = mReadPosition + Offset; break; case IOStream::SeekType_End: newPos = mBytesInBuffer + Offset; break; default: THROW_EXCEPTION(CommonException, IOStreamBadSeekType) break; } // Make sure it doesn't go over if(newPos > mBytesInBuffer) { newPos = mBytesInBuffer; } // or under if(newPos < 0) { newPos = 0; } // Set the new read position mReadPosition = newPos; } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::StreamDataLeft() // Purpose: As interface // Created: 2003/09/05 // // -------------------------------------------------------------------------- bool MemBlockStream::StreamDataLeft() { return mReadPosition < mBytesInBuffer; } // -------------------------------------------------------------------------- // // Function // Name: MemBlockStream::StreamClosed() // Purpose: As interface // Created: 2003/09/05 // // -------------------------------------------------------------------------- bool MemBlockStream::StreamClosed() { return true; } boxbackup/lib/common/Conversion.h0000664000175000017500000000476710347400657017672 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Conversion.h // Purpose: Convert between various types // Created: 9/4/04 // // -------------------------------------------------------------------------- #ifndef CONVERSION__H #define CONVERSION__H #include namespace BoxConvert { // -------------------------------------------------------------------------- // // Function // Name: BoxConvert::Convert(to_type &, from_type) // Purpose: Convert from types to types // Created: 9/4/04 // // -------------------------------------------------------------------------- template inline to_type Convert(from_type From) { // Default conversion, simply use C++ conversion return From; } // Specialise for string -> integer int32_t _ConvertStringToInt(const char *pString, int Size); template<> inline int32_t Convert(const std::string &rFrom) { return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 32); } template<> inline int16_t Convert(const std::string &rFrom) { return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 16); } template<> inline int8_t Convert(const std::string &rFrom) { return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 8); } template<> inline int32_t Convert(const char *pFrom) { return BoxConvert::_ConvertStringToInt(pFrom, 32); } template<> inline int16_t Convert(const char *pFrom) { return BoxConvert::_ConvertStringToInt(pFrom, 16); } template<> inline int8_t Convert(const char *pFrom) { return BoxConvert::_ConvertStringToInt(pFrom, 8); } // Specialise for integer -> string void _ConvertIntToString(std::string &rTo, int32_t From); template<> inline std::string Convert(int32_t From) { std::string r; BoxConvert::_ConvertIntToString(r, From); return r; } template<> inline std::string Convert(int16_t From) { std::string r; BoxConvert::_ConvertIntToString(r, From); return r; } template<> inline std::string Convert(int8_t From) { std::string r; BoxConvert::_ConvertIntToString(r, From); return r; } // Specialise for bool -> string template<> inline std::string Convert(bool From) { return std::string(From?"true":"false"); } }; #endif // CONVERSION__H boxbackup/lib/common/ZeroStream.cpp0000664000175000017500000001004110614700110020131 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ZeroStream.cpp // Purpose: An IOStream which returns all zeroes up to a certain size // Created: 2007/04/28 // // -------------------------------------------------------------------------- #include "Box.h" #include "ZeroStream.h" #include "CommonException.h" #include #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::ZeroStream(IOStream::pos_type) // Purpose: Constructor // Created: 2007/04/28 // // -------------------------------------------------------------------------- ZeroStream::ZeroStream(IOStream::pos_type size) : mSize(size), mPosition(0) { } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::Read(void *, int) // Purpose: Reads bytes from the file // Created: 2007/01/16 // // -------------------------------------------------------------------------- int ZeroStream::Read(void *pBuffer, int NBytes, int Timeout) { ASSERT(NBytes > 0); int bytesToRead = NBytes; if (bytesToRead > mSize - mPosition) { bytesToRead = mSize - mPosition; } memset(pBuffer, 0, bytesToRead); mPosition += bytesToRead; return bytesToRead; } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::BytesLeftToRead() // Purpose: Returns number of bytes to read (may not be most efficient function ever) // Created: 2007/01/16 // // -------------------------------------------------------------------------- IOStream::pos_type ZeroStream::BytesLeftToRead() { return mSize - mPosition; } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::Write(void *, int) // Purpose: Writes bytes to the underlying stream (not supported) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void ZeroStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::GetPosition() // Purpose: Get position in stream // Created: 2003/08/21 // // -------------------------------------------------------------------------- IOStream::pos_type ZeroStream::GetPosition() const { return mPosition; } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::Seek(pos_type, int) // Purpose: Seeks within file, as lseek, invalidate buffer // Created: 2003/07/31 // // -------------------------------------------------------------------------- void ZeroStream::Seek(IOStream::pos_type Offset, int SeekType) { switch (SeekType) { case SeekType_Absolute: { mPosition = Offset; } break; case SeekType_Relative: { mPosition += Offset; } break; case SeekType_End: { mPosition = mSize - Offset; } } } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::Close() // Purpose: Closes the underlying stream (not needed) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void ZeroStream::Close() { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::StreamDataLeft() // Purpose: Any data left to write? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool ZeroStream::StreamDataLeft() { return false; } // -------------------------------------------------------------------------- // // Function // Name: ZeroStream::StreamClosed() // Purpose: Is the stream closed? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool ZeroStream::StreamClosed() { return false; } boxbackup/lib/common/InvisibleTempFileStream.h0000664000175000017500000000152510514015473022252 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: InvisibleTempFileStream.h // Purpose: FileStream interface to temporary files that // delete themselves // Created: 2006/10/13 // // -------------------------------------------------------------------------- #ifndef INVISIBLETEMPFILESTREAM__H #define INVISIBLETEMPFILESTREAM__H #include "FileStream.h" class InvisibleTempFileStream : public FileStream { public: InvisibleTempFileStream(const char *Filename, #ifdef WIN32 int flags = (O_RDONLY | O_BINARY), #else int flags = O_RDONLY, #endif int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); private: InvisibleTempFileStream(const InvisibleTempFileStream &rToCopy) : FileStream(INVALID_FILE) { /* do not call */ } }; #endif // INVISIBLETEMPFILESTREAM__H boxbackup/lib/common/WaitForEvent.cpp0000664000175000017500000001012410374104755020435 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: WaitForEvent.cpp // Purpose: Generic waiting for events, using an efficient method (platform dependent) // Created: 9/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include "WaitForEvent.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: WaitForEvent::WaitForEvent() // Purpose: Constructor // Created: 9/3/04 // // -------------------------------------------------------------------------- #ifdef HAVE_KQUEUE WaitForEvent::WaitForEvent(int Timeout) : mKQueue(::kqueue()), mpTimeout(0) { if(mKQueue == -1) { THROW_EXCEPTION(CommonException, CouldNotCreateKQueue) } // Set the choosen timeout SetTimeout(Timeout); } #else WaitForEvent::WaitForEvent(int Timeout) : mTimeout(Timeout), mpPollInfo(0) { } #endif // -------------------------------------------------------------------------- // // Function // Name: WaitForEvent::~WaitForEvent() // Purpose: Destructor // Created: 9/3/04 // // -------------------------------------------------------------------------- WaitForEvent::~WaitForEvent() { #ifdef HAVE_KQUEUE ::close(mKQueue); mKQueue = -1; #else if(mpPollInfo != 0) { ::free(mpPollInfo); mpPollInfo = 0; } #endif } // -------------------------------------------------------------------------- // // Function // Name: WaitForEvent::SetTimeout // Purpose: Sets the timeout for future wait calls // Created: 9/3/04 // // -------------------------------------------------------------------------- void WaitForEvent::SetTimeout(int Timeout) { #ifdef HAVE_KQUEUE // Generate timeout if(Timeout != TimeoutInfinite) { mTimeout.tv_sec = Timeout / 1000; mTimeout.tv_nsec = (Timeout % 1000) * 1000000; } // Infinite or not? mpTimeout = (Timeout != TimeoutInfinite)?(&mTimeout):(NULL); #else mTimeout = Timeout; #endif } // -------------------------------------------------------------------------- // // Function // Name: WaitForEvent::Wait(int) // Purpose: Wait for an event to take place. Returns a pointer to the object // which has been signalled, or returns 0 for the timeout condition. // Timeout specified in milliseconds. // Created: 9/3/04 // // -------------------------------------------------------------------------- void *WaitForEvent::Wait() { #ifdef HAVE_KQUEUE // Event return structure struct kevent e; ::memset(&e, 0, sizeof(e)); switch(::kevent(mKQueue, NULL, 0, &e, 1, mpTimeout)) { case 0: // Timeout return 0; break; case 1: // Event happened! return e.udata; break; default: // Interrupted system calls aren't an error, just equivalent to a timeout if(errno != EINTR) { THROW_EXCEPTION(CommonException, KEventErrorWait) } return 0; break; } #else // Use poll() instead. // Need to build the structures? if(mpPollInfo == 0) { // Yes... mpPollInfo = (struct pollfd *)::malloc((sizeof(struct pollfd) * mItems.size()) + 4); if(mpPollInfo == 0) { throw std::bad_alloc(); } // Build... for(unsigned int l = 0; l < mItems.size(); ++l) { mpPollInfo[l].fd = mItems[l].fd; mpPollInfo[l].events = mItems[l].events; mpPollInfo[l].revents = 0; } } // Make sure everything is reset (don't really have to do this, but don't trust the OS) for(unsigned int l = 0; l < mItems.size(); ++l) { mpPollInfo[l].revents = 0; } // Poll! switch(::poll(mpPollInfo, mItems.size(), mTimeout)) { case -1: // Interrupted system calls aren't an error, just equivalent to a timeout if(errno != EINTR) { THROW_EXCEPTION(CommonException, KEventErrorWait) } return 0; break; case 0: // timed out return 0; break; default: // got some thing... // control flows on... break; } // Find the item which was ready for(unsigned int s = 0; s < mItems.size(); ++s) { if(mpPollInfo[s].revents & POLLIN) { return mItems[s].item; break; } } #endif return 0; } boxbackup/lib/common/TemporaryDirectory.h0000664000175000017500000000222110347400657021373 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: TemporaryDirectory.h // Purpose: Location of temporary directory // Created: 2003/10/13 // // -------------------------------------------------------------------------- #ifndef TEMPORARYDIRECTORY__H #define TEMPORARYDIRECTORY__H #include #ifdef WIN32 #include #endif // Prefix name with Box to avoid clashing with OS API names std::string BoxGetTemporaryDirectoryName() { #ifdef WIN32 // http://msdn.microsoft.com/library/default.asp? // url=/library/en-us/fileio/fs/creating_and_using_a_temporary_file.asp DWORD dwRetVal; char lpPathBuffer[1024]; DWORD dwBufSize = sizeof(lpPathBuffer); // Get the temp path. dwRetVal = GetTempPath(dwBufSize, // length of the buffer lpPathBuffer); // buffer for path if (dwRetVal > dwBufSize) { THROW_EXCEPTION(CommonException, TempDirPathTooLong) } return std::string(lpPathBuffer); #elif defined TEMP_DIRECTORY_NAME return std::string(TEMP_DIRECTORY_NAME); #else #error non-static temporary directory names not supported yet #endif } #endif // TEMPORARYDIRECTORY__H boxbackup/lib/common/MemLeakFinder.h0000664000175000017500000000314611165365160020174 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MemLeakFinder.h // Purpose: Memory leak finder // Created: 12/1/04 // // -------------------------------------------------------------------------- #ifndef MEMLEAKFINDER__H #define MEMLEAKFINDER__H #ifdef MEMLEAKFINDER_FULL_MALLOC_MONITORING // include stdlib now, to avoid problems with having the macros defined already #include #endif // global enable flag extern bool memleakfinder_global_enable; class MemLeakSuppressionGuard { public: MemLeakSuppressionGuard(); ~MemLeakSuppressionGuard(); }; extern "C" { void *memleakfinder_malloc(size_t size, const char *file, int line); void *memleakfinder_realloc(void *ptr, size_t size); void memleakfinder_free(void *ptr); } void memleakfinder_init(); int memleakfinder_numleaks(); void memleakfinder_reportleaks(); void memleakfinder_reportleaks_appendfile(const char *filename, const char *markertext); void memleakfinder_setup_exit_report(const char *filename, const char *markertext); void memleakfinder_startsectionmonitor(); void memleakfinder_traceblocksinsection(); void memleakfinder_notaleak(void *ptr); void *operator new (size_t size, const char *file, int line); void *operator new[](size_t size, const char *file, int line); // define the malloc functions now, if required #ifdef MEMLEAKFINDER_FULL_MALLOC_MONITORING #define malloc(X) memleakfinder_malloc(X, __FILE__, __LINE__) #define realloc memleakfinder_realloc #define free memleakfinder_free #define MEMLEAKFINDER_MALLOC_MONITORING_DEFINED #endif #endif // MEMLEAKFINDER__H boxbackup/lib/common/BannerText.h0000664000175000017500000000072011345746100017573 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BannerText.h // Purpose: Banner text for daemons and utilities // Created: 1/1/04 // // -------------------------------------------------------------------------- #ifndef BANNERTEXT__H #define BANNERTEXT__H #define BANNER_TEXT(UtilityName) \ "Box " UtilityName " v" BOX_VERSION ", (c) Ben Summers and " \ "contributors 2003-2010" #endif // BANNERTEXT__H boxbackup/lib/common/Makefile.extra0000664000175000017500000000060011345266370020135 0ustar siretartsiretart MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_CommonException.h autogen_CommonException.cpp: $(MAKEEXCEPTION) CommonException.txt $(_PERL) $(MAKEEXCEPTION) CommonException.txt # AUTOGEN SEEDING autogen_ConversionException.h autogen_ConversionException.cpp: $(MAKEEXCEPTION) ConversionException.txt $(_PERL) $(MAKEEXCEPTION) ConversionException.txt boxbackup/lib/common/Configuration.cpp0000664000175000017500000005210411436002351020660 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Configuration.cpp // Purpose: Reading configuration files // Created: 2003/07/23 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include "Configuration.h" #include "CommonException.h" #include "Guards.h" #include "FdGetLine.h" #include "MemLeakFindOn.h" #include // utility whitespace function inline bool iw(int c) { return (c == ' ' || c == '\t' || c == '\v' || c == '\f'); // \r, \n are already excluded } // boolean values static const char *sValueBooleanStrings[] = {"yes", "true", "no", "false", 0}; static const bool sValueBooleanValue[] = {true, true, false, false}; ConfigurationVerifyKey::ConfigurationVerifyKey ( std::string name, int flags, void *testFunction ) : mName(name), mHasDefaultValue(false), mFlags(flags), mTestFunction(testFunction) { } // to allow passing NULL for default ListenAddresses ConfigurationVerifyKey::ConfigurationVerifyKey ( std::string name, int flags, NoDefaultValue_t t, void *testFunction ) : mName(name), mHasDefaultValue(false), mFlags(flags), mTestFunction(testFunction) { } ConfigurationVerifyKey::ConfigurationVerifyKey ( std::string name, int flags, std::string defaultValue, void *testFunction ) : mName(name), mDefaultValue(defaultValue), mHasDefaultValue(true), mFlags(flags), mTestFunction(testFunction) { } ConfigurationVerifyKey::ConfigurationVerifyKey ( std::string name, int flags, const char *defaultValue, void *testFunction ) : mName(name), mDefaultValue(defaultValue), mHasDefaultValue(true), mFlags(flags), mTestFunction(testFunction) { } ConfigurationVerifyKey::ConfigurationVerifyKey ( std::string name, int flags, int defaultValue, void *testFunction ) : mName(name), mHasDefaultValue(true), mFlags(flags), mTestFunction(testFunction) { ASSERT(flags & ConfigTest_IsInt); std::ostringstream val; val << defaultValue; mDefaultValue = val.str(); } ConfigurationVerifyKey::ConfigurationVerifyKey ( std::string name, int flags, bool defaultValue, void *testFunction ) : mName(name), mHasDefaultValue(true), mFlags(flags), mTestFunction(testFunction) { ASSERT(flags & ConfigTest_IsBool); mDefaultValue = defaultValue ? "yes" : "no"; } ConfigurationVerifyKey::ConfigurationVerifyKey ( const ConfigurationVerifyKey& rToCopy ) : mName(rToCopy.mName), mDefaultValue(rToCopy.mDefaultValue), mHasDefaultValue(rToCopy.mHasDefaultValue), mFlags(rToCopy.mFlags), mTestFunction(rToCopy.mTestFunction) { } // -------------------------------------------------------------------------- // // Function // Name: Configuration::Configuration(const std::string &) // Purpose: Constructor // Created: 2003/07/23 // // -------------------------------------------------------------------------- Configuration::Configuration(const std::string &rName) : mName(rName) { } // -------------------------------------------------------------------------- // // Function // Name: Configuration::Configuration(const Configuration &) // Purpose: Copy constructor // Created: 2003/07/23 // // -------------------------------------------------------------------------- Configuration::Configuration(const Configuration &rToCopy) : mName(rToCopy.mName), mKeys(rToCopy.mKeys), mSubConfigurations(rToCopy.mSubConfigurations) { } // -------------------------------------------------------------------------- // // Function // Name: Configuration::~Configuration() // Purpose: Destructor // Created: 2003/07/23 // // -------------------------------------------------------------------------- Configuration::~Configuration() { } // -------------------------------------------------------------------------- // // Function // Name: Configuration::LoadAndVerify(const std::string &, const ConfigurationVerify *, std::string &) // Purpose: Loads a configuration file from disc, checks it. Returns NULL if it was faulting, in which // case they'll be an error message. // Created: 2003/07/23 // // -------------------------------------------------------------------------- std::auto_ptr Configuration::LoadAndVerify( const std::string& rFilename, const ConfigurationVerify *pVerify, std::string &rErrorMsg) { // Just to make sure rErrorMsg.erase(); // Open the file FileHandleGuard file(rFilename); // GetLine object FdGetLine getline(file); // Object to create std::auto_ptr apConfig( new Configuration(std::string(""))); try { // Load LoadInto(*apConfig, getline, rErrorMsg, true); if(!rErrorMsg.empty()) { // An error occured, return now BOX_ERROR("Error in Configuration::LoadInto: " << rErrorMsg); return std::auto_ptr(0); } // Verify? if(pVerify) { if(!apConfig->Verify(*pVerify, std::string(), rErrorMsg)) { BOX_ERROR("Error verifying configuration: " << rErrorMsg); return std::auto_ptr(0); } } } catch(...) { // Clean up throw; } // Success. Return result. return apConfig; } // -------------------------------------------------------------------------- // // Function // Name: LoadInto(Configuration &, FdGetLine &, std::string &, bool) // Purpose: Private. Load configuration information from the file into the config object. // Returns 'abort' flag, if error, will be appended to rErrorMsg. // Created: 2003/07/24 // // -------------------------------------------------------------------------- bool Configuration::LoadInto(Configuration &rConfig, FdGetLine &rGetLine, std::string &rErrorMsg, bool RootLevel) { bool startBlockExpected = false; std::string blockName; //TRACE1("BLOCK: |%s|\n", rConfig.mName.c_str()); while(!rGetLine.IsEOF()) { std::string line(rGetLine.GetLine(true)); /* preprocess out whitespace and comments */ if(line.empty()) { // Ignore blank lines continue; } // Line an open block string? if(line == "{") { if(startBlockExpected) { // New config object Configuration subConfig(blockName); // Continue processing into this block if(!LoadInto(subConfig, rGetLine, rErrorMsg, false)) { // Abort error return false; } startBlockExpected = false; // Store... rConfig.AddSubConfig(blockName, subConfig); } else { rErrorMsg += "Unexpected start block in " + rConfig.mName + "\n"; } } else { // Close block? if(line == "}") { if(RootLevel) { // error -- root level doesn't have a close rErrorMsg += "Root level has close block -- forgot to terminate subblock?\n"; // but otherwise ignore } else { //TRACE0("ENDBLOCK\n"); return true; // All very good and nice } } // Either a key, or a sub block beginning else { // Can't be a start block if(startBlockExpected) { rErrorMsg += "Block " + blockName + " wasn't started correctly (no '{' on line of it's own)\n"; startBlockExpected = false; } // Has the line got an = in it? unsigned int equals = 0; for(; equals < line.size(); ++equals) { if(line[equals] == '=') { // found! break; } } if(equals < line.size()) { // Make key value pair unsigned int keyend = equals; while(keyend > 0 && iw(line[keyend-1])) { keyend--; } unsigned int valuestart = equals+1; while(valuestart < line.size() && iw(line[valuestart])) { valuestart++; } if(keyend > 0 && valuestart <= line.size()) { std::string key(line.substr(0, keyend)); std::string value(line.substr(valuestart)); rConfig.AddKeyValue(key, value); } else { rErrorMsg += "Invalid configuration key: " + line + "\n"; } } else { // Start of sub block blockName = line; startBlockExpected = true; } } } } // End of file? if(!RootLevel && rGetLine.IsEOF()) { // Error if EOF and this isn't the root level rErrorMsg += "File ended without terminating all subblocks\n"; } return true; } void Configuration::AddKeyValue(const std::string& rKey, const std::string& rValue) { // Check for duplicate values if(mKeys.find(rKey) != mKeys.end()) { // Multi-values allowed here, but checked later on mKeys[rKey] += MultiValueSeparator; mKeys[rKey] += rValue; } else { // Store mKeys[rKey] = rValue; } } void Configuration::AddSubConfig(const std::string& rName, const Configuration& rSubConfig) { mSubConfigurations.push_back( std::pair(rName, rSubConfig)); } // -------------------------------------------------------------------------- // // Function // Name: Configuration::KeyExists(const std::string&) // Purpose: Checks to see if a key exists // Created: 2003/07/23 // // -------------------------------------------------------------------------- bool Configuration::KeyExists(const std::string& rKeyName) const { return mKeys.find(rKeyName) != mKeys.end(); } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyValue(const std::string&) // Purpose: Returns the value of a configuration variable // Created: 2003/07/23 // // -------------------------------------------------------------------------- const std::string &Configuration::GetKeyValue(const std::string& rKeyName) const { std::map::const_iterator i(mKeys.find(rKeyName)); if(i == mKeys.end()) { BOX_ERROR("Missing configuration key: " << rKeyName); THROW_EXCEPTION(CommonException, ConfigNoKey) } else { return i->second; } } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyValueInt(const std::string& rKeyName) // Purpose: Gets a key value as an integer // Created: 2003/07/23 // // -------------------------------------------------------------------------- int Configuration::GetKeyValueInt(const std::string& rKeyName) const { std::map::const_iterator i(mKeys.find(rKeyName)); if(i == mKeys.end()) { THROW_EXCEPTION(CommonException, ConfigNoKey) } else { long value = ::strtol((i->second).c_str(), NULL, 0 /* C style handling */); if(value == LONG_MAX || value == LONG_MIN) { THROW_EXCEPTION(CommonException, ConfigBadIntValue) } return (int)value; } } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyValueUint32(const std::string& rKeyName) // Purpose: Gets a key value as a 32-bit unsigned integer // Created: 2003/07/23 // // -------------------------------------------------------------------------- uint32_t Configuration::GetKeyValueUint32(const std::string& rKeyName) const { std::map::const_iterator i(mKeys.find(rKeyName)); if(i == mKeys.end()) { THROW_EXCEPTION(CommonException, ConfigNoKey) } else { errno = 0; long value = ::strtoul((i->second).c_str(), NULL, 0 /* C style handling */); if(errno != 0) { THROW_EXCEPTION(CommonException, ConfigBadIntValue) } return (int)value; } } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyValueBool(const std::string&) // Purpose: Gets a key value as a boolean // Created: 17/2/04 // // -------------------------------------------------------------------------- bool Configuration::GetKeyValueBool(const std::string& rKeyName) const { std::map::const_iterator i(mKeys.find(rKeyName)); if(i == mKeys.end()) { THROW_EXCEPTION(CommonException, ConfigNoKey) } else { bool value = false; // Anything this is called for should have been verified as having a correct // string in the verification section. However, this does default to false // if it isn't in the string table. for(int l = 0; sValueBooleanStrings[l] != 0; ++l) { if(::strcasecmp((i->second).c_str(), sValueBooleanStrings[l]) == 0) { // Found. value = sValueBooleanValue[l]; break; } } return value; } } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyNames() // Purpose: Returns list of key names // Created: 2003/07/24 // // -------------------------------------------------------------------------- std::vector Configuration::GetKeyNames() const { std::map::const_iterator i(mKeys.begin()); std::vector r; for(; i != mKeys.end(); ++i) { r.push_back(i->first); } return r; } // -------------------------------------------------------------------------- // // Function // Name: Configuration::SubConfigurationExists(const // std::string&) // Purpose: Checks to see if a sub configuration exists // Created: 2003/07/23 // // -------------------------------------------------------------------------- bool Configuration::SubConfigurationExists(const std::string& rSubName) const { // Attempt to find it... std::list >::const_iterator i(mSubConfigurations.begin()); for(; i != mSubConfigurations.end(); ++i) { // This the one? if(i->first == rSubName) { // Yes. return true; } } // didn't find it. return false; } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetSubConfiguration(const // std::string&) // Purpose: Gets a sub configuration // Created: 2003/07/23 // // -------------------------------------------------------------------------- const Configuration &Configuration::GetSubConfiguration(const std::string& rSubName) const { // Attempt to find it... std::list >::const_iterator i(mSubConfigurations.begin()); for(; i != mSubConfigurations.end(); ++i) { // This the one? if(i->first == rSubName) { // Yes. return i->second; } } THROW_EXCEPTION(CommonException, ConfigNoSubConfig) } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetSubConfiguration(const // std::string&) // Purpose: Gets a sub configuration for editing // Created: 2008/08/12 // // -------------------------------------------------------------------------- Configuration &Configuration::GetSubConfigurationEditable(const std::string& rSubName) { // Attempt to find it... for(SubConfigListType::iterator i = mSubConfigurations.begin(); i != mSubConfigurations.end(); ++i) { // This the one? if(i->first == rSubName) { // Yes. return i->second; } } THROW_EXCEPTION(CommonException, ConfigNoSubConfig) } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetSubConfigurationNames() // Purpose: Return list of sub configuration names // Created: 2003/07/24 // // -------------------------------------------------------------------------- std::vector Configuration::GetSubConfigurationNames() const { std::list >::const_iterator i(mSubConfigurations.begin()); std::vector r; for(; i != mSubConfigurations.end(); ++i) { r.push_back(i->first); } return r; } // -------------------------------------------------------------------------- // // Function // Name: Configuration::Verify(const ConfigurationVerify &, const std::string &, std::string &) // Purpose: Checks that the configuration is valid according to the // supplied verifier // Created: 2003/07/24 // // -------------------------------------------------------------------------- bool Configuration::Verify(const ConfigurationVerify &rVerify, const std::string &rLevel, std::string &rErrorMsg) { bool ok = true; // First... check the keys if(rVerify.mpKeys != 0) { const ConfigurationVerifyKey *pvkey = rVerify.mpKeys; bool todo = true; do { // Can the key be found? if(KeyExists(pvkey->Name())) { // Get value const std::string &rval = GetKeyValue(pvkey->Name()); const char *val = rval.c_str(); // Check it's a number? if((pvkey->Flags() & ConfigTest_IsInt) == ConfigTest_IsInt) { // Test it... char *end; long r = ::strtol(val, &end, 0); if(r == LONG_MIN || r == LONG_MAX || end != (val + rval.size())) { // not a good value ok = false; rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid integer.\n"; } } // Check it's a number? if(pvkey->Flags() & ConfigTest_IsUint32) { // Test it... char *end; errno = 0; uint32_t r = ::strtoul(val, &end, 0); if(errno != 0 || end != (val + rval.size())) { // not a good value ok = false; rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid unsigned 32-bit integer.\n"; } } // Check it's a bool? if((pvkey->Flags() & ConfigTest_IsBool) == ConfigTest_IsBool) { // See if it's one of the allowed strings. bool found = false; for(int l = 0; sValueBooleanStrings[l] != 0; ++l) { if(::strcasecmp(val, sValueBooleanStrings[l]) == 0) { // Found. found = true; break; } } // Error if it's not one of them. if(!found) { ok = false; rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid boolean value.\n"; } } // Check for multi valued statments where they're not allowed if((pvkey->Flags() & ConfigTest_MultiValueAllowed) == 0) { // Check to see if this key is a multi-value -- it shouldn't be if(rval.find(MultiValueSeparator) != rval.npos) { ok = false; rErrorMsg += rLevel + mName +"." + pvkey->Name() + " (key) multi value not allowed (duplicated key?).\n"; } } } else { // Is it required to exist? if((pvkey->Flags() & ConfigTest_Exists) == ConfigTest_Exists) { // Should exist, but doesn't. ok = false; rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is missing.\n"; } else if(pvkey->HasDefaultValue()) { mKeys[pvkey->Name()] = pvkey->DefaultValue(); } } if((pvkey->Flags() & ConfigTest_LastEntry) == ConfigTest_LastEntry) { // No more! todo = false; } // next pvkey++; } while(todo); // Check for additional keys for(std::map::const_iterator i = mKeys.begin(); i != mKeys.end(); ++i) { // Is the name in the list? const ConfigurationVerifyKey *scan = rVerify.mpKeys; bool found = false; while(scan) { if(scan->Name() == i->first) { found = true; break; } // Next? if((scan->Flags() & ConfigTest_LastEntry) == ConfigTest_LastEntry) { break; } scan++; } if(!found) { // Shouldn't exist, but does. ok = false; rErrorMsg += rLevel + mName + "." + i->first + " (key) is not a known key. Check spelling and placement.\n"; } } } // Then the sub configurations if(rVerify.mpSubConfigurations) { // Find the wildcard entry, if it exists, and check that required subconfigs are there const ConfigurationVerify *wildcardverify = 0; const ConfigurationVerify *scan = rVerify.mpSubConfigurations; while(scan) { if(scan->mName.length() > 0 && scan->mName[0] == '*') { wildcardverify = scan; } // Required? if((scan->Tests & ConfigTest_Exists) == ConfigTest_Exists) { if(scan->mName.length() > 0 && scan->mName[0] == '*') { // Check something exists if(mSubConfigurations.size() < 1) { // A sub config should exist, but doesn't. ok = false; rErrorMsg += rLevel + mName + ".* (block) is missing (a block must be present).\n"; } } else { // Check real thing exists if(!SubConfigurationExists(scan->mName)) { // Should exist, but doesn't. ok = false; rErrorMsg += rLevel + mName + "." + scan->mName + " (block) is missing.\n"; } } } // Next? if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) { break; } scan++; } // Go through the sub configurations, one by one for(SubConfigListType::iterator i = mSubConfigurations.begin(); i != mSubConfigurations.end(); ++i) { // Can this be found? const ConfigurationVerify *subverify = 0; const ConfigurationVerify *scan = rVerify.mpSubConfigurations; const char *name = i->first.c_str(); ASSERT(name); while(scan) { if(scan->mName == name) { // found it! subverify = scan; } // Next? if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) { break; } scan++; } // Use wildcard? if(subverify == 0) { subverify = wildcardverify; } // Verify if(subverify) { // override const-ness here... if(!i->second.Verify(*subverify, mName + '.', rErrorMsg)) { ok = false; } } } } return ok; } boxbackup/lib/common/ReadLoggingStream.cpp0000664000175000017500000001234511053243116021413 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ReadLoggingStream.cpp // Purpose: Buffering wrapper around IOStreams // Created: 2007/01/16 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "ReadLoggingStream.h" #include "CommonException.h" #include "Logging.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::ReadLoggingStream(const char *, int, int) // Purpose: Constructor, set up buffer // Created: 2007/01/16 // // -------------------------------------------------------------------------- ReadLoggingStream::ReadLoggingStream(IOStream& rSource, Logger& rLogger) : mrSource(rSource), mOffset(0), mLength(mrSource.BytesLeftToRead()), mTotalRead(0), mStartTime(GetCurrentBoxTime()), mrLogger(rLogger) { } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::Read(void *, int) // Purpose: Reads bytes from the file // Created: 2007/01/16 // // -------------------------------------------------------------------------- int ReadLoggingStream::Read(void *pBuffer, int NBytes, int Timeout) { int numBytesRead = mrSource.Read(pBuffer, NBytes, Timeout); if (numBytesRead > 0) { mTotalRead += numBytesRead; mOffset += numBytesRead; } if (mLength == 0) { mrLogger.Log(numBytesRead, mOffset); } else if (mTotalRead == 0) { mrLogger.Log(numBytesRead, mOffset, mLength); } else { box_time_t timeNow = GetCurrentBoxTime(); box_time_t elapsed = timeNow - mStartTime; box_time_t finish = (elapsed * mLength) / mTotalRead; // box_time_t remain = finish - elapsed; mrLogger.Log(numBytesRead, mOffset, mLength, elapsed, finish); } return numBytesRead; } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::BytesLeftToRead() // Purpose: Returns number of bytes to read (may not be most efficient function ever) // Created: 2007/01/16 // // -------------------------------------------------------------------------- IOStream::pos_type ReadLoggingStream::BytesLeftToRead() { return mLength - mOffset; } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::Write(void *, int) // Purpose: Writes bytes to the underlying stream (not supported) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void ReadLoggingStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::GetPosition() // Purpose: Get position in stream // Created: 2003/08/21 // // -------------------------------------------------------------------------- IOStream::pos_type ReadLoggingStream::GetPosition() const { return mOffset; } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::Seek(pos_type, int) // Purpose: Seeks within file, as lseek, invalidate buffer // Created: 2003/07/31 // // -------------------------------------------------------------------------- void ReadLoggingStream::Seek(IOStream::pos_type Offset, int SeekType) { mrSource.Seek(Offset, SeekType); switch (SeekType) { case SeekType_Absolute: { // just go there mOffset = Offset; } break; case SeekType_Relative: { // Actual underlying file position is // (mBufferSize - mBufferPosition) ahead of us. // Need to subtract that amount from the seek // to seek forward that much less, putting the // real pointer in the right place. mOffset += Offset; } break; case SeekType_End: { // Actual underlying file position is // (mBufferSize - mBufferPosition) ahead of us. // Need to add that amount to the seek // to seek backwards that much more, putting the // real pointer in the right place. mOffset = mLength - Offset; } } } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::Close() // Purpose: Closes the underlying stream (not needed) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void ReadLoggingStream::Close() { THROW_EXCEPTION(CommonException, NotSupported); } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::StreamDataLeft() // Purpose: Any data left to write? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool ReadLoggingStream::StreamDataLeft() { return mrSource.StreamDataLeft(); } // -------------------------------------------------------------------------- // // Function // Name: ReadLoggingStream::StreamClosed() // Purpose: Is the stream closed? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool ReadLoggingStream::StreamClosed() { return mrSource.StreamClosed(); } boxbackup/lib/common/NamedLock.cpp0000664000175000017500000000674211221737343017725 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: NamedLock.cpp // Purpose: A global named lock, implemented as a lock file in // file system // Created: 2003/08/28 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_FLOCK #include #endif #include "NamedLock.h" #include "CommonException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: NamedLock::NamedLock() // Purpose: Constructor // Created: 2003/08/28 // // -------------------------------------------------------------------------- NamedLock::NamedLock() : mFileDescriptor(-1) { } // -------------------------------------------------------------------------- // // Function // Name: NamedLock::~NamedLock() // Purpose: Destructor (automatically unlocks if locked) // Created: 2003/08/28 // // -------------------------------------------------------------------------- NamedLock::~NamedLock() { if(mFileDescriptor != -1) { ReleaseLock(); } } // -------------------------------------------------------------------------- // // Function // Name: NamedLock::TryAndGetLock(const char *, int) // Purpose: Tries to get a lock on the name in the file system. // IMPORTANT NOTE: If a file exists with this name, it // will be deleted. // Created: 2003/08/28 // // -------------------------------------------------------------------------- bool NamedLock::TryAndGetLock(const std::string& rFilename, int mode) { // Check if(mFileDescriptor != -1) { THROW_EXCEPTION(CommonException, NamedLockAlreadyLockingSomething) } // See if the lock can be got #if HAVE_DECL_O_EXLOCK int fd = ::open(rFilename.c_str(), O_WRONLY | O_NONBLOCK | O_CREAT | O_TRUNC | O_EXLOCK, mode); if(fd != -1) { // Got a lock, lovely mFileDescriptor = fd; return true; } // Failed. Why? if(errno != EWOULDBLOCK) { // Not the expected error THROW_EXCEPTION(CommonException, OSFileError) } return false; #else int fd = ::open(rFilename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode); if(fd == -1) { BOX_WARNING("Failed to open lockfile: " << rFilename); THROW_EXCEPTION(CommonException, OSFileError) } #ifdef HAVE_FLOCK if(::flock(fd, LOCK_EX | LOCK_NB) != 0) { ::close(fd); if(errno == EWOULDBLOCK) { return false; } else { THROW_EXCEPTION(CommonException, OSFileError) } } #elif HAVE_DECL_F_SETLK struct flock desc; desc.l_type = F_WRLCK; desc.l_whence = SEEK_SET; desc.l_start = 0; desc.l_len = 0; if(::fcntl(fd, F_SETLK, &desc) != 0) { ::close(fd); if(errno == EAGAIN) { return false; } else { THROW_EXCEPTION(CommonException, OSFileError) } } #endif // Success mFileDescriptor = fd; return true; #endif } // -------------------------------------------------------------------------- // // Function // Name: NamedLock::ReleaseLock() // Purpose: Release the lock. Exceptions if the lock is not held // Created: 2003/08/28 // // -------------------------------------------------------------------------- void NamedLock::ReleaseLock() { // Got a lock? if(mFileDescriptor == -1) { THROW_EXCEPTION(CommonException, NamedLockNotHeld) } // Close the file if(::close(mFileDescriptor) != 0) { THROW_EXCEPTION(CommonException, OSFileError) } // Mark as unlocked mFileDescriptor = -1; } boxbackup/lib/common/EventWatchFilesystemObject.cpp0000664000175000017500000000562010775523641023335 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: EventWatchFilesystemObject.cpp // Purpose: WaitForEvent compatible object for watching directories // Created: 12/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include "EventWatchFilesystemObject.h" #include "autogen_CommonException.h" #include "Logging.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: EventWatchFilesystemObject::EventWatchFilesystemObject // (const char *) // Purpose: Constructor -- opens the file object // Created: 12/3/04 // // -------------------------------------------------------------------------- EventWatchFilesystemObject::EventWatchFilesystemObject(const char *Filename) #ifdef HAVE_KQUEUE : mDescriptor(::open(Filename, O_RDONLY /*O_EVTONLY*/, 0)) #endif { #ifdef HAVE_KQUEUE if(mDescriptor == -1) { BOX_LOG_SYS_ERROR("EventWatchFilesystemObject: " "Failed to open file '" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileOpenError) } #else THROW_EXCEPTION(CommonException, KQueueNotSupportedOnThisPlatform) #endif } // -------------------------------------------------------------------------- // // Function // Name: EventWatchFilesystemObject::~EventWatchFilesystemObject() // Purpose: Destructor // Created: 12/3/04 // // -------------------------------------------------------------------------- EventWatchFilesystemObject::~EventWatchFilesystemObject() { if(mDescriptor != -1) { ::close(mDescriptor); } } // -------------------------------------------------------------------------- // // Function // Name: EventWatchFilesystemObject::EventWatchFilesystemObject // (const EventWatchFilesystemObject &) // Purpose: Copy constructor // Created: 12/3/04 // // -------------------------------------------------------------------------- EventWatchFilesystemObject::EventWatchFilesystemObject( const EventWatchFilesystemObject &rToCopy) : mDescriptor(::dup(rToCopy.mDescriptor)) { if(mDescriptor == -1) { THROW_EXCEPTION(CommonException, OSFileError) } } #ifdef HAVE_KQUEUE // -------------------------------------------------------------------------- // // Function // Name: EventWatchFilesystemObject::FillInKEvent(struct kevent &, int) // Purpose: For WaitForEvent // Created: 12/3/04 // // -------------------------------------------------------------------------- void EventWatchFilesystemObject::FillInKEvent(struct kevent &rEvent, int Flags) const { EV_SET(&rEvent, mDescriptor, EVFILT_VNODE, EV_CLEAR, NOTE_DELETE | NOTE_WRITE, 0, (void*)this); } #else void EventWatchFilesystemObject::FillInPoll(int &fd, short &events, int Flags) const { THROW_EXCEPTION(CommonException, KQueueNotSupportedOnThisPlatform) } #endif boxbackup/lib/common/ExcludeList.cpp0000664000175000017500000002540710675532710020317 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ExcludeList.cpp // Purpose: General purpose exclusion list // Created: 28/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_REGEX_SUPPORT #ifdef HAVE_PCREPOSIX_H #include #else #include #endif #define EXCLUDELIST_IMPLEMENTATION_REGEX_T_DEFINED #endif #include "ExcludeList.h" #include "Utils.h" #include "Configuration.h" #include "Archive.h" #include "Logging.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::ExcludeList() // Purpose: Constructor. Generates an exclude list which will allow everything // Created: 28/1/04 // // -------------------------------------------------------------------------- ExcludeList::ExcludeList() : mpAlwaysInclude(0) { } // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::~ExcludeList() // Purpose: Destructor // Created: 28/1/04 // // -------------------------------------------------------------------------- ExcludeList::~ExcludeList() { #ifdef HAVE_REGEX_SUPPORT // free regex memory while(mRegex.size() > 0) { regex_t *pregex = mRegex.back(); mRegex.pop_back(); // Free regex storage, and the structure itself ::regfree(pregex); delete pregex; } #endif // Clean up exceptions list if(mpAlwaysInclude != 0) { delete mpAlwaysInclude; mpAlwaysInclude = 0; } } #ifdef WIN32 std::string ExcludeList::ReplaceSlashesDefinite(const std::string& input) const { std::string output = input; for (std::string::size_type pos = output.find("/"); pos != std::string::npos; pos = output.find("/")) { output.replace(pos, 1, DIRECTORY_SEPARATOR); } for (std::string::iterator i = output.begin(); i != output.end(); i++) { *i = tolower(*i); } return output; } std::string ExcludeList::ReplaceSlashesRegex(const std::string& input) const { std::string output = input; for (std::string::size_type pos = output.find("/"); pos != std::string::npos; pos = output.find("/")) { output.replace(pos, 1, "\\" DIRECTORY_SEPARATOR); } for (std::string::iterator i = output.begin(); i != output.end(); i++) { *i = tolower(*i); } return output; } #endif // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::AddDefiniteEntries(const std::string &) // Purpose: Adds a number of definite entries to the exclude list -- ones which // will be excluded if and only if the test string matches exactly. // Uses the Configuration classes' multi-value conventions, with // multiple entires in one string separated by Configuration::MultiValueSeparator // Created: 28/1/04 // // -------------------------------------------------------------------------- void ExcludeList::AddDefiniteEntries(const std::string &rEntries) { // Split strings up std::vector ens; SplitString(rEntries, Configuration::MultiValueSeparator, ens); // Add to set of excluded strings for(std::vector::const_iterator i(ens.begin()); i != ens.end(); ++i) { if(i->size() > 0) { std::string entry = *i; // Convert any forward slashes in the string // to backslashes #ifdef WIN32 entry = ReplaceSlashesDefinite(entry); #endif if (entry.size() > 0 && entry[entry.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR) { BOX_WARNING("Exclude entry ends in path " "separator, will never match: " << entry); } mDefinite.insert(entry); } } } // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::AddRegexEntries(const std::string &) // Purpose: Adds a number of regular expression entries to the exclude list -- // if the test expression matches any of these regex, it will be excluded. // Uses the Configuration classes' multi-value conventions, with // multiple entires in one string separated by Configuration::MultiValueSeparator // Created: 28/1/04 // // -------------------------------------------------------------------------- void ExcludeList::AddRegexEntries(const std::string &rEntries) { #ifdef HAVE_REGEX_SUPPORT // Split strings up std::vector ens; SplitString(rEntries, Configuration::MultiValueSeparator, ens); // Create and add new regular expressions for(std::vector::const_iterator i(ens.begin()); i != ens.end(); ++i) { if(i->size() > 0) { // Allocate memory regex_t *pregex = new regex_t; try { std::string entry = *i; // Convert any forward slashes in the string // to appropriately escaped backslashes #ifdef WIN32 entry = ReplaceSlashesRegex(entry); #endif // Compile int errcode = ::regcomp(pregex, entry.c_str(), REG_EXTENDED | REG_NOSUB); if (errcode != 0) { char buf[1024]; regerror(errcode, pregex, buf, sizeof(buf)); BOX_ERROR("Invalid regular expression: " << entry << ": " << buf); THROW_EXCEPTION(CommonException, BadRegularExpression) } // Store in list of regular expressions mRegex.push_back(pregex); // Store in list of regular expression string for Serialize mRegexStr.push_back(entry.c_str()); } catch(...) { delete pregex; throw; } } } #else THROW_EXCEPTION(CommonException, RegexNotSupportedOnThisPlatform) #endif } // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::IsExcluded(const std::string &) // Purpose: Returns true if the entry should be excluded // Created: 28/1/04 // // -------------------------------------------------------------------------- bool ExcludeList::IsExcluded(const std::string &rTest) const { std::string test = rTest; #ifdef WIN32 test = ReplaceSlashesDefinite(test); #endif // Check against the always include list if(mpAlwaysInclude != 0) { if(mpAlwaysInclude->IsExcluded(test)) { // Because the "always include" list says it's 'excluded' // this means it should actually be included. return false; } } // Is it in the set of definite entries? if(mDefinite.find(test) != mDefinite.end()) { return true; } // Check against regular expressions #ifdef HAVE_REGEX_SUPPORT for(std::vector::const_iterator i(mRegex.begin()); i != mRegex.end(); ++i) { // Test against this expression if(regexec(*i, test.c_str(), 0, 0 /* no match information required */, 0 /* no flags */) == 0) { // match happened return true; } // In all other cases, including an error, just continue to the next expression } #endif return false; } // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::SetAlwaysIncludeList(ExcludeList *) // Purpose: Takes ownership of the list, deletes any pre-existing list. // NULL is acceptable to delete the list. // The AlwaysInclude list is a list of exceptions to the exclusions. // Created: 19/2/04 // // -------------------------------------------------------------------------- void ExcludeList::SetAlwaysIncludeList(ExcludeList *pAlwaysInclude) { // Delete old list if(mpAlwaysInclude != 0) { delete mpAlwaysInclude; mpAlwaysInclude = 0; } // Store the pointer mpAlwaysInclude = pAlwaysInclude; } // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::Deserialize(Archive & rArchive) // Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction. // // Created: 2005/04/11 // // -------------------------------------------------------------------------- void ExcludeList::Deserialize(Archive & rArchive) { // // // mDefinite.clear(); #ifdef HAVE_REGEX_SUPPORT // free regex memory while(mRegex.size() > 0) { regex_t *pregex = mRegex.back(); mRegex.pop_back(); // Free regex storage, and the structure itself ::regfree(pregex); delete pregex; } mRegexStr.clear(); #endif // Clean up exceptions list if(mpAlwaysInclude != 0) { delete mpAlwaysInclude; mpAlwaysInclude = 0; } // // // int64_t iCount = 0; rArchive.Read(iCount); if (iCount > 0) { for (int v = 0; v < iCount; v++) { // load each one std::string strItem; rArchive.Read(strItem); mDefinite.insert(strItem); } } // // // #ifdef HAVE_REGEX_SUPPORT rArchive.Read(iCount); if (iCount > 0) { for (int v = 0; v < iCount; v++) { std::string strItem; rArchive.Read(strItem); // Allocate memory regex_t* pregex = new regex_t; try { // Compile if(::regcomp(pregex, strItem.c_str(), REG_EXTENDED | REG_NOSUB) != 0) { THROW_EXCEPTION(CommonException, BadRegularExpression) } // Store in list of regular expressions mRegex.push_back(pregex); // Store in list of regular expression strings // for Serialize mRegexStr.push_back(strItem); } catch(...) { delete pregex; throw; } } } #endif // HAVE_REGEX_SUPPORT // // // int64_t aMagicMarker = 0; rArchive.Read(aMagicMarker); if (aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP) { // NOOP } else if (aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE) { mpAlwaysInclude = new ExcludeList; if (!mpAlwaysInclude) { throw std::bad_alloc(); } mpAlwaysInclude->Deserialize(rArchive); } else { // there is something going on here THROW_EXCEPTION(CommonException, Internal) } } // -------------------------------------------------------------------------- // // Function // Name: ExcludeList::Serialize(Archive & rArchive) // Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction. // // Created: 2005/04/11 // // -------------------------------------------------------------------------- void ExcludeList::Serialize(Archive & rArchive) const { // // // int64_t iCount = mDefinite.size(); rArchive.Write(iCount); for (std::set::const_iterator i = mDefinite.begin(); i != mDefinite.end(); i++) { rArchive.Write(*i); } // // // #ifdef HAVE_REGEX_SUPPORT // don't even try to save compiled regular expressions, // use string copies instead. ASSERT(mRegex.size() == mRegexStr.size()); iCount = mRegexStr.size(); rArchive.Write(iCount); for (std::vector::const_iterator i = mRegexStr.begin(); i != mRegexStr.end(); i++) { rArchive.Write(*i); } #endif // HAVE_REGEX_SUPPORT // // // if (!mpAlwaysInclude) { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP; rArchive.Write(aMagicMarker); } else { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows rArchive.Write(aMagicMarker); mpAlwaysInclude->Serialize(rArchive); } } boxbackup/lib/common/IOStreamGetLine.cpp0000664000175000017500000001237010347400657021020 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: IOStreamGetLine.cpp // Purpose: Line based file descriptor reading // Created: 2003/07/24 // // -------------------------------------------------------------------------- #include "Box.h" #include "IOStreamGetLine.h" #include "CommonException.h" #include "MemLeakFindOn.h" // utility whitespace function inline bool iw(int c) { return (c == ' ' || c == '\t' || c == '\v' || c == '\f'); // \r, \n are already excluded } // -------------------------------------------------------------------------- // // Function // Name: IOStreamGetLine::IOStreamGetLine(int) // Purpose: Constructor, taking file descriptor // Created: 2003/07/24 // // -------------------------------------------------------------------------- IOStreamGetLine::IOStreamGetLine(IOStream &Stream) : mrStream(Stream), mLineNumber(0), mBufferBegin(0), mBytesInBuffer(0), mPendingEOF(false), mEOF(false) { } // -------------------------------------------------------------------------- // // Function // Name: IOStreamGetLine::~IOStreamGetLine() // Purpose: Destructor // Created: 2003/07/24 // // -------------------------------------------------------------------------- IOStreamGetLine::~IOStreamGetLine() { } // -------------------------------------------------------------------------- // // Function // Name: IOStreamGetLine::GetLine(std::string &, bool, int) // Purpose: Gets a line from the file, returning it in rOutput. If Preprocess is true, leading // and trailing whitespace is removed, and comments (after #) // are deleted. // Returns true if a line is available now, false if retrying may get a line (eg timeout, signal), // and exceptions if it's EOF. // Created: 2003/07/24 // // -------------------------------------------------------------------------- bool IOStreamGetLine::GetLine(std::string &rOutput, bool Preprocess, int Timeout) { // EOF? if(mEOF) {THROW_EXCEPTION(CommonException, GetLineEOF)} // Initialise string to stored into std::string r(mPendingString); mPendingString.erase(); bool foundLineEnd = false; while(!foundLineEnd && !mEOF) { // Use any bytes left in the buffer while(mBufferBegin < mBytesInBuffer) { int c = mBuffer[mBufferBegin++]; if(c == '\r') { // Ignore nasty Windows line ending extra chars } else if(c == '\n') { // Line end! foundLineEnd = true; break; } else { // Add to string r += c; } // Implicit line ending at EOF if(mBufferBegin >= mBytesInBuffer && mPendingEOF) { foundLineEnd = true; } } // Check size if(r.size() > IOSTREAMGETLINE_MAX_LINE_SIZE) { THROW_EXCEPTION(CommonException, GetLineTooLarge) } // Read more in? if(!foundLineEnd && mBufferBegin >= mBytesInBuffer && !mPendingEOF) { int bytes = mrStream.Read(mBuffer, sizeof(mBuffer), Timeout); // Adjust buffer info mBytesInBuffer = bytes; mBufferBegin = 0; // EOF / closed? if(!mrStream.StreamDataLeft()) { mPendingEOF = true; } // No data returned? if(bytes == 0 && mrStream.StreamDataLeft()) { // store string away mPendingString = r; // Return false; return false; } } // EOF? if(mPendingEOF && mBufferBegin >= mBytesInBuffer) { // File is EOF, and now we've depleted the buffer completely, so tell caller as well. mEOF = true; } } if(!Preprocess) { rOutput = r; return true; } else { // Check for comment char, but char before must be whitespace int end = 0; int size = r.size(); while(end < size) { if(r[end] == '#' && (end == 0 || (iw(r[end-1])))) { break; } end++; } // Remove whitespace int begin = 0; while(begin < size && iw(r[begin])) { begin++; } if(!iw(r[end])) end--; while(end > begin && iw(r[end])) { end--; } // Return a sub string rOutput = r.substr(begin, end - begin + 1); return true; } } // -------------------------------------------------------------------------- // // Function // Name: IOStreamGetLine::DetachFile() // Purpose: Detaches the file handle, setting the file pointer correctly. // Probably not good for sockets... // Created: 2003/07/24 // // -------------------------------------------------------------------------- void IOStreamGetLine::DetachFile() { // Adjust file pointer int bytesOver = mBytesInBuffer - mBufferBegin; ASSERT(bytesOver >= 0); if(bytesOver > 0) { mrStream.Seek(0 - bytesOver, IOStream::SeekType_Relative); } } // -------------------------------------------------------------------------- // // Function // Name: IOStreamGetLine::IgnoreBufferedData(int) // Purpose: Ignore buffered bytes (effectively removing them from the // beginning of the buffered data.) // Cannot remove more bytes than are currently in the buffer. // Be careful when this is used! // Created: 22/12/04 // // -------------------------------------------------------------------------- void IOStreamGetLine::IgnoreBufferedData(int BytesToIgnore) { int bytesInBuffer = mBytesInBuffer - mBufferBegin; if(BytesToIgnore < 0 || BytesToIgnore > bytesInBuffer) { THROW_EXCEPTION(CommonException, IOStreamGetLineNotEnoughDataToIgnore) } mBufferBegin += BytesToIgnore; } boxbackup/lib/common/MemLeakFindOff.h0000664000175000017500000000107410347400657020300 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MemLeakFindOff.h // Purpose: Switch memory leak finding off // Created: 13/1/04 // // -------------------------------------------------------------------------- // no header guard #ifdef BOX_MEMORY_LEAK_TESTING #undef new #ifndef MEMLEAKFINDER_FULL_MALLOC_MONITORING #ifdef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED #undef malloc #undef realloc #undef free #undef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED #endif #endif #undef MEMLEAKFINDER_ENABLED #endif boxbackup/lib/common/BoxTimeToText.h0000664000175000017500000000070310514176575020253 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxTimeToText.h // Purpose: Convert box time to text // Created: 2003/10/10 // // -------------------------------------------------------------------------- #ifndef BOXTIMETOTEXT__H #define BOXTIMETOTEXT__H #include #include "BoxTime.h" std::string BoxTimeToISO8601String(box_time_t Time, bool localTime); #endif // BOXTIMETOTEXT__H boxbackup/lib/common/FdGetLine.cpp0000664000175000017500000001127210374104755017666 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: FdGetLine.cpp // Purpose: Line based file descriptor reading // Created: 2003/07/24 // // -------------------------------------------------------------------------- #include "Box.h" #include #ifdef HAVE_UNISTD_H #include #endif #include "FdGetLine.h" #include "CommonException.h" #include "MemLeakFindOn.h" // utility whitespace function inline bool iw(int c) { return (c == ' ' || c == '\t' || c == '\v' || c == '\f'); // \r, \n are already excluded } // -------------------------------------------------------------------------- // // Function // Name: FdGetLine::FdGetLine(int) // Purpose: Constructor, taking file descriptor // Created: 2003/07/24 // // -------------------------------------------------------------------------- FdGetLine::FdGetLine(int fd) : mFileHandle(fd), mLineNumber(0), mBufferBegin(0), mBytesInBuffer(0), mPendingEOF(false), mEOF(false) { if(mFileHandle < 0) {THROW_EXCEPTION(CommonException, BadArguments)} //printf("FdGetLine buffer size = %d\n", sizeof(mBuffer)); } // -------------------------------------------------------------------------- // // Function // Name: FdGetLine::~FdGetLine() // Purpose: Destructor // Created: 2003/07/24 // // -------------------------------------------------------------------------- FdGetLine::~FdGetLine() { } // -------------------------------------------------------------------------- // // Function // Name: FdGetLine::GetLine(bool) // Purpose: Returns a file from the file. If Preprocess is true, leading // and trailing whitespace is removed, and comments (after #) // are deleted. // Created: 2003/07/24 // // -------------------------------------------------------------------------- std::string FdGetLine::GetLine(bool Preprocess) { if(mFileHandle == -1) {THROW_EXCEPTION(CommonException, GetLineNoHandle)} // EOF? if(mEOF) {THROW_EXCEPTION(CommonException, GetLineEOF)} std::string r; bool foundLineEnd = false; while(!foundLineEnd && !mEOF) { // Use any bytes left in the buffer while(mBufferBegin < mBytesInBuffer) { int c = mBuffer[mBufferBegin++]; if(c == '\r') { // Ignore nasty Windows line ending extra chars } else if(c == '\n') { // Line end! foundLineEnd = true; break; } else { // Add to string r += c; } // Implicit line ending at EOF if(mBufferBegin >= mBytesInBuffer && mPendingEOF) { foundLineEnd = true; } } // Check size if(r.size() > FDGETLINE_MAX_LINE_SIZE) { THROW_EXCEPTION(CommonException, GetLineTooLarge) } // Read more in? if(!foundLineEnd && mBufferBegin >= mBytesInBuffer && !mPendingEOF) { #ifdef WIN32 int bytes; if (mFileHandle == _fileno(stdin)) { bytes = console_read(mBuffer, sizeof(mBuffer)); } else { bytes = ::read(mFileHandle, mBuffer, sizeof(mBuffer)); } #else // !WIN32 int bytes = ::read(mFileHandle, mBuffer, sizeof(mBuffer)); #endif // WIN32 // Error? if(bytes == -1) { THROW_EXCEPTION(CommonException, OSFileError) } // Adjust buffer info mBytesInBuffer = bytes; mBufferBegin = 0; // EOF / closed? if(bytes == 0) { mPendingEOF = true; } } // EOF? if(mPendingEOF && mBufferBegin >= mBytesInBuffer) { // File is EOF, and now we've depleted the buffer completely, so tell caller as well. mEOF = true; } } if(!Preprocess) { return r; } else { // Check for comment char, but char before must be whitespace int end = 0; int size = r.size(); while(end < size) { if(r[end] == '#' && (end == 0 || (iw(r[end-1])))) { break; } end++; } // Remove whitespace int begin = 0; while(begin < size && iw(r[begin])) { begin++; } if(!iw(r[end])) end--; while(end > begin && iw(r[end])) { end--; } // Return a sub string return r.substr(begin, end - begin + 1); } } // -------------------------------------------------------------------------- // // Function // Name: FdGetLine::DetachFile() // Purpose: Detaches the file handle, setting the file pointer correctly. // Probably not good for sockets... // Created: 2003/07/24 // // -------------------------------------------------------------------------- void FdGetLine::DetachFile() { if(mFileHandle == -1) {THROW_EXCEPTION(CommonException, GetLineNoHandle)} // Adjust file pointer int bytesOver = mBufferBegin - mBufferBegin; ASSERT(bytesOver >= 0); if(bytesOver > 0) { if(::lseek(mFileHandle, 0 - bytesOver, SEEK_CUR) == -1) { THROW_EXCEPTION(CommonException, OSFileError) } } // Unset file pointer mFileHandle = -1; } boxbackup/lib/common/CommonException.txt0000664000175000017500000000342010515012310021203 0ustar siretartsiretart # NOTE: Exception descriptions are for public distributions of Box Backup only -- do not rely for other applications. EXCEPTION Common 1 Internal 0 AssertFailed 1 OSFileOpenError 2 Can't open a file -- attempted to load a non-existant config file or bad file referenced within? OSFileCloseError 3 FileAlreadyClosed 4 BadArguments 5 ConfigNoKey 6 ConfigNoSubConfig 7 GetLineNoHandle 8 OSFileError 9 Error accessing a file. Check permissions. GetLineEOF 10 ConfigBadIntValue 11 GetLineTooLarge 12 Protects against very large lines using up lots of memory. NotSupported 13 OSFileReadError 14 OSFileWriteError 15 FileClosed 16 IOStreamBadSeekType 17 CantWriteToPartialReadStream 18 CollectInBufferStreamNotInCorrectPhase 19 NamedLockAlreadyLockingSomething 20 NamedLockNotHeld 21 StreamableMemBlockIncompleteRead 22 MemBlockStreamNotSupported 23 StreamDoesntHaveRequiredProperty 24 CannotWriteToReadGatherStream 25 ReadGatherStreamAddingBadBlock 26 CouldNotLookUpUsername 27 CouldNotRestoreProcessUser 28 CouldNotChangeProcessUser 29 RegexNotSupportedOnThisPlatform 30 Your platform does not have built in regular expression libraries. BadRegularExpression 31 CouldNotCreateKQueue 32 KEventErrorAdd 33 KEventErrorWait 34 KEventErrorRemove 35 KQueueNotSupportedOnThisPlatform 36 IOStreamGetLineNotEnoughDataToIgnore 37 Bad value passed to IOStreamGetLine::IgnoreBufferedData() TempDirPathTooLong 38 Your temporary directory path is too long. Check the TMP and TEMP environment variables. ArchiveBlockIncompleteRead 39 The Store Object Info File is too short or corrupted, and will be rewritten automatically when the next backup completes. AccessDenied 40 Access to the file or directory was denied. Please check the permissions. boxbackup/lib/common/WaitForEvent.h0000664000175000017500000000621211165365160020103 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: WaitForEvent.h // Purpose: Generic waiting for events, using an efficient method (platform dependent) // Created: 9/3/04 // // -------------------------------------------------------------------------- #ifndef WAITFOREVENT__H #define WAITFOREVENT__H #include #ifdef HAVE_KQUEUE #include #include #else #include #ifndef WIN32 #include #endif #endif #include #include "CommonException.h" #include "MemLeakFindOn.h" class WaitForEvent { public: WaitForEvent(int Timeout = TimeoutInfinite); ~WaitForEvent(); private: // No copying. WaitForEvent(const WaitForEvent &); WaitForEvent &operator=(const WaitForEvent &); public: enum { TimeoutInfinite = -1 }; void SetTimeout(int Timeout = TimeoutInfinite); void *Wait(); #ifndef HAVE_KQUEUE typedef struct { int fd; short events; void *item; } ItemInfo; #endif // -------------------------------------------------------------------------- // // Function // Name: WaitForEvent::Add(const Type &, int) // Purpose: Adds an event to the list of items to wait on. The flags are passed to the object. // Created: 9/3/04 // // -------------------------------------------------------------------------- template void Add(const T *pItem, int Flags = 0) { ASSERT(pItem != 0); #ifdef HAVE_KQUEUE struct kevent e; pItem->FillInKEvent(e, Flags); // Fill in extra flags to say what to do e.flags |= EV_ADD; e.udata = (void*)pItem; if(::kevent(mKQueue, &e, 1, NULL, 0, NULL) == -1) { THROW_EXCEPTION(CommonException, KEventErrorAdd) } #else // Add item ItemInfo i; pItem->FillInPoll(i.fd, i.events, Flags); i.item = (void*)pItem; mItems.push_back(i); // Delete any pre-prepared poll info, as it's now out of date if(mpPollInfo != 0) { ::free(mpPollInfo); mpPollInfo = 0; } #endif } // -------------------------------------------------------------------------- // // Function // Name: WaitForEvent::Remove(const Type &, int) // Purpose: Removes an event from the list of items to wait on. The flags are passed to the object. // Created: 9/3/04 // // -------------------------------------------------------------------------- template void Remove(const T *pItem, int Flags = 0) { ASSERT(pItem != 0); #ifdef HAVE_KQUEUE struct kevent e; pItem->FillInKEvent(e, Flags); // Fill in extra flags to say what to do e.flags |= EV_DELETE; e.udata = (void*)pItem; if(::kevent(mKQueue, &e, 1, NULL, 0, NULL) == -1) { THROW_EXCEPTION(CommonException, KEventErrorRemove) } #else if(mpPollInfo != 0) { ::free(mpPollInfo); mpPollInfo = 0; } for(std::vector::iterator i(mItems.begin()); i != mItems.end(); ++i) { if(i->item == pItem) { mItems.erase(i); return; } } #endif } private: #ifdef HAVE_KQUEUE int mKQueue; struct timespec mTimeout; struct timespec *mpTimeout; #else int mTimeout; std::vector mItems; struct pollfd *mpPollInfo; #endif }; #include "MemLeakFindOff.h" #endif // WAITFOREVENT__H boxbackup/lib/common/ConversionException.txt0000664000175000017500000000024110347400657022120 0ustar siretartsiretart EXCEPTION Conversion 12 Internal 0 CannotConvertEmptyStringToInt 1 BadStringRepresentationOfInt 2 IntOverflowInConvertFromString 3 BadIntSize 4 boxbackup/lib/common/FileModificationTime.h0000664000175000017500000000114611345271627021557 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: FileModificationTime.h // Purpose: Function for getting file modification time. // Created: 2003/08/28 // // -------------------------------------------------------------------------- #ifndef FILEMODIFICATIONTIME__H #define FILEMODIFICATIONTIME__H #include #include "BoxTime.h" box_time_t FileModificationTime(EMU_STRUCT_STAT &st); box_time_t FileAttrModificationTime(EMU_STRUCT_STAT &st); box_time_t FileModificationTimeMaxModAndAttr(EMU_STRUCT_STAT &st); #endif // FILEMODIFICATIONTIME__H boxbackup/lib/common/Utils.h0000664000175000017500000000205011245071337016621 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Utils.h // Purpose: Utility function // Created: 2003/07/31 // // -------------------------------------------------------------------------- #ifndef UTILS__H #define UTILS__H #include #include #include "MemLeakFindOn.h" std::string GetBoxBackupVersion(); void SplitString(const std::string &String, char SplitOn, std::vector &rOutput); #ifdef SHOW_BACKTRACE_ON_EXCEPTION void DumpStackBacktrace(); #endif bool FileExists(const std::string& rFilename, int64_t *pFileSize = 0, bool TreatLinksAsNotExisting = false); enum { ObjectExists_NoObject = 0, ObjectExists_File = 1, ObjectExists_Dir = 2 }; int ObjectExists(const std::string& rFilename); std::string HumanReadableSize(int64_t Bytes); std::string FormatUsageBar(int64_t Blocks, int64_t Bytes, int64_t Max, bool MachineReadable); std::string FormatUsageLineStart(const std::string& rName, bool MachineReadable); #include "MemLeakFindOff.h" #endif // UTILS__H boxbackup/lib/common/DebugMemLeakFinder.cpp0000664000175000017500000002715511165365160021504 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MemLeakFinder.cpp // Purpose: Memory leak finder // Created: 12/1/04 // // -------------------------------------------------------------------------- #ifndef BOX_RELEASE_BUILD #include "Box.h" #undef malloc #undef realloc #undef free #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include // for std::atexit #include "MemLeakFinder.h" static bool memleakfinder_initialised = false; bool memleakfinder_global_enable = false; typedef struct { size_t size; const char *file; int line; } MallocBlockInfo; typedef struct { size_t size; const char *file; int line; bool array; } ObjectInfo; namespace { static std::map sMallocBlocks; static std::map sObjectBlocks; static bool sTrackingDataDestroyed = false; static class DestructionWatchdog { public: ~DestructionWatchdog() { sTrackingDataDestroyed = true; } } sWatchdog; static bool sTrackMallocInSection = false; static std::set sSectionMallocBlocks; static bool sTrackObjectsInSection = false; static std::map sSectionObjectBlocks; static std::set sNotLeaks; void *sNotLeaksPre[1024]; size_t sNotLeaksPreNum = 0; } void memleakfinder_init() { ASSERT(!memleakfinder_initialised); { // allocates a permanent buffer on Solaris. // not a leak? std::ostringstream oss; } memleakfinder_initialised = true; } MemLeakSuppressionGuard::MemLeakSuppressionGuard() { ASSERT(memleakfinder_global_enable); memleakfinder_global_enable = false; } MemLeakSuppressionGuard::~MemLeakSuppressionGuard() { ASSERT(!memleakfinder_global_enable); memleakfinder_global_enable = true; } // these functions may well allocate memory, which we don't want to track. static int sInternalAllocDepth = 0; class InternalAllocGuard { public: InternalAllocGuard () { sInternalAllocDepth++; } ~InternalAllocGuard() { sInternalAllocDepth--; } }; void memleakfinder_malloc_add_block(void *b, size_t size, const char *file, int line) { InternalAllocGuard guard; if(b != 0) { MallocBlockInfo i; i.size = size; i.file = file; i.line = line; sMallocBlocks[b] = i; if(sTrackMallocInSection) { sSectionMallocBlocks.insert(b); } } } void *memleakfinder_malloc(size_t size, const char *file, int line) { InternalAllocGuard guard; void *b = std::malloc(size); if(!memleakfinder_global_enable) return b; if(!memleakfinder_initialised) return b; memleakfinder_malloc_add_block(b, size, file, line); //TRACE4("malloc(), %d, %s, %d, %08x\n", size, file, line, b); return b; } void *memleakfinder_realloc(void *ptr, size_t size) { InternalAllocGuard guard; if(!memleakfinder_global_enable || !memleakfinder_initialised) { return std::realloc(ptr, size); } // Check it's been allocated std::map::iterator i(sMallocBlocks.find(ptr)); if(ptr && i == sMallocBlocks.end()) { BOX_WARNING("Block " << ptr << " realloc()ated, but not " "in list. Error? Or allocated in startup static " "objects?"); } void *b = std::realloc(ptr, size); if(ptr && i!=sMallocBlocks.end()) { // Worked? if(b != 0) { // Update map MallocBlockInfo inf = i->second; inf.size = size; sMallocBlocks.erase(i); sMallocBlocks[b] = inf; if(sTrackMallocInSection) { std::set::iterator si(sSectionMallocBlocks.find(ptr)); if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); sSectionMallocBlocks.insert(b); } } } else { memleakfinder_malloc_add_block(b, size, "FOUND-IN-REALLOC", 0); } //TRACE3("realloc(), %d, %08x->%08x\n", size, ptr, b); return b; } void memleakfinder_free(void *ptr) { InternalAllocGuard guard; if(memleakfinder_global_enable && memleakfinder_initialised) { // Check it's been allocated std::map::iterator i(sMallocBlocks.find(ptr)); if(i != sMallocBlocks.end()) { sMallocBlocks.erase(i); } else { BOX_WARNING("Block " << ptr << " freed, but not " "known. Error? Or allocated in startup " "static allocation?"); } if(sTrackMallocInSection) { std::set::iterator si(sSectionMallocBlocks.find(ptr)); if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); } } //TRACE1("free(), %08x\n", ptr); std::free(ptr); } void memleakfinder_notaleak_insert_pre() { InternalAllocGuard guard; if(!memleakfinder_global_enable) return; if(!memleakfinder_initialised) return; for(size_t l = 0; l < sNotLeaksPreNum; l++) { sNotLeaks.insert(sNotLeaksPre[l]); } sNotLeaksPreNum = 0; } bool is_leak(void *ptr) { InternalAllocGuard guard; ASSERT(memleakfinder_initialised); memleakfinder_notaleak_insert_pre(); return sNotLeaks.find(ptr) == sNotLeaks.end(); } void memleakfinder_notaleak(void *ptr) { InternalAllocGuard guard; ASSERT(!sTrackingDataDestroyed); memleakfinder_notaleak_insert_pre(); if(memleakfinder_global_enable && memleakfinder_initialised) { sNotLeaks.insert(ptr); } else { if ( sNotLeaksPreNum < sizeof(sNotLeaksPre)/sizeof(*sNotLeaksPre) ) sNotLeaksPre[sNotLeaksPreNum++] = ptr; } /* { std::map::iterator i(sMallocBlocks.find(ptr)); if(i != sMallocBlocks.end()) sMallocBlocks.erase(i); } { std::set::iterator si(sSectionMallocBlocks.find(ptr)); if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); } { std::map::iterator i(sObjectBlocks.find(ptr)); if(i != sObjectBlocks.end()) sObjectBlocks.erase(i); }*/ } // start monitoring a section of code void memleakfinder_startsectionmonitor() { InternalAllocGuard guard; ASSERT(memleakfinder_initialised); ASSERT(!sTrackingDataDestroyed); sTrackMallocInSection = true; sSectionMallocBlocks.clear(); sTrackObjectsInSection = true; sSectionObjectBlocks.clear(); } // trace all blocks allocated and still allocated since memleakfinder_startsectionmonitor() called void memleakfinder_traceblocksinsection() { InternalAllocGuard guard; ASSERT(memleakfinder_initialised); ASSERT(!sTrackingDataDestroyed); std::set::iterator s(sSectionMallocBlocks.begin()); for(; s != sSectionMallocBlocks.end(); ++s) { std::map::const_iterator i(sMallocBlocks.find(*s)); if(i == sMallocBlocks.end()) { BOX_WARNING("Logical error in section block finding"); } else { BOX_TRACE("Block " << i->first << " size " << i->second.size << " allocated at " << i->second.file << ":" << i->second.line); } } for(std::map::const_iterator i(sSectionObjectBlocks.begin()); i != sSectionObjectBlocks.end(); ++i) { BOX_TRACE("Object" << (i->second.array?" []":"") << " " << i->first << " size " << i->second.size << " allocated at " << i->second.file << ":" << i->second.line); } } int memleakfinder_numleaks() { InternalAllocGuard guard; ASSERT(memleakfinder_initialised); ASSERT(!sTrackingDataDestroyed); int n = 0; for(std::map::const_iterator i(sMallocBlocks.begin()); i != sMallocBlocks.end(); ++i) { if(is_leak(i->first)) ++n; } for(std::map::const_iterator i(sObjectBlocks.begin()); i != sObjectBlocks.end(); ++i) { const ObjectInfo& rInfo = i->second; if(is_leak(i->first)) ++n; } return n; } void memleakfinder_reportleaks_file(FILE *file) { InternalAllocGuard guard; ASSERT(!sTrackingDataDestroyed); for(std::map::const_iterator i(sMallocBlocks.begin()); i != sMallocBlocks.end(); ++i) { if(is_leak(i->first)) { ::fprintf(file, "Block %p size %d allocated at " "%s:%d\n", i->first, i->second.size, i->second.file, i->second.line); } } for(std::map::const_iterator i(sObjectBlocks.begin()); i != sObjectBlocks.end(); ++i) { if(is_leak(i->first)) { ::fprintf(file, "Object%s %p size %d allocated at " "%s:%d\n", i->second.array?" []":"", i->first, i->second.size, i->second.file, i->second.line); } } } void memleakfinder_reportleaks() { InternalAllocGuard guard; // report to stdout memleakfinder_reportleaks_file(stdout); } void memleakfinder_reportleaks_appendfile(const char *filename, const char *markertext) { InternalAllocGuard guard; FILE *file = ::fopen(filename, "a"); if(file != 0) { if(memleakfinder_numleaks() > 0) { #ifdef HAVE_GETPID fprintf(file, "MEMORY LEAKS FROM PROCESS %d (%s)\n", getpid(), markertext); #else fprintf(file, "MEMORY LEAKS (%s)\n", markertext); #endif memleakfinder_reportleaks_file(file); } ::fclose(file); } else { BOX_WARNING("Couldn't open memory leak results file " << filename << " for appending"); } } static char atexit_filename[512]; static char atexit_markertext[512]; static bool atexit_registered = false; extern "C" void memleakfinder_atexit() { memleakfinder_reportleaks_appendfile(atexit_filename, atexit_markertext); } void memleakfinder_setup_exit_report(const char *filename, const char *markertext) { ::strncpy(atexit_filename, filename, sizeof(atexit_filename)-1); ::strncpy(atexit_markertext, markertext, sizeof(atexit_markertext)-1); atexit_filename[sizeof(atexit_filename)-1] = 0; atexit_markertext[sizeof(atexit_markertext)-1] = 0; if(!atexit_registered) { std::atexit(memleakfinder_atexit); atexit_registered = true; } } void add_object_block(void *block, size_t size, const char *file, int line, bool array) { InternalAllocGuard guard; if(!memleakfinder_global_enable) return; if(!memleakfinder_initialised) return; ASSERT(!sTrackingDataDestroyed); if(block != 0) { ObjectInfo i; i.size = size; i.file = file; i.line = line; i.array = array; sObjectBlocks[block] = i; if(sTrackObjectsInSection) { sSectionObjectBlocks[block] = i; } } } void remove_object_block(void *block) { InternalAllocGuard guard; if(!memleakfinder_global_enable) return; if(!memleakfinder_initialised) return; if(sTrackingDataDestroyed) return; std::map::iterator i(sObjectBlocks.find(block)); if(i != sObjectBlocks.end()) { sObjectBlocks.erase(i); } if(sTrackObjectsInSection) { std::map::iterator i(sSectionObjectBlocks.find(block)); if(i != sSectionObjectBlocks.end()) { sSectionObjectBlocks.erase(i); } } // If it's not in the list, just ignore it, as lots of stuff goes this way... } static void *internal_new(size_t size, const char *file, int line) { void *r; { InternalAllocGuard guard; r = std::malloc(size); } if (sInternalAllocDepth == 0) { InternalAllocGuard guard; add_object_block(r, size, file, line, false); //TRACE4("new(), %d, %s, %d, %08x\n", size, file, line, r); } return r; } void *operator new(size_t size, const char *file, int line) { return internal_new(size, file, line); } void *operator new[](size_t size, const char *file, int line) { return internal_new(size, file, line); } // where there is no doctor... need to override standard new() too // http://www.relisoft.com/book/tech/9new.html // disabled because it causes hangs on FC2 in futex() in test/common // while reading files. reason unknown. /* void *operator new(size_t size) { return internal_new(size, "standard libraries", 0); } */ void *operator new[](size_t size) { return internal_new(size, "standard libraries", 0); } void internal_delete(void *ptr) { InternalAllocGuard guard; std::free(ptr); remove_object_block(ptr); //TRACE1("delete[]() called, %08x\n", ptr); } void operator delete[](void *ptr) throw () { internal_delete(ptr); } void operator delete(void *ptr) throw () { internal_delete(ptr); } #endif // BOX_RELEASE_BUILD boxbackup/lib/common/BoxTime.cpp0000664000175000017500000000373411163700314017426 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxTime.cpp // Purpose: Time for the box // Created: 2003/10/08 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_TIME_H #include #endif #include #include #include "BoxTime.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: GetCurrentBoxTime() // Purpose: Returns the current time as a box time. // (1 sec precision, or better if supported by system) // Created: 2003/10/08 // // -------------------------------------------------------------------------- box_time_t GetCurrentBoxTime() { #ifdef HAVE_GETTIMEOFDAY struct timeval tv; if (gettimeofday(&tv, NULL) != 0) { BOX_LOG_SYS_ERROR("Failed to gettimeofday(), " "dropping precision"); } else { box_time_t timeNow = (tv.tv_sec * MICRO_SEC_IN_SEC_LL) + tv.tv_usec; return timeNow; } #endif return SecondsToBoxTime(time(0)); } std::string FormatTime(box_time_t time, bool includeDate, bool showMicros) { std::ostringstream buf; time_t seconds = BoxTimeToSeconds(time); int micros = BoxTimeToMicroSeconds(time) % MICRO_SEC_IN_SEC; struct tm tm_now, *tm_ptr = &tm_now; #ifdef WIN32 if ((tm_ptr = localtime(&seconds)) != NULL) #else if (localtime_r(&seconds, &tm_now) != NULL) #endif { buf << std::setfill('0'); if (includeDate) { buf << std::setw(4) << (tm_ptr->tm_year + 1900) << "-" << std::setw(2) << (tm_ptr->tm_mon + 1) << "-" << std::setw(2) << (tm_ptr->tm_mday) << " "; } buf << std::setw(2) << tm_ptr->tm_hour << ":" << std::setw(2) << tm_ptr->tm_min << ":" << std::setw(2) << tm_ptr->tm_sec; if (showMicros) { buf << "." << std::setw(6) << micros; } } else { buf << strerror(errno); } return buf.str(); } boxbackup/lib/common/Logging.cpp0000664000175000017500000002174111345271627017457 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Logging.cpp // Purpose: Generic logging core routines implementation // Created: 2006/12/16 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include // for stderror // c.f. http://bugs.debian.org/512510 #include #ifdef HAVE_SYSLOG_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include #include #include "BoxTime.h" #include "Logging.h" bool Logging::sLogToSyslog = false; bool Logging::sLogToConsole = false; bool Logging::sContextSet = false; bool HideExceptionMessageGuard::sHiddenState = false; std::vector Logging::sLoggers; std::string Logging::sContext; Console* Logging::spConsole = NULL; Syslog* Logging::spSyslog = NULL; Log::Level Logging::sGlobalLevel = Log::EVERYTHING; Logging Logging::sGlobalLogging; //automatic initialisation std::string Logging::sProgramName; Logging::Logging() { ASSERT(!spConsole); ASSERT(!spSyslog); spConsole = new Console(); spSyslog = new Syslog(); sLogToConsole = true; sLogToSyslog = true; } Logging::~Logging() { sLogToConsole = false; sLogToSyslog = false; delete spConsole; delete spSyslog; spConsole = NULL; spSyslog = NULL; } void Logging::ToSyslog(bool enabled) { if (!sLogToSyslog && enabled) { Add(spSyslog); } if (sLogToSyslog && !enabled) { Remove(spSyslog); } sLogToSyslog = enabled; } void Logging::ToConsole(bool enabled) { if (!sLogToConsole && enabled) { Add(spConsole); } if (sLogToConsole && !enabled) { Remove(spConsole); } sLogToConsole = enabled; } void Logging::FilterConsole(Log::Level level) { spConsole->Filter(level); } void Logging::FilterSyslog(Log::Level level) { spSyslog->Filter(level); } void Logging::Add(Logger* pNewLogger) { for (std::vector::iterator i = sLoggers.begin(); i != sLoggers.end(); i++) { if (*i == pNewLogger) { return; } } sLoggers.insert(sLoggers.begin(), pNewLogger); } void Logging::Remove(Logger* pOldLogger) { for (std::vector::iterator i = sLoggers.begin(); i != sLoggers.end(); i++) { if (*i == pOldLogger) { sLoggers.erase(i); return; } } } void Logging::Log(Log::Level level, const std::string& rFile, int line, const std::string& rMessage) { if (level > sGlobalLevel) { return; } std::string newMessage; if (sContextSet) { newMessage += "[" + sContext + "] "; } newMessage += rMessage; for (std::vector::iterator i = sLoggers.begin(); i != sLoggers.end(); i++) { bool result = (*i)->Log(level, rFile, line, newMessage); if (!result) { return; } } } void Logging::LogToSyslog(Log::Level level, const std::string& rFile, int line, const std::string& rMessage) { if (!sLogToSyslog) { return; } if (level > sGlobalLevel) { return; } std::string newMessage; if (sContextSet) { newMessage += "[" + sContext + "] "; } newMessage += rMessage; spSyslog->Log(level, rFile, line, newMessage); } void Logging::SetContext(std::string context) { sContext = context; sContextSet = true; } Log::Level Logging::GetNamedLevel(const std::string& rName) { if (rName == "nothing") { return Log::NOTHING; } else if (rName == "fatal") { return Log::FATAL; } else if (rName == "error") { return Log::ERROR; } else if (rName == "warning") { return Log::WARNING; } else if (rName == "notice") { return Log::NOTICE; } else if (rName == "info") { return Log::INFO; } else if (rName == "trace") { return Log::TRACE; } else if (rName == "everything") { return Log::EVERYTHING; } else { BOX_ERROR("Unknown verbosity level: " << rName); return Log::INVALID; } } void Logging::ClearContext() { sContextSet = false; } void Logging::SetProgramName(const std::string& rProgramName) { sProgramName = rProgramName; for (std::vector::iterator i = sLoggers.begin(); i != sLoggers.end(); i++) { (*i)->SetProgramName(rProgramName); } } void Logging::SetFacility(int facility) { spSyslog->SetFacility(facility); } Logger::Logger() : mCurrentLevel(Log::EVERYTHING) { Logging::Add(this); } Logger::Logger(Log::Level Level) : mCurrentLevel(Level) { Logging::Add(this); } Logger::~Logger() { Logging::Remove(this); } bool Console::sShowTime = false; bool Console::sShowTimeMicros = false; bool Console::sShowTag = false; bool Console::sShowPID = false; std::string Console::sTag; void Console::SetProgramName(const std::string& rProgramName) { sTag = rProgramName; } void Console::SetShowTag(bool enabled) { sShowTag = enabled; } void Console::SetShowTime(bool enabled) { sShowTime = enabled; } void Console::SetShowTimeMicros(bool enabled) { sShowTimeMicros = enabled; } void Console::SetShowPID(bool enabled) { sShowPID = enabled; } bool Console::Log(Log::Level level, const std::string& rFile, int line, std::string& rMessage) { if (level > GetLevel()) { return true; } FILE* target = stdout; if (level <= Log::WARNING) { target = stderr; } std::ostringstream buf; if (sShowTime) { buf << FormatTime(GetCurrentBoxTime(), false, sShowTimeMicros); buf << " "; } if (sShowTag) { if (sShowPID) { buf << "[" << sTag << " " << getpid() << "] "; } else { buf << "[" << sTag << "] "; } } else if (sShowPID) { buf << "[" << getpid() << "] "; } if (level <= Log::FATAL) { buf << "FATAL: "; } else if (level <= Log::ERROR) { buf << "ERROR: "; } else if (level <= Log::WARNING) { buf << "WARNING: "; } else if (level <= Log::NOTICE) { buf << "NOTICE: "; } else if (level <= Log::INFO) { buf << "INFO: "; } else if (level <= Log::TRACE) { buf << "TRACE: "; } buf << rMessage; #ifdef WIN32 std::string output = buf.str(); ConvertUtf8ToConsole(output.c_str(), output); fprintf(target, "%s\n", output.c_str()); #else fprintf(target, "%s\n", buf.str().c_str()); #endif return true; } bool Syslog::Log(Log::Level level, const std::string& rFile, int line, std::string& rMessage) { if (level > GetLevel()) { return true; } int syslogLevel = LOG_ERR; switch(level) { case Log::NOTHING: /* fall through */ case Log::INVALID: /* fall through */ case Log::FATAL: syslogLevel = LOG_CRIT; break; case Log::ERROR: syslogLevel = LOG_ERR; break; case Log::WARNING: syslogLevel = LOG_WARNING; break; case Log::NOTICE: syslogLevel = LOG_NOTICE; break; case Log::INFO: syslogLevel = LOG_INFO; break; case Log::TRACE: /* fall through */ case Log::EVERYTHING: syslogLevel = LOG_DEBUG; break; } std::string msg; if (level <= Log::FATAL) { msg = "FATAL: "; } else if (level <= Log::ERROR) { msg = "ERROR: "; } else if (level <= Log::WARNING) { msg = "WARNING: "; } else if (level <= Log::NOTICE) { msg = "NOTICE: "; } msg += rMessage; syslog(syslogLevel, "%s", msg.c_str()); return true; } Syslog::Syslog() : mFacility(LOG_LOCAL6) { ::openlog("Box Backup", LOG_PID, mFacility); } Syslog::~Syslog() { ::closelog(); } void Syslog::SetProgramName(const std::string& rProgramName) { mName = rProgramName; ::closelog(); ::openlog(mName.c_str(), LOG_PID, mFacility); } void Syslog::SetFacility(int facility) { mFacility = facility; ::closelog(); ::openlog(mName.c_str(), LOG_PID, mFacility); } int Syslog::GetNamedFacility(const std::string& rFacility) { #define CASE_RETURN(x) if (rFacility == #x) { return LOG_ ## x; } CASE_RETURN(LOCAL0) CASE_RETURN(LOCAL1) CASE_RETURN(LOCAL2) CASE_RETURN(LOCAL3) CASE_RETURN(LOCAL4) CASE_RETURN(LOCAL5) CASE_RETURN(LOCAL6) CASE_RETURN(DAEMON) #undef CASE_RETURN BOX_ERROR("Unknown log facility '" << rFacility << "', " "using default LOCAL6"); return LOG_LOCAL6; } bool FileLogger::Log(Log::Level Level, const std::string& rFile, int line, std::string& rMessage) { if (Level > GetLevel()) { return true; } /* avoid infinite loop if this throws an exception */ Logging::Remove(this); std::ostringstream buf; buf << FormatTime(GetCurrentBoxTime(), true, false); buf << " "; if (Level <= Log::FATAL) { buf << "[FATAL] "; } else if (Level <= Log::ERROR) { buf << "[ERROR] "; } else if (Level <= Log::WARNING) { buf << "[WARNING] "; } else if (Level <= Log::NOTICE) { buf << "[NOTICE] "; } else if (Level <= Log::INFO) { buf << "[INFO] "; } else if (Level <= Log::TRACE) { buf << "[TRACE] "; } buf << rMessage << "\n"; std::string output = buf.str(); #ifdef WIN32 ConvertUtf8ToConsole(output.c_str(), output); #endif mLogFile.Write(output.c_str(), output.length()); Logging::Add(this); return true; } std::string PrintEscapedBinaryData(const std::string& rInput) { std::ostringstream output; for (size_t i = 0; i < rInput.length(); i++) { if (isprint(rInput[i])) { output << rInput[i]; } else { output << "\\x" << std::hex << std::setw(2) << std::setfill('0') << (int) rInput[i] << std::dec; } } return output.str(); } boxbackup/lib/common/BufferedWriteStream.h0000664000175000017500000000222411443463447021444 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BufferedWriteStream.h // Purpose: Buffering write-only wrapper around IOStreams // Created: 2010/09/13 // // -------------------------------------------------------------------------- #ifndef BUFFEREDWRITESTREAM__H #define BUFFEREDWRITESTREAM__H #include "IOStream.h" class BufferedWriteStream : public IOStream { private: IOStream& mrSink; char mBuffer[4096]; int mBufferPosition; public: BufferedWriteStream(IOStream& rSource); virtual ~BufferedWriteStream() { Close(); } virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(IOStream::pos_type Offset, int SeekType); virtual void Flush(int Timeout = IOStream::TimeOutInfinite); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: BufferedWriteStream(const BufferedWriteStream &rToCopy) : mrSink(rToCopy.mrSink) { /* do not call */ } }; #endif // BUFFEREDWRITESTREAM__H boxbackup/lib/common/ReadGatherStream.cpp0000664000175000017500000001605610614700276021251 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ReadGatherStream.cpp // Purpose: Build a stream (for reading only) out of a number of other streams. // Created: 10/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include "ReadGatherStream.h" #include "CommonException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::ReadGatherStream(bool) // Purpose: Constructor. Args says whether or not all the component streams will be deleted when this // object is deleted. // Created: 10/12/03 // // -------------------------------------------------------------------------- ReadGatherStream::ReadGatherStream(bool DeleteComponentStreamsOnDestruction) : mDeleteComponentStreamsOnDestruction(DeleteComponentStreamsOnDestruction), mCurrentPosition(0), mTotalSize(0), mCurrentBlock(0), mPositionInCurrentBlock(0), mSeekDoneForCurrent(false) { } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::~ReadGatherStream() // Purpose: Destructor. Will delete all the stream objects, if required. // Created: 10/12/03 // // -------------------------------------------------------------------------- ReadGatherStream::~ReadGatherStream() { // Delete compoenent streams? if(mDeleteComponentStreamsOnDestruction) { for(unsigned int l = 0; l < mComponents.size(); ++l) { delete mComponents[l]; } } } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::AddComponent(IOStream *) // Purpose: Add a component to this stream, returning the index // of this component in the internal list. Use this // with AddBlock() // Created: 10/12/03 // // -------------------------------------------------------------------------- int ReadGatherStream::AddComponent(IOStream *pStream) { ASSERT(pStream != 0); // Just add the component to the list, returning it's index. int index = mComponents.size(); mComponents.push_back(pStream); return index; } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::AddBlock(int, pos_type, bool, pos_type) // Purpose: Add a block to the list of blocks being gathered into one stream. // Length is length of block to read from this component, Seek == true // if a seek is required, and if true, SeekTo is the position (absolute) // in the stream to be seeked to when this block is required. // Created: 10/12/03 // // -------------------------------------------------------------------------- void ReadGatherStream::AddBlock(int Component, pos_type Length, bool Seek, pos_type SeekTo) { // Check block if(Component < 0 || Component >= (int)mComponents.size() || Length < 0 || SeekTo < 0) { THROW_EXCEPTION(CommonException, ReadGatherStreamAddingBadBlock); } // Add to list Block b; b.mLength = Length; b.mSeekTo = SeekTo; b.mComponent = Component; b.mSeek = Seek; mBlocks.push_back(b); // And update the total size mTotalSize += Length; } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::Read(void *, int, int) // Purpose: As interface. // Created: 10/12/03 // // -------------------------------------------------------------------------- int ReadGatherStream::Read(void *pBuffer, int NBytes, int Timeout) { int bytesToRead = NBytes; uint8_t *buffer = (uint8_t*)pBuffer; while(bytesToRead > 0) { // Done? if(mCurrentBlock >= mBlocks.size()) { // Stop now, as have finished the last block return NBytes - bytesToRead; } // Seek? if(mPositionInCurrentBlock == 0 && mBlocks[mCurrentBlock].mSeek && !mSeekDoneForCurrent) { // Do seeks in this manner so that seeks are done regardless of whether the block // has length > 0, and it will only be done once, and at as late a stage as possible. mComponents[mBlocks[mCurrentBlock].mComponent]->Seek(mBlocks[mCurrentBlock].mSeekTo, IOStream::SeekType_Absolute); mSeekDoneForCurrent = true; } // Anything in the current block? if(mPositionInCurrentBlock < mBlocks[mCurrentBlock].mLength) { // Read! pos_type s = mBlocks[mCurrentBlock].mLength - mPositionInCurrentBlock; if(s > bytesToRead) s = bytesToRead; pos_type r = mComponents[mBlocks[mCurrentBlock].mComponent]->Read(buffer, s, Timeout); // update variables mPositionInCurrentBlock += r; buffer += r; bytesToRead -= r; mCurrentPosition += r; if(r != s) { // Stream returned less than requested. To avoid blocking when not necessary, // return now. return NBytes - bytesToRead; } } else { // Move to next block ++mCurrentBlock; mPositionInCurrentBlock = 0; mSeekDoneForCurrent = false; } } return NBytes - bytesToRead; } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::GetPosition() // Purpose: As interface // Created: 10/12/03 // // -------------------------------------------------------------------------- IOStream::pos_type ReadGatherStream::GetPosition() const { return mCurrentPosition; } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::BytesLeftToRead() // Purpose: As interface // Created: 10/12/03 // // -------------------------------------------------------------------------- IOStream::pos_type ReadGatherStream::BytesLeftToRead() { return mTotalSize - mCurrentPosition; } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::Write(const void *, int) // Purpose: As interface. // Created: 10/12/03 // // -------------------------------------------------------------------------- void ReadGatherStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(CommonException, CannotWriteToReadGatherStream); } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::StreamDataLeft() // Purpose: As interface. // Created: 10/12/03 // // -------------------------------------------------------------------------- bool ReadGatherStream::StreamDataLeft() { if(mCurrentBlock >= mBlocks.size()) { // Done all the blocks return false; } if(mCurrentBlock == (mBlocks.size() - 1) && mPositionInCurrentBlock >= mBlocks[mCurrentBlock].mLength) { // Are on the last block, and have got all the data from it. return false; } // Otherwise, there's more data to be read return true; } // -------------------------------------------------------------------------- // // Function // Name: ReadGatherStream::StreamClosed() // Purpose: As interface. But the stream is always closed. // Created: 10/12/03 // // -------------------------------------------------------------------------- bool ReadGatherStream::StreamClosed() { return true; } boxbackup/lib/common/Test.h0000664000175000017500000001220511224217377016446 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Test.h // Purpose: Useful stuff for tests // Created: 2003/07/11 // // -------------------------------------------------------------------------- #ifndef TEST__H #define TEST__H #include #ifdef WIN32 #define BBACKUPCTL "..\\..\\bin\\bbackupctl\\bbackupctl.exe" #define BBACKUPD "..\\..\\bin\\bbackupd\\bbackupd.exe" #define BBSTORED "..\\..\\bin\\bbstored\\bbstored.exe" #define BBACKUPQUERY "..\\..\\bin\\bbackupquery\\bbackupquery.exe" #define BBSTOREACCOUNTS "..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts.exe" #define TEST_RETURN(actual, expected) TEST_EQUAL(expected, actual); #else #define BBACKUPCTL "../../bin/bbackupctl/bbackupctl" #define BBACKUPD "../../bin/bbackupd/bbackupd" #define BBSTORED "../../bin/bbstored/bbstored" #define BBACKUPQUERY "../../bin/bbackupquery/bbackupquery" #define BBSTOREACCOUNTS "../../bin/bbstoreaccounts/bbstoreaccounts" #define TEST_RETURN(actual, expected) TEST_EQUAL((expected << 8), actual); #endif extern int failures; extern int first_fail_line; extern std::string first_fail_file; extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args; #define TEST_FAIL_WITH_MESSAGE(msg) \ { \ if (failures == 0) \ { \ first_fail_file = __FILE__; \ first_fail_line = __LINE__; \ } \ failures++; \ BOX_ERROR("**** TEST FAILURE: " << msg << " at " << __FILE__ << \ ":" << __LINE__); \ } #define TEST_ABORT_WITH_MESSAGE(msg) {TEST_FAIL_WITH_MESSAGE(msg); return 1;} #define TEST_THAT(condition) {if(!(condition)) TEST_FAIL_WITH_MESSAGE("Condition [" #condition "] failed")} #define TEST_THAT_ABORTONFAIL(condition) {if(!(condition)) TEST_ABORT_WITH_MESSAGE("Condition [" #condition "] failed")} // NOTE: The 0- bit is to allow this to work with stuff which has negative constants for flags (eg ConnectionException) #define TEST_CHECK_THROWS(statement, excepttype, subtype) \ { \ bool didthrow = false; \ HideExceptionMessageGuard hide; \ try \ { \ statement; \ } \ catch(excepttype &e) \ { \ if(e.GetSubType() != ((unsigned int)excepttype::subtype) \ && e.GetSubType() != (unsigned int)(0-excepttype::subtype)) \ { \ throw; \ } \ didthrow = true; \ } \ catch(...) \ { \ throw; \ } \ if(!didthrow) \ { \ TEST_FAIL_WITH_MESSAGE("Didn't throw exception " #excepttype "(" #subtype ")") \ } \ } // utility macro for comparing two strings in a line #define TEST_EQUAL(_expected, _found) \ { \ std::ostringstream _oss1; \ _oss1 << _expected; \ std::string _exp_str = _oss1.str(); \ \ std::ostringstream _oss2; \ _oss2 << _found; \ std::string _found_str = _oss2.str(); \ \ if(_exp_str != _found_str) \ { \ BOX_WARNING("Expected <" << _exp_str << "> but found <" << \ _found_str << ">"); \ \ std::ostringstream _oss3; \ _oss3 << #_found << " != " << #_expected; \ \ TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \ } \ } // utility macro for comparing two strings in a line #define TEST_EQUAL_LINE(_expected, _found, _line) \ { \ std::ostringstream _oss1; \ _oss1 << _expected; \ std::string _exp_str = _oss1.str(); \ \ std::ostringstream _oss2; \ _oss2 << _found; \ std::string _found_str = _oss2.str(); \ \ if(_exp_str != _found_str) \ { \ std::ostringstream _ossl; \ _ossl << _line; \ std::string _line_str = _ossl.str(); \ printf("Expected <%s> but found <%s> in <%s>\n", \ _exp_str.c_str(), _found_str.c_str(), _line_str.c_str()); \ \ std::ostringstream _oss3; \ _oss3 << #_found << " != " << #_expected << " in " << _line; \ \ TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \ } \ } // utility macro for testing a line #define TEST_LINE(_condition, _line) \ TEST_THAT(_condition); \ if (!(_condition)) \ { \ printf("Test failed on <%s>\n", _line.c_str()); \ } bool TestFileExists(const char *Filename); bool TestDirExists(const char *Filename); // -1 if doesn't exist int TestGetFileSize(const char *Filename); std::string ConvertPaths(const std::string& rOriginal); int RunCommand(const std::string& rCommandLine); bool ServerIsAlive(int pid); int ReadPidFile(const char *pidFile); int LaunchServer(const std::string& rCommandLine, const char *pidFile); int WaitForServerStartup(const char *pidFile, int pidIfKnown); #define TestRemoteProcessMemLeaks(filename) \ TestRemoteProcessMemLeaksFunc(filename, __FILE__, __LINE__) void TestRemoteProcessMemLeaksFunc(const char *filename, const char* file, int line); void force_sync(); void wait_for_sync_start(); void wait_for_sync_end(); void sync_and_wait(); void terminate_bbackupd(int pid); // Wait a given number of seconds for something to complete void wait_for_operation(int seconds, const char* message); void safe_sleep(int seconds); #endif // TEST__H boxbackup/lib/common/BeginStructPackForWire.h0000664000175000017500000000071310347400657022056 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BeginStructPackForWire.h // Purpose: Begin structure packing for wire // Created: 25/11/03 // // -------------------------------------------------------------------------- // No header guard -- this is intentional #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #pragma pack(1) #else logical error -- check BoxPlatform.h and including file #endif boxbackup/lib/common/FileModificationTime.cpp0000664000175000017500000000411511345271627022111 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: FileModificationTime.cpp // Purpose: Function for getting file modification time. // Created: 2010/02/15 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BoxTime.h" #include "FileModificationTime.h" #include "MemLeakFindOn.h" box_time_t FileModificationTime(EMU_STRUCT_STAT &st) { #ifndef HAVE_STRUCT_STAT_ST_MTIMESPEC box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL); #else box_time_t datamodified = (((int64_t)st.st_mtimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + (((int64_t)st.st_mtimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); #endif return datamodified; } box_time_t FileAttrModificationTime(EMU_STRUCT_STAT &st) { box_time_t statusmodified = #ifdef HAVE_STRUCT_STAT_ST_MTIMESPEC (((int64_t)st.st_ctimespec.tv_nsec) / (NANO_SEC_IN_USEC_LL)) + (((int64_t)st.st_ctimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); #elif defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC (((int64_t)st.st_ctim.tv_nsec) / (NANO_SEC_IN_USEC_LL)) + (((int64_t)st.st_ctim.tv_sec) * (MICRO_SEC_IN_SEC_LL)); #elif defined HAVE_STRUCT_STAT_ST_ATIMENSEC (((int64_t)st.st_ctimensec) / (NANO_SEC_IN_USEC_LL)) + (((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL)); #else // no nanoseconds anywhere (((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL)); #endif return statusmodified; } box_time_t FileModificationTimeMaxModAndAttr(EMU_STRUCT_STAT &st) { #ifndef HAVE_STRUCT_STAT_ST_MTIMESPEC box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL); box_time_t statusmodified = ((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL); #else box_time_t datamodified = (((int64_t)st.st_mtimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + (((int64_t)st.st_mtimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); box_time_t statusmodified = (((int64_t)st.st_ctimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + (((int64_t)st.st_ctimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); #endif return (datamodified > statusmodified)?datamodified:statusmodified; } boxbackup/lib/common/StreamableMemBlock.h0000664000175000017500000000332210347400657021220 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: StreamableMemBlock.h // Purpose: Memory blocks which can be loaded and saved from streams // Created: 2003/09/05 // // -------------------------------------------------------------------------- #ifndef STREAMABLEMEMBLOCK__H #define STREAMABLEMEMBLOCK__H class IOStream; // -------------------------------------------------------------------------- // // Class // Name: StreamableMemBlock // Purpose: Memory blocks which can be loaded and saved from streams // Created: 2003/09/05 // // -------------------------------------------------------------------------- class StreamableMemBlock { public: StreamableMemBlock(); StreamableMemBlock(int Size); StreamableMemBlock(void *pBuffer, int Size); StreamableMemBlock(const StreamableMemBlock &rToCopy); ~StreamableMemBlock(); void Set(const StreamableMemBlock &rBlock); void Set(void *pBuffer, int Size); void Set(IOStream &rStream, int Timeout); StreamableMemBlock &operator=(const StreamableMemBlock &rBlock) { Set(rBlock); return *this; } void ReadFromStream(IOStream &rStream, int Timeout); void WriteToStream(IOStream &rStream) const; static void WriteEmptyBlockToStream(IOStream &rStream); void *GetBuffer() const; // Size of block int GetSize() const {return mSize;} // Buffer empty? bool IsEmpty() const {return mSize == 0;} // Clear the contents of the block void Clear() {FreeBlock();} bool operator==(const StreamableMemBlock &rCompare) const; void ResizeBlock(int Size); protected: // be careful with these! void AllocateBlock(int Size); void FreeBlock(); private: void *mpBuffer; int mSize; }; #endif // STREAMABLEMEMBLOCK__H boxbackup/lib/common/SelfFlushingStream.h0000664000175000017500000000313711053246002021263 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SelfFlushingStream.h // Purpose: A stream wrapper that always flushes the underlying // stream, to ensure protocol safety. // Created: 2008/08/20 // // -------------------------------------------------------------------------- #ifndef SELFFLUSHINGSTREAM__H #define SELFFLUSHINGSTREAM__H #include "IOStream.h" // -------------------------------------------------------------------------- // // Class // Name: SelfFlushingStream // Purpose: A stream wrapper that always flushes the underlying // stream, to ensure protocol safety. // Created: 2008/08/20 // // -------------------------------------------------------------------------- class SelfFlushingStream : public IOStream { public: SelfFlushingStream(IOStream &rSource) : mrSource(rSource) { } SelfFlushingStream(const SelfFlushingStream &rToCopy) : mrSource(rToCopy.mrSource) { } ~SelfFlushingStream() { Flush(); } private: // no copying from IOStream allowed SelfFlushingStream(const IOStream& rToCopy); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) { return mrSource.Read(pBuffer, NBytes, Timeout); } virtual pos_type BytesLeftToRead() { return mrSource.BytesLeftToRead(); } virtual void Write(const void *pBuffer, int NBytes) { mrSource.Write(pBuffer, NBytes); } virtual bool StreamDataLeft() { return mrSource.StreamDataLeft(); } virtual bool StreamClosed() { return mrSource.StreamClosed(); } private: IOStream &mrSource; }; #endif // SELFFLUSHINGSTREAM__H boxbackup/lib/common/CollectInBufferStream.h0000664000175000017500000000304510347400657021713 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CollectInBufferStream.h // Purpose: Collect data in a buffer, and then read it out. // Created: 2003/08/26 // // -------------------------------------------------------------------------- #ifndef COLLECTINBUFFERSTREAM__H #define COLLECTINBUFFERSTREAM__H #include "IOStream.h" #include "Guards.h" // -------------------------------------------------------------------------- // // Class // Name: CollectInBufferStream // Purpose: Collect data in a buffer, and then read it out. // Created: 2003/08/26 // // -------------------------------------------------------------------------- class CollectInBufferStream : public IOStream { public: CollectInBufferStream(); ~CollectInBufferStream(); private: // No copying CollectInBufferStream(const CollectInBufferStream &); CollectInBufferStream(const IOStream &); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(pos_type Offset, int SeekType); virtual bool StreamDataLeft(); virtual bool StreamClosed(); void SetForReading(); void Reset(); void *GetBuffer() const; int GetSize() const; bool IsSetForReading() const {return !mInWritePhase;} private: MemoryBlockGuard mBuffer; int mBufferSize; int mBytesInBuffer; int mReadPosition; bool mInWritePhase; }; #endif // COLLECTINBUFFERSTREAM__H boxbackup/lib/common/CollectInBufferStream.cpp0000664000175000017500000001711110347400657022245 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: CollectInBufferStream.cpp // Purpose: Collect data in a buffer, and then read it out. // Created: 2003/08/26 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "CollectInBufferStream.h" #include "CommonException.h" #include "MemLeakFindOn.h" #define INITIAL_BUFFER_SIZE 1024 #define MAX_BUFFER_ADDITION (1024*64) // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::CollectInBufferStream() // Purpose: Constructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- CollectInBufferStream::CollectInBufferStream() : mBuffer(INITIAL_BUFFER_SIZE), mBufferSize(INITIAL_BUFFER_SIZE), mBytesInBuffer(0), mReadPosition(0), mInWritePhase(true) { } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::~CollectInBufferStream() // Purpose: Destructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- CollectInBufferStream::~CollectInBufferStream() { } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::Read(void *, int, int) // Purpose: As interface. But only works in read phase // Created: 2003/08/26 // // -------------------------------------------------------------------------- int CollectInBufferStream::Read(void *pBuffer, int NBytes, int Timeout) { if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } // Adjust to number of bytes left if(NBytes > (mBytesInBuffer - mReadPosition)) { NBytes = (mBytesInBuffer - mReadPosition); } ASSERT(NBytes >= 0); if(NBytes <= 0) return 0; // careful now // Copy in the requested number of bytes and adjust the read pointer ::memcpy(pBuffer, ((char*)mBuffer) + mReadPosition, NBytes); mReadPosition += NBytes; return NBytes; } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::BytesLeftToRead() // Purpose: As interface. But only works in read phase // Created: 2003/08/26 // // -------------------------------------------------------------------------- IOStream::pos_type CollectInBufferStream::BytesLeftToRead() { if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } return (mBytesInBuffer - mReadPosition); } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::Write(void *, int) // Purpose: As interface. But only works in write phase // Created: 2003/08/26 // // -------------------------------------------------------------------------- void CollectInBufferStream::Write(const void *pBuffer, int NBytes) { if(mInWritePhase != true) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } // Enough space in the buffer if((mBytesInBuffer + NBytes) > mBufferSize) { // Need to reallocate... what's the block size we'll use? int allocateBlockSize = mBufferSize; if(allocateBlockSize > MAX_BUFFER_ADDITION) { allocateBlockSize = MAX_BUFFER_ADDITION; } // Write it the easy way. Although it's not the most efficient... int newSize = mBufferSize; while(newSize < (mBytesInBuffer + NBytes)) { newSize += allocateBlockSize; } // Reallocate buffer mBuffer.Resize(newSize); // Store new size mBufferSize = newSize; } // Copy in data and adjust counter ::memcpy(((char*)mBuffer) + mBytesInBuffer, pBuffer, NBytes); mBytesInBuffer += NBytes; } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::GetPosition() // Purpose: In write phase, returns the number of bytes written, in read // phase, the number of bytes to go // Created: 2003/08/26 // // -------------------------------------------------------------------------- IOStream::pos_type CollectInBufferStream::GetPosition() const { return mInWritePhase?mBytesInBuffer:mReadPosition; } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::Seek(pos_type, int) // Purpose: As interface. But read phase only. // Created: 2003/08/26 // // -------------------------------------------------------------------------- void CollectInBufferStream::Seek(pos_type Offset, int SeekType) { if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } int newPos = 0; switch(SeekType) { case IOStream::SeekType_Absolute: newPos = Offset; break; case IOStream::SeekType_Relative: newPos = mReadPosition + Offset; break; case IOStream::SeekType_End: newPos = mBytesInBuffer + Offset; break; default: THROW_EXCEPTION(CommonException, IOStreamBadSeekType) break; } // Make sure it doesn't go over if(newPos > mBytesInBuffer) { newPos = mBytesInBuffer; } // or under if(newPos < 0) { newPos = 0; } // Set the new read position mReadPosition = newPos; } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::StreamDataLeft() // Purpose: As interface // Created: 2003/08/26 // // -------------------------------------------------------------------------- bool CollectInBufferStream::StreamDataLeft() { return mInWritePhase?(false):(mReadPosition < mBytesInBuffer); } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::StreamClosed() // Purpose: As interface // Created: 2003/08/26 // // -------------------------------------------------------------------------- bool CollectInBufferStream::StreamClosed() { return !mInWritePhase; } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::SetForReading() // Purpose: Switch to read phase, after all data written // Created: 2003/08/26 // // -------------------------------------------------------------------------- void CollectInBufferStream::SetForReading() { if(mInWritePhase != true) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } // Move to read phase mInWritePhase = false; } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::GetBuffer() // Purpose: Returns the buffer // Created: 2003/09/05 // // -------------------------------------------------------------------------- void *CollectInBufferStream::GetBuffer() const { return mBuffer.GetPtr(); } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::GetSize() // Purpose: Returns the buffer size // Created: 2003/09/05 // // -------------------------------------------------------------------------- int CollectInBufferStream::GetSize() const { return mBytesInBuffer; } // -------------------------------------------------------------------------- // // Function // Name: CollectInBufferStream::Reset() // Purpose: Reset the stream, so it is empty and ready to be written to. // Created: 8/12/03 // // -------------------------------------------------------------------------- void CollectInBufferStream::Reset() { mInWritePhase = true; mBytesInBuffer = 0; mReadPosition = 0; } boxbackup/lib/common/MemBlockStream.h0000664000175000017500000000272310347400657020400 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: MemBlockStream.h // Purpose: Stream out data from any memory block // Created: 2003/09/05 // // -------------------------------------------------------------------------- #ifndef MEMBLOCKSTREAM__H #define MEMBLOCKSTREAM__H #include "IOStream.h" class StreamableMemBlock; class CollectInBufferStream; // -------------------------------------------------------------------------- // // Class // Name: MemBlockStream // Purpose: Stream out data from any memory block -- be careful the lifetime // of the block is greater than the lifetime of this stream. // Created: 2003/09/05 // // -------------------------------------------------------------------------- class MemBlockStream : public IOStream { public: MemBlockStream(const void *pBuffer, int Size); MemBlockStream(const StreamableMemBlock &rBlock); MemBlockStream(const CollectInBufferStream &rBuffer); MemBlockStream(const MemBlockStream &rToCopy); ~MemBlockStream(); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual pos_type GetPosition() const; virtual void Seek(pos_type Offset, int SeekType); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: const char *mpBuffer; int mBytesInBuffer; int mReadPosition; }; #endif // MEMBLOCKSTREAM__H boxbackup/lib/server/0000775000175000017500000000000011652362374015377 5ustar siretartsiretartboxbackup/lib/server/ServerStream.h0000664000175000017500000002234711325425667020204 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ServerStream.h // Purpose: Stream based server daemons // Created: 2003/07/31 // // -------------------------------------------------------------------------- #ifndef SERVERSTREAM__H #define SERVERSTREAM__H #include #include #ifndef WIN32 #include #endif #include "Daemon.h" #include "SocketListen.h" #include "Utils.h" #include "Configuration.h" #include "WaitForEvent.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Class // Name: ServerStream // Purpose: Stream based server daemon // Created: 2003/07/31 // // -------------------------------------------------------------------------- template class ServerStream : public Daemon { public: ServerStream() { } ~ServerStream() { DeleteSockets(); } private: ServerStream(const ServerStream &rToCopy) { } public: virtual const char *DaemonName() const { return "generic-stream-server"; } virtual void OnIdle() { } virtual void Run() { // Set process title as appropriate SetProcessTitle(ForkToHandleRequests?"server":"idle"); // Handle exceptions and child task quitting gracefully. bool childExit = false; try { Run2(childExit); } catch(BoxException &e) { if(childExit) { BOX_ERROR("Error in child process, " "terminating connection: exception " << e.what() << "(" << e.GetType() << "/" << e.GetSubType() << ")"); _exit(1); } else throw; } catch(std::exception &e) { if(childExit) { BOX_ERROR("Error in child process, " "terminating connection: exception " << e.what()); _exit(1); } else throw; } catch(...) { if(childExit) { BOX_ERROR("Error in child process, " "terminating connection: " "unknown exception"); _exit(1); } else throw; } // if it's a child fork, exit the process now if(childExit) { // Child task, dump leaks to trace, which we make sure is on #ifdef BOX_MEMORY_LEAK_TESTING #ifndef BOX_RELEASE_BUILD TRACE_TO_SYSLOG(true); TRACE_TO_STDOUT(true); #endif memleakfinder_traceblocksinsection(); #endif // If this is a child quitting, exit now to stop bad things happening _exit(0); } } protected: virtual void NotifyListenerIsReady() { } public: virtual void Run2(bool &rChildExit) { try { // Wait object with a timeout of 1 second, which // is a reasonable time to wait before cleaning up // finished child processes, and allows the daemon // to terminate reasonably quickly on request. WaitForEvent connectionWait(1000); // BLOCK { // Get the address we need to bind to // this-> in next line required to build under some gcc versions const Configuration &config(this->GetConfiguration()); const Configuration &server(config.GetSubConfiguration("Server")); std::string addrs = server.GetKeyValue("ListenAddresses"); // split up the list of addresses std::vector addrlist; SplitString(addrs, ',', addrlist); for(unsigned int a = 0; a < addrlist.size(); ++a) { // split the address up into components std::vector c; SplitString(addrlist[a], ':', c); // listen! SocketListen *psocket = new SocketListen; try { if(c[0] == "inet") { // Check arguments if(c.size() != 2 && c.size() != 3) { THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) } // Which port? int port = Port; if(c.size() == 3) { // Convert to number port = ::atol(c[2].c_str()); if(port <= 0 || port > ((64*1024)-1)) { THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) } } // Listen psocket->Listen(Socket::TypeINET, c[1].c_str(), port); } else if(c[0] == "unix") { #ifdef WIN32 BOX_WARNING("Ignoring request to listen on a Unix socket on Windows: " << addrlist[a]); delete psocket; psocket = NULL; #else // Check arguments size if(c.size() != 2) { THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) } // unlink anything there ::unlink(c[1].c_str()); psocket->Listen(Socket::TypeUNIX, c[1].c_str()); #endif // WIN32 } else { delete psocket; THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) } if (psocket != NULL) { // Add to list of sockets mSockets.push_back(psocket); } } catch(...) { delete psocket; throw; } if (psocket != NULL) { // Add to the list of things to wait on connectionWait.Add(psocket); } } } NotifyListenerIsReady(); while(!StopRun()) { // Wait for a connection, or timeout SocketListen *psocket = (SocketListen *)connectionWait.Wait(); if(psocket) { // Get the incoming connection // (with zero wait time) std::string logMessage; std::auto_ptr connection(psocket->Accept(0, &logMessage)); // Was there one (there should be...) if(connection.get()) { // Since this is a template parameter, the if() will be optimised out by the compiler #ifndef WIN32 // no fork on Win32 if(ForkToHandleRequests && !IsSingleProcess()) { pid_t pid = ::fork(); switch(pid) { case -1: // Error! THROW_EXCEPTION(ServerException, ServerForkError) break; case 0: // Child process rChildExit = true; // Close listening sockets DeleteSockets(); // Set up daemon EnterChild(); SetProcessTitle("transaction"); // Memory leak test the forked process #ifdef BOX_MEMORY_LEAK_TESTING memleakfinder_startsectionmonitor(); #endif // The derived class does some server magic with the connection HandleConnection(*connection); // Since rChildExit == true, the forked process will call _exit() on return from this fn return; default: // parent daemon process break; } // Log it BOX_NOTICE("Message from child process " << pid << ": " << logMessage); } else { #endif // !WIN32 // Just handle in this process SetProcessTitle("handling"); HandleConnection(*connection); SetProcessTitle("idle"); #ifndef WIN32 } #endif // !WIN32 } } OnIdle(); #ifndef WIN32 // Clean up child processes (if forking daemon) if(ForkToHandleRequests && !IsSingleProcess()) { WaitForChildren(); } #endif // !WIN32 } } catch(...) { DeleteSockets(); throw; } // Delete the sockets DeleteSockets(); } #ifndef WIN32 // no waitpid() on Windows void WaitForChildren() { int p = 0; do { int status = 0; p = ::waitpid(0 /* any child in process group */, &status, WNOHANG); if(p == -1 && errno != ECHILD && errno != EINTR) { THROW_EXCEPTION(ServerException, ServerWaitOnChildError) } else if(p == 0) { // no children exited, will return from // function } else if(WIFEXITED(status)) { BOX_INFO("child process " << p << " " "terminated normally"); } else if(WIFSIGNALED(status)) { int sig = WTERMSIG(status); BOX_ERROR("child process " << p << " " "terminated abnormally with " "signal " << sig); } else { BOX_WARNING("something unknown happened " "to child process " << p << ": " "status = " << status); } } while(p > 0); } #endif virtual void HandleConnection(StreamType &rStream) { Connection(rStream); } virtual void Connection(StreamType &rStream) = 0; protected: // For checking code in derived classes -- use if you have an algorithm which // depends on the forking model in case someone changes it later. bool WillForkToHandleRequests() { #ifdef WIN32 return false; #else return ForkToHandleRequests && !IsSingleProcess(); #endif // WIN32 } private: // -------------------------------------------------------------------------- // // Function // Name: ServerStream::DeleteSockets() // Purpose: Delete sockets // Created: 9/3/04 // // -------------------------------------------------------------------------- void DeleteSockets() { for(unsigned int l = 0; l < mSockets.size(); ++l) { if(mSockets[l]) { mSockets[l]->Close(); delete mSockets[l]; } mSockets[l] = 0; } mSockets.clear(); } private: std::vector *> mSockets; }; #define SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ ConfigurationVerifyKey("ListenAddresses", 0, DEFAULT_ADDRESSES), \ DAEMON_VERIFY_SERVER_KEYS #include "MemLeakFindOff.h" #endif // SERVERSTREAM__H boxbackup/lib/server/TLSContext.cpp0000664000175000017500000000710010752640674020112 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: TLSContext.h // Purpose: TLS (SSL) context for connections // Created: 2003/08/06 // // -------------------------------------------------------------------------- #include "Box.h" #define TLS_CLASS_IMPLEMENTATION_CPP #include #include "TLSContext.h" #include "ServerException.h" #include "SSLLib.h" #include "TLSContext.h" #include "MemLeakFindOn.h" #define MAX_VERIFICATION_DEPTH 2 #define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" // -------------------------------------------------------------------------- // // Function // Name: TLSContext::TLSContext() // Purpose: Constructor // Created: 2003/08/06 // // -------------------------------------------------------------------------- TLSContext::TLSContext() : mpContext(0) { } // -------------------------------------------------------------------------- // // Function // Name: TLSContext::~TLSContext() // Purpose: Destructor // Created: 2003/08/06 // // -------------------------------------------------------------------------- TLSContext::~TLSContext() { if(mpContext != 0) { ::SSL_CTX_free(mpContext); } } // -------------------------------------------------------------------------- // // Function // Name: TLSContext::Initialise(bool, const char *, const char *, const char *) // Purpose: Initialise the context, loading in the specified certificate and private key files // Created: 2003/08/06 // // -------------------------------------------------------------------------- void TLSContext::Initialise(bool AsServer, const char *CertificatesFile, const char *PrivateKeyFile, const char *TrustedCAsFile) { if(mpContext != 0) { ::SSL_CTX_free(mpContext); } mpContext = ::SSL_CTX_new(AsServer?TLSv1_server_method():TLSv1_client_method()); if(mpContext == NULL) { THROW_EXCEPTION(ServerException, TLSAllocationFailed) } // Setup our identity if(::SSL_CTX_use_certificate_chain_file(mpContext, CertificatesFile) != 1) { std::string msg = "loading certificates from "; msg += CertificatesFile; SSLLib::LogError(msg); THROW_EXCEPTION(ServerException, TLSLoadCertificatesFailed) } if(::SSL_CTX_use_PrivateKey_file(mpContext, PrivateKeyFile, SSL_FILETYPE_PEM) != 1) { std::string msg = "loading private key from "; msg += PrivateKeyFile; SSLLib::LogError(msg); THROW_EXCEPTION(ServerException, TLSLoadPrivateKeyFailed) } // Setup the identify of CAs we trust if(::SSL_CTX_load_verify_locations(mpContext, TrustedCAsFile, NULL) != 1) { std::string msg = "loading CA cert from "; msg += TrustedCAsFile; SSLLib::LogError(msg); THROW_EXCEPTION(ServerException, TLSLoadTrustedCAsFailed) } // Setup options to require these certificates ::SSL_CTX_set_verify(mpContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); // and a sensible maximum depth ::SSL_CTX_set_verify_depth(mpContext, MAX_VERIFICATION_DEPTH); // Setup allowed ciphers if(::SSL_CTX_set_cipher_list(mpContext, CIPHER_LIST) != 1) { SSLLib::LogError("setting cipher list to " CIPHER_LIST); THROW_EXCEPTION(ServerException, TLSSetCiphersFailed) } } // -------------------------------------------------------------------------- // // Function // Name: TLSContext::GetRawContext() // Purpose: Get the raw context for OpenSSL API // Created: 2003/08/06 // // -------------------------------------------------------------------------- SSL_CTX *TLSContext::GetRawContext() const { if(mpContext == 0) { THROW_EXCEPTION(ServerException, TLSContextNotInitialised) } return mpContext; } boxbackup/lib/server/SocketStreamTLS.h0000664000175000017500000000276611127732440020542 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SocketStreamTLS.h // Purpose: Socket stream encrpyted and authenticated by TLS // Created: 2003/08/06 // // -------------------------------------------------------------------------- #ifndef SOCKETSTREAMTLS__H #define SOCKETSTREAMTLS__H #include #include "SocketStream.h" class TLSContext; #ifndef TLS_CLASS_IMPLEMENTATION_CPP class SSL; class BIO; #endif // -------------------------------------------------------------------------- // // Class // Name: SocketStreamTLS // Purpose: Socket stream encrpyted and authenticated by TLS // Created: 2003/08/06 // // -------------------------------------------------------------------------- class SocketStreamTLS : public SocketStream { public: SocketStreamTLS(); SocketStreamTLS(int socket); ~SocketStreamTLS(); private: SocketStreamTLS(const SocketStreamTLS &rToCopy); public: void Open(const TLSContext &rContext, Socket::Type Type, const std::string& rName, int Port = 0); void Handshake(const TLSContext &rContext, bool IsServer = false); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual void Write(const void *pBuffer, int NBytes); virtual void Close(); virtual void Shutdown(bool Read = true, bool Write = true); std::string GetPeerCommonName(); private: bool WaitWhenRetryRequired(int SSLErrorCode, int Timeout); private: SSL *mpSSL; BIO *mpBIO; }; #endif // SOCKETSTREAMTLS__H boxbackup/lib/server/WinNamedPipeStream.h0000664000175000017500000000337011071524474021243 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: WinNamedPipeStream.h // Purpose: I/O stream interface for Win32 named pipes // Created: 2005/12/07 // // -------------------------------------------------------------------------- #if ! defined WINNAMEDPIPESTREAM__H && defined WIN32 #define WINNAMEDPIPESTREAM__H #include "IOStream.h" // -------------------------------------------------------------------------- // // Class // Name: WinNamedPipeStream // Purpose: I/O stream interface for Win32 named pipes // Created: 2003/07/31 // // -------------------------------------------------------------------------- class WinNamedPipeStream : public IOStream { public: WinNamedPipeStream(); WinNamedPipeStream(HANDLE hNamedPipe); ~WinNamedPipeStream(); // server side - create the named pipe and listen for connections // use WinNamedPipeListener to do this instead. // client side - connect to a waiting server void Connect(const std::string& rName); // both sides virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual void Write(const void *pBuffer, int NBytes); virtual void WriteAllBuffered(); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); protected: void MarkAsReadClosed() {mReadClosed = true;} void MarkAsWriteClosed() {mWriteClosed = true;} private: WinNamedPipeStream(const WinNamedPipeStream &rToCopy) { /* do not call */ } HANDLE mSocketHandle; HANDLE mReadableEvent; OVERLAPPED mReadOverlap; uint8_t mReadBuffer[4096]; size_t mBytesInBuffer; bool mReadClosed; bool mWriteClosed; bool mIsServer; bool mIsConnected; public: static std::string sPipeNamePrefix; }; #endif // WINNAMEDPIPESTREAM__H boxbackup/lib/server/ServerControl.cpp0000664000175000017500000000737011173734217020716 0ustar siretartsiretart#include "Box.h" #include #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SIGNAL_H #include #endif #include "ServerControl.h" #include "Test.h" #ifdef WIN32 #include "WinNamedPipeStream.h" #include "IOStreamGetLine.h" #include "BoxPortsAndFiles.h" static std::string sPipeName; void SetNamedPipeName(const std::string& rPipeName) { sPipeName = rPipeName; } bool SendCommands(const std::string& rCmd) { WinNamedPipeStream connection; try { connection.Connect(sPipeName); } catch(...) { BOX_ERROR("Failed to connect to daemon control socket"); return false; } // For receiving data IOStreamGetLine getLine(connection); // Wait for the configuration summary std::string configSummary; if(!getLine.GetLine(configSummary)) { BOX_ERROR("Failed to receive configuration summary from daemon"); return false; } // Was the connection rejected by the server? if(getLine.IsEOF()) { BOX_ERROR("Server rejected the connection"); return false; } // Decode it int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", &autoBackup, &updateStoreInterval, &minimumFileAge, &maxUploadWait) != 4) { BOX_ERROR("Config summary didn't decode"); return false; } std::string cmds; bool expectResponse; if (rCmd != "") { cmds = rCmd; cmds += "\nquit\n"; expectResponse = true; } else { cmds = "quit\n"; expectResponse = false; } connection.Write(cmds.c_str(), cmds.size()); // Read the response std::string line; bool statusOk = !expectResponse; while (expectResponse && !getLine.IsEOF() && getLine.GetLine(line)) { // Is this an OK or error line? if (line == "ok") { statusOk = true; } else if (line == "error") { BOX_ERROR(rCmd); break; } else { BOX_WARNING("Unexpected response to command '" << rCmd << "': " << line) } } return statusOk; } bool HUPServer(int pid) { return SendCommands("reload"); } bool KillServerInternal(int pid) { HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, false, pid); if (hProcess == NULL) { BOX_ERROR("Failed to open process " << pid << ": " << GetErrorMessage(GetLastError())); return false; } if (!TerminateProcess(hProcess, 1)) { BOX_ERROR("Failed to terminate process " << pid << ": " << GetErrorMessage(GetLastError())); CloseHandle(hProcess); return false; } CloseHandle(hProcess); return true; } #else // !WIN32 bool HUPServer(int pid) { if(pid == 0) return false; return ::kill(pid, SIGHUP) == 0; } bool KillServerInternal(int pid) { if(pid == 0 || pid == -1) return false; bool killed = (::kill(pid, SIGTERM) == 0); if (!killed) { BOX_LOG_SYS_ERROR("Failed to kill process " << pid); } TEST_THAT(killed); return killed; } #endif // WIN32 bool KillServer(int pid, bool WaitForProcess) { if (!KillServerInternal(pid)) { return false; } #ifdef HAVE_WAITPID if (WaitForProcess) { int status, result; result = waitpid(pid, &status, 0); if (result != pid) { BOX_LOG_SYS_ERROR("waitpid failed"); } TEST_THAT(result == pid); TEST_THAT(WIFEXITED(status)); if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { BOX_WARNING("process exited with code " << WEXITSTATUS(status)); } TEST_THAT(WEXITSTATUS(status) == 0); } } #endif for (int i = 0; i < 30; i++) { if (i == 0) { printf("Waiting for server to die (pid %d): ", pid); } printf("."); fflush(stdout); if (!ServerIsAlive(pid)) break; ::sleep(1); if (!ServerIsAlive(pid)) break; } if (!ServerIsAlive(pid)) { printf(" done.\n"); } else { printf(" failed!\n"); } fflush(stdout); return !ServerIsAlive(pid); } boxbackup/lib/server/ProtocolUncertainStream.cpp0000664000175000017500000001304011071514346022720 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ProtocolUncertainStream.h // Purpose: Read part of another stream // Created: 2003/12/05 // // -------------------------------------------------------------------------- #include "Box.h" #include "ProtocolUncertainStream.h" #include "ServerException.h" #include "Protocol.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: ProtocolUncertainStream::ProtocolUncertainStream(IOStream &, int) // Purpose: Constructor, taking another stream. // Created: 2003/12/05 // // -------------------------------------------------------------------------- ProtocolUncertainStream::ProtocolUncertainStream(IOStream &rSource) : mrSource(rSource), mBytesLeftInCurrentBlock(0), mFinished(false) { } // -------------------------------------------------------------------------- // // Function // Name: ProtocolUncertainStream::~ProtocolUncertainStream() // Purpose: Destructor. Won't absorb any unread bytes. // Created: 2003/12/05 // // -------------------------------------------------------------------------- ProtocolUncertainStream::~ProtocolUncertainStream() { if(!mFinished) { BOX_WARNING("ProtocolUncertainStream destroyed before " "stream finished"); } } // -------------------------------------------------------------------------- // // Function // Name: ProtocolUncertainStream::Read(void *, int, int) // Purpose: As interface. // Created: 2003/12/05 // // -------------------------------------------------------------------------- int ProtocolUncertainStream::Read(void *pBuffer, int NBytes, int Timeout) { // Finished? if(mFinished) { return 0; } int read = 0; while(read < NBytes) { // Anything we can get from the current block? ASSERT(mBytesLeftInCurrentBlock >= 0); if(mBytesLeftInCurrentBlock > 0) { // Yes, let's use some of these up int toRead = (NBytes - read); if(toRead > mBytesLeftInCurrentBlock) { // Adjust downwards to only read stuff out of the current block toRead = mBytesLeftInCurrentBlock; } BOX_TRACE("Reading " << toRead << " bytes from stream"); // Read it int r = mrSource.Read(((uint8_t*)pBuffer) + read, toRead, Timeout); // Give up now if it didn't return anything if(r == 0) { BOX_TRACE("Read " << r << " bytes from " "stream, returning"); return read; } // Adjust counts of bytes by the bytes recieved read += r; mBytesLeftInCurrentBlock -= r; // stop now if the stream returned less than we asked for -- avoid blocking if(r != toRead) { BOX_TRACE("Read " << r << " bytes from " "stream, returning"); return read; } } else { // Read the header byte to find out how much there is // in the next block uint8_t header; if(mrSource.Read(&header, 1, Timeout) == 0) { // Didn't get the byte, return now BOX_TRACE("Read 0 bytes of block header, " "returning with " << read << " bytes " "read this time"); return read; } // Interpret the byte... if(header == Protocol::ProtocolStreamHeader_EndOfStream) { // All done. mFinished = true; BOX_TRACE("Stream finished, returning with " << read << " bytes read this time"); return read; } else if(header <= Protocol::ProtocolStreamHeader_MaxEncodedSizeValue) { // get size of the block from the Protocol's lovely list mBytesLeftInCurrentBlock = Protocol::sProtocolStreamHeaderLengths[header]; } else if(header == Protocol::ProtocolStreamHeader_SizeIs64k) { // 64k mBytesLeftInCurrentBlock = (64*1024); } else { // Bad. It used the reserved values. THROW_EXCEPTION(ServerException, ProtocolUncertainStreamBadBlockHeader) } BOX_TRACE("Read header byte " << (int)header << ", " "next block has " << mBytesLeftInCurrentBlock << " bytes"); } } // Return the number read return read; } // -------------------------------------------------------------------------- // // Function // Name: ProtocolUncertainStream::BytesLeftToRead() // Purpose: As interface. // Created: 2003/12/05 // // -------------------------------------------------------------------------- IOStream::pos_type ProtocolUncertainStream::BytesLeftToRead() { // Only know how much is left if everything is finished return mFinished?(0):(IOStream::SizeOfStreamUnknown); } // -------------------------------------------------------------------------- // // Function // Name: ProtocolUncertainStream::Write(const void *, int) // Purpose: As interface. But will exception. // Created: 2003/12/05 // // -------------------------------------------------------------------------- void ProtocolUncertainStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(ServerException, CantWriteToProtocolUncertainStream) } // -------------------------------------------------------------------------- // // Function // Name: ProtocolUncertainStream::StreamDataLeft() // Purpose: As interface. // Created: 2003/12/05 // // -------------------------------------------------------------------------- bool ProtocolUncertainStream::StreamDataLeft() { return !mFinished; } // -------------------------------------------------------------------------- // // Function // Name: ProtocolUncertainStream::StreamClosed() // Purpose: As interface. // Created: 2003/12/05 // // -------------------------------------------------------------------------- bool ProtocolUncertainStream::StreamClosed() { // always closed return true; } boxbackup/lib/server/ServerControl.h0000664000175000017500000000062511046621544020354 0ustar siretartsiretart#ifndef SERVER_CONTROL_H #define SERVER_CONTROL_H #include "Test.h" bool HUPServer(int pid); bool KillServer(int pid, bool WaitForProcess = false); #ifdef WIN32 #include "WinNamedPipeStream.h" #include "IOStreamGetLine.h" #include "BoxPortsAndFiles.h" void SetNamedPipeName(const std::string& rPipeName); // bool SendCommands(const std::string& rCmd); #endif // WIN32 #endif // SERVER_CONTROL_H boxbackup/lib/server/SocketStream.h0000664000175000017500000000364011127624055020151 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SocketStream.h // Purpose: I/O stream interface for sockets // Created: 2003/07/31 // // -------------------------------------------------------------------------- #ifndef SOCKETSTREAM__H #define SOCKETSTREAM__H #include "IOStream.h" #include "Socket.h" #ifdef WIN32 typedef SOCKET tOSSocketHandle; #define INVALID_SOCKET_VALUE (tOSSocketHandle)(-1) #else typedef int tOSSocketHandle; #define INVALID_SOCKET_VALUE -1 #endif // -------------------------------------------------------------------------- // // Class // Name: SocketStream // Purpose: Stream interface for sockets // Created: 2003/07/31 // // -------------------------------------------------------------------------- class SocketStream : public IOStream { public: SocketStream(); SocketStream(int socket); SocketStream(const SocketStream &rToCopy); ~SocketStream(); void Open(Socket::Type Type, const std::string& rName, int Port = 0); void Attach(int socket); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual void Write(const void *pBuffer, int NBytes); virtual void Close(); virtual bool StreamDataLeft(); virtual bool StreamClosed(); virtual void Shutdown(bool Read = true, bool Write = true); virtual bool GetPeerCredentials(uid_t &rUidOut, gid_t &rGidOut); protected: tOSSocketHandle GetSocketHandle(); void MarkAsReadClosed() {mReadClosed = true;} void MarkAsWriteClosed() {mWriteClosed = true;} private: tOSSocketHandle mSocketHandle; bool mReadClosed; bool mWriteClosed; protected: off_t mBytesRead; off_t mBytesWritten; public: off_t GetBytesRead() const {return mBytesRead;} off_t GetBytesWritten() const {return mBytesWritten;} void ResetCounters() {mBytesRead = mBytesWritten = 0;} bool IsOpened() { return mSocketHandle != INVALID_SOCKET_VALUE; } }; #endif // SOCKETSTREAM__H boxbackup/lib/server/ServerTLS.h0000664000175000017500000000460111053246057017374 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ServerTLS.h // Purpose: Implementation of a server using TLS streams // Created: 2003/08/06 // // -------------------------------------------------------------------------- #ifndef SERVERTLS__H #define SERVERTLS__H #include "ServerStream.h" #include "SocketStreamTLS.h" #include "SSLLib.h" #include "TLSContext.h" // -------------------------------------------------------------------------- // // Class // Name: ServerTLS // Purpose: Implementation of a server using TLS streams // Created: 2003/08/06 // // -------------------------------------------------------------------------- template class ServerTLS : public ServerStream { public: ServerTLS() { // Safe to call this here, as the Daemon class makes sure there is only one instance every of a Daemon. SSLLib::Initialise(); } ~ServerTLS() { } private: ServerTLS(const ServerTLS &) { } public: virtual void Run2(bool &rChildExit) { // First, set up the SSL context. // Get parameters from the configuration // this-> in next line required to build under some gcc versions const Configuration &conf(this->GetConfiguration()); const Configuration &serverconf(conf.GetSubConfiguration("Server")); std::string certFile(serverconf.GetKeyValue("CertificateFile")); std::string keyFile(serverconf.GetKeyValue("PrivateKeyFile")); std::string caFile(serverconf.GetKeyValue("TrustedCAsFile")); mContext.Initialise(true /* as server */, certFile.c_str(), keyFile.c_str(), caFile.c_str()); // Then do normal stream server stuff ServerStream::Run2(rChildExit); } virtual void HandleConnection(SocketStreamTLS &rStream) { rStream.Handshake(mContext, true /* is server */); // this-> in next line required to build under some gcc versions this->Connection(rStream); } private: TLSContext mContext; }; #define SERVERTLS_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ ConfigurationVerifyKey("CertificateFile", ConfigTest_Exists), \ ConfigurationVerifyKey("PrivateKeyFile", ConfigTest_Exists), \ ConfigurationVerifyKey("TrustedCAsFile", ConfigTest_Exists), \ SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) #endif // SERVERTLS__H boxbackup/lib/server/Protocol.cpp0000664000175000017500000006440311126433077017706 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Protocol.cpp // Purpose: Generic protocol support // Created: 2003/08/19 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include "Protocol.h" #include "ProtocolWire.h" #include "IOStream.h" #include "ServerException.h" #include "PartialReadStream.h" #include "ProtocolUncertainStream.h" #include "Logging.h" #include "MemLeakFindOn.h" #ifdef BOX_RELEASE_BUILD #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 1024 #else // #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 1024 #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 4 #endif #define UNCERTAIN_STREAM_SIZE_BLOCK (64*1024) // -------------------------------------------------------------------------- // // Function // Name: Protocol::Protocol(IOStream &rStream) // Purpose: Constructor // Created: 2003/08/19 // // -------------------------------------------------------------------------- Protocol::Protocol(IOStream &rStream) : mrStream(rStream), mHandshakeDone(false), mMaxObjectSize(PROTOCOL_DEFAULT_MAXOBJSIZE), mTimeout(PROTOCOL_DEFAULT_TIMEOUT), mpBuffer(0), mBufferSize(0), mReadOffset(-1), mWriteOffset(-1), mValidDataSize(-1), mLastErrorType(NoError), mLastErrorSubType(NoError) { BOX_TRACE("Send block allocation size is " << PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::~Protocol() // Purpose: Destructor // Created: 2003/08/19 // // -------------------------------------------------------------------------- Protocol::~Protocol() { // Free buffer? if(mpBuffer != 0) { free(mpBuffer); mpBuffer = 0; } } // -------------------------------------------------------------------------- // // Function // Name: Protocol::GetLastError(int &, int &) // Purpose: Returns true if there was an error, and type and subtype if there was. // Created: 2003/08/19 // // -------------------------------------------------------------------------- bool Protocol::GetLastError(int &rTypeOut, int &rSubTypeOut) { if(mLastErrorType == NoError) { // no error. return false; } // Return type and subtype in args rTypeOut = mLastErrorType; rSubTypeOut = mLastErrorSubType; // and unset them mLastErrorType = NoError; mLastErrorSubType = NoError; return true; } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Handshake() // Purpose: Handshake with peer (exchange ident strings) // Created: 2003/08/20 // // -------------------------------------------------------------------------- void Protocol::Handshake() { // Already done? if(mHandshakeDone) { THROW_EXCEPTION(CommonException, Internal) } // Make handshake block PW_Handshake hsSend; ::memset(&hsSend, 0, sizeof(hsSend)); // Copy in ident string ::strncpy(hsSend.mIdent, GetIdentString(), sizeof(hsSend.mIdent)); // Send it mrStream.Write(&hsSend, sizeof(hsSend)); mrStream.WriteAllBuffered(); // Receive a handshake from the peer PW_Handshake hsReceive; ::memset(&hsReceive, 0, sizeof(hsReceive)); char *readInto = (char*)&hsReceive; int bytesToRead = sizeof(hsReceive); while(bytesToRead > 0) { // Get some data from the stream int bytesRead = mrStream.Read(readInto, bytesToRead, mTimeout); if(bytesRead == 0) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_Timeout) } readInto += bytesRead; bytesToRead -= bytesRead; } ASSERT(bytesToRead == 0); // Are they the same? if(::memcmp(&hsSend, &hsReceive, sizeof(hsSend)) != 0) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_HandshakeFailed) } // Mark as done mHandshakeDone = true; } // -------------------------------------------------------------------------- // // Function // Name: Protocol::CheckAndReadHdr(void *) // Purpose: Check read for recieve call and get object header from stream. // Don't use type here to avoid dependency in .h file. // Created: 2003/08/26 // // -------------------------------------------------------------------------- void Protocol::CheckAndReadHdr(void *hdr) { // Check usage if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) { THROW_EXCEPTION(ServerException, Protocol_BadUsage) } // Handshake done? if(!mHandshakeDone) { Handshake(); } // Get some data into this header if(!mrStream.ReadFullBuffer(hdr, sizeof(PW_ObjectHeader), 0 /* not interested in bytes read if this fails */, mTimeout)) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_Timeout) } } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Recieve() // Purpose: Recieves an object from the stream, creating it from the factory object type // Created: 2003/08/19 // // -------------------------------------------------------------------------- std::auto_ptr Protocol::Receive() { // Get object header PW_ObjectHeader objHeader; CheckAndReadHdr(&objHeader); // Hope it's not a stream if(ntohl(objHeader.mObjType) == SPECIAL_STREAM_OBJECT_TYPE) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_StreamWhenObjExpected) } // Check the object size u_int32_t objSize = ntohl(objHeader.mObjSize); if(objSize < sizeof(objHeader) || objSize > mMaxObjectSize) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_ObjTooBig) } // Create a blank object std::auto_ptr obj(MakeProtocolObject(ntohl(objHeader.mObjType))); // Make sure memory is allocated to read it into EnsureBufferAllocated(objSize); // Read data if(!mrStream.ReadFullBuffer(mpBuffer, objSize - sizeof(objHeader), 0 /* not interested in bytes read if this fails */, mTimeout)) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_Timeout) } // Setup ready to read out data from the buffer mValidDataSize = objSize - sizeof(objHeader); mReadOffset = 0; // Get the object to read its properties from the data recieved try { obj->SetPropertiesFromStreamData(*this); } catch(...) { // Make sure state is reset! mValidDataSize = -1; mReadOffset = -1; throw; } // Any data left over? bool dataLeftOver = (mValidDataSize != mReadOffset); // Unset read state, so future read calls don't fail mValidDataSize = -1; mReadOffset = -1; // Exception if not all the data was consumed if(dataLeftOver) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_BadCommandRecieved) } return obj; } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Send() // Purpose: Send an object to the other side of the connection. // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Send(const ProtocolObject &rObject) { // Check usage if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) { THROW_EXCEPTION(ServerException, Protocol_BadUsage) } // Handshake done? if(!mHandshakeDone) { Handshake(); } // Make sure there's a little bit of space allocated EnsureBufferAllocated(((sizeof(PW_ObjectHeader) + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK - 1) / PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK) * PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); ASSERT(mBufferSize >= (int)sizeof(PW_ObjectHeader)); // Setup for write operation mValidDataSize = 0; // Not used, but must not be -1 mWriteOffset = sizeof(PW_ObjectHeader); try { rObject.WritePropertiesToStreamData(*this); } catch(...) { // Make sure state is reset! mValidDataSize = -1; mWriteOffset = -1; throw; } // How big? int writtenSize = mWriteOffset; // Reset write state mValidDataSize = -1; mWriteOffset = -1; // Make header in the existing block PW_ObjectHeader *pobjHeader = (PW_ObjectHeader*)(mpBuffer); pobjHeader->mObjSize = htonl(writtenSize); pobjHeader->mObjType = htonl(rObject.GetType()); // Write data mrStream.Write(mpBuffer, writtenSize); mrStream.WriteAllBuffered(); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::EnsureBufferAllocated(int) // Purpose: Private. Ensures the buffer is at least the size requested. // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::EnsureBufferAllocated(int Size) { if(mpBuffer != 0 && mBufferSize >= Size) { // Nothing to do! return; } // Need to allocate, or reallocate, the block if(mpBuffer != 0) { // Reallocate void *b = realloc(mpBuffer, Size); if(b == 0) { throw std::bad_alloc(); } mpBuffer = (char*)b; mBufferSize = Size; } else { // Just allocate mpBuffer = (char*)malloc(Size); if(mpBuffer == 0) { throw std::bad_alloc(); } mBufferSize = Size; } } #define READ_START_CHECK \ if(mValidDataSize == -1 || mWriteOffset != -1 || mReadOffset == -1) \ { \ THROW_EXCEPTION(ServerException, Protocol_BadUsage) \ } #define READ_CHECK_BYTES_AVAILABLE(bytesRequired) \ if((mReadOffset + (int)(bytesRequired)) > mValidDataSize) \ { \ THROW_EXCEPTION(ConnectionException, Conn_Protocol_BadCommandRecieved) \ } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Read(void *, int) // Purpose: Read raw data from the stream (buffered) // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Read(void *Buffer, int Size) { READ_START_CHECK READ_CHECK_BYTES_AVAILABLE(Size) // Copy data out ::memmove(Buffer, mpBuffer + mReadOffset, Size); mReadOffset += Size; } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Read(std::string &, int) // Purpose: Read raw data from the stream (buffered), into a std::string // Created: 2003/08/26 // // -------------------------------------------------------------------------- void Protocol::Read(std::string &rOut, int Size) { READ_START_CHECK READ_CHECK_BYTES_AVAILABLE(Size) rOut.assign(mpBuffer + mReadOffset, Size); mReadOffset += Size; } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Read(int64_t &) // Purpose: Read a value from the stream (buffered) // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Read(int64_t &rOut) { READ_START_CHECK READ_CHECK_BYTES_AVAILABLE(sizeof(int64_t)) #ifdef HAVE_ALIGNED_ONLY_INT64 int64_t nvalue; memcpy(&nvalue, mpBuffer + mReadOffset, sizeof(int64_t)); #else int64_t nvalue = *((int64_t*)(mpBuffer + mReadOffset)); #endif rOut = box_ntoh64(nvalue); mReadOffset += sizeof(int64_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Read(int32_t &) // Purpose: Read a value from the stream (buffered) // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Read(int32_t &rOut) { READ_START_CHECK READ_CHECK_BYTES_AVAILABLE(sizeof(int32_t)) #ifdef HAVE_ALIGNED_ONLY_INT32 int32_t nvalue; memcpy(&nvalue, mpBuffer + mReadOffset, sizeof(int32_t)); #else int32_t nvalue = *((int32_t*)(mpBuffer + mReadOffset)); #endif rOut = ntohl(nvalue); mReadOffset += sizeof(int32_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Read(int16_t &) // Purpose: Read a value from the stream (buffered) // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Read(int16_t &rOut) { READ_START_CHECK READ_CHECK_BYTES_AVAILABLE(sizeof(int16_t)) rOut = ntohs(*((int16_t*)(mpBuffer + mReadOffset))); mReadOffset += sizeof(int16_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Read(int8_t &) // Purpose: Read a value from the stream (buffered) // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Read(int8_t &rOut) { READ_START_CHECK READ_CHECK_BYTES_AVAILABLE(sizeof(int8_t)) rOut = *((int8_t*)(mpBuffer + mReadOffset)); mReadOffset += sizeof(int8_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Read(std::string &) // Purpose: Read a value from the stream (buffered) // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Read(std::string &rOut) { // READ_START_CHECK implied int32_t size; Read(size); READ_CHECK_BYTES_AVAILABLE(size) // initialise string rOut.assign(mpBuffer + mReadOffset, size); mReadOffset += size; } #define WRITE_START_CHECK \ if(mValidDataSize == -1 || mWriteOffset == -1 || mReadOffset != -1) \ { \ THROW_EXCEPTION(ServerException, Protocol_BadUsage) \ } #define WRITE_ENSURE_BYTES_AVAILABLE(bytesToWrite) \ if(mWriteOffset + (int)(bytesToWrite) > mBufferSize) \ { \ EnsureBufferAllocated((((mWriteOffset + (int)(bytesToWrite)) + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK - 1) / PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK) * PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); \ ASSERT(mWriteOffset + (int)(bytesToWrite) <= mBufferSize); \ } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Write(const void *, int) // Purpose: Writes the contents of a buffer to the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Write(const void *Buffer, int Size) { WRITE_START_CHECK WRITE_ENSURE_BYTES_AVAILABLE(Size) ::memmove(mpBuffer + mWriteOffset, Buffer, Size); mWriteOffset += Size; } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Write(int64_t) // Purpose: Writes a value to the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Write(int64_t Value) { WRITE_START_CHECK WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int64_t)) int64_t nvalue = box_hton64(Value); #ifdef HAVE_ALIGNED_ONLY_INT64 memcpy(mpBuffer + mWriteOffset, &nvalue, sizeof(int64_t)); #else *((int64_t*)(mpBuffer + mWriteOffset)) = nvalue; #endif mWriteOffset += sizeof(int64_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Write(int32_t) // Purpose: Writes a value to the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Write(int32_t Value) { WRITE_START_CHECK WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int32_t)) int32_t nvalue = htonl(Value); #ifdef HAVE_ALIGNED_ONLY_INT32 memcpy(mpBuffer + mWriteOffset, &nvalue, sizeof(int32_t)); #else *((int32_t*)(mpBuffer + mWriteOffset)) = nvalue; #endif mWriteOffset += sizeof(int32_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Write(int16_t) // Purpose: Writes a value to the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Write(int16_t Value) { WRITE_START_CHECK WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int16_t)) *((int16_t*)(mpBuffer + mWriteOffset)) = htons(Value); mWriteOffset += sizeof(int16_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Write(int8_t) // Purpose: Writes a value to the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Write(int8_t Value) { WRITE_START_CHECK WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int8_t)) *((int8_t*)(mpBuffer + mWriteOffset)) = Value; mWriteOffset += sizeof(int8_t); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::Write(const std::string &) // Purpose: Writes a value to the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- void Protocol::Write(const std::string &rValue) { // WRITE_START_CHECK implied Write((int32_t)(rValue.size())); WRITE_ENSURE_BYTES_AVAILABLE(rValue.size()) Write(rValue.c_str(), rValue.size()); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::ReceieveStream() // Purpose: Receive a stream from the remote side // Created: 2003/08/26 // // -------------------------------------------------------------------------- std::auto_ptr Protocol::ReceiveStream() { // Get object header PW_ObjectHeader objHeader; CheckAndReadHdr(&objHeader); // Hope it's not an object if(ntohl(objHeader.mObjType) != SPECIAL_STREAM_OBJECT_TYPE) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_ObjWhenStreamExpected) } // Get the stream size u_int32_t streamSize = ntohl(objHeader.mObjSize); // Inform sub class InformStreamReceiving(streamSize); // Return a stream object if(streamSize == ProtocolStream_SizeUncertain) { BOX_TRACE("Receiving stream, size uncertain"); return std::auto_ptr( new ProtocolUncertainStream(mrStream)); } else { BOX_TRACE("Receiving stream, size " << streamSize << " bytes"); return std::auto_ptr( new PartialReadStream(mrStream, streamSize)); } } // -------------------------------------------------------------------------- // // Function // Name: Protocol::SendStream(IOStream &) // Purpose: Send a stream to the remote side // Created: 2003/08/26 // // -------------------------------------------------------------------------- void Protocol::SendStream(IOStream &rStream) { // Check usage if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) { THROW_EXCEPTION(ServerException, Protocol_BadUsage) } // Handshake done? if(!mHandshakeDone) { Handshake(); } // How should this be streamed? bool uncertainSize = false; IOStream::pos_type streamSize = rStream.BytesLeftToRead(); if(streamSize == IOStream::SizeOfStreamUnknown || streamSize > 0x7fffffff) { // Can't send this using the fixed size header uncertainSize = true; } // Inform sub class InformStreamSending(streamSize); // Make header PW_ObjectHeader objHeader; objHeader.mObjSize = htonl(uncertainSize?(ProtocolStream_SizeUncertain):streamSize); objHeader.mObjType = htonl(SPECIAL_STREAM_OBJECT_TYPE); // Write header mrStream.Write(&objHeader, sizeof(objHeader)); // Could be sent in one of two ways if(uncertainSize) { // Don't know how big this is going to be -- so send it in chunks // Allocate memory uint8_t *blockA = (uint8_t *)malloc(UNCERTAIN_STREAM_SIZE_BLOCK + sizeof(int)); if(blockA == 0) { throw std::bad_alloc(); } uint8_t *block = blockA + sizeof(int); // so that everything is word aligned for reading, but can put the one byte header before it try { int bytesInBlock = 0; while(rStream.StreamDataLeft()) { // Read some of it bytesInBlock += rStream.Read(block + bytesInBlock, UNCERTAIN_STREAM_SIZE_BLOCK - bytesInBlock); // Send as much as we can out bytesInBlock -= SendStreamSendBlock(block, bytesInBlock); } // Everything recieved from stream, but need to send whatevers left in the block while(bytesInBlock > 0) { bytesInBlock -= SendStreamSendBlock(block, bytesInBlock); } // Send final byte to finish the stream BOX_TRACE("Sending end of stream byte"); uint8_t endOfStream = ProtocolStreamHeader_EndOfStream; mrStream.Write(&endOfStream, 1); BOX_TRACE("Sent end of stream byte"); } catch(...) { free(blockA); throw; } // Clean up free(blockA); } else { // Fixed size stream, send it all in one go if(!rStream.CopyStreamTo(mrStream, mTimeout, 4096 /* slightly larger buffer */)) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_TimeOutWhenSendingStream) } } // Make sure everything is written mrStream.WriteAllBuffered(); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::SendStreamSendBlock(uint8_t *, int) // Purpose: Sends as much of the block as can be sent, moves the remainer down to the beginning, // and returns the number of bytes sent. WARNING: Will write to Block[-1] // Created: 5/12/03 // // -------------------------------------------------------------------------- int Protocol::SendStreamSendBlock(uint8_t *Block, int BytesInBlock) { // Quick sanity check if(BytesInBlock == 0) { BOX_TRACE("Zero size block, not sending anything"); return 0; } // Work out the header byte uint8_t header = 0; int writeSize = 0; if(BytesInBlock >= (64*1024)) { header = ProtocolStreamHeader_SizeIs64k; writeSize = (64*1024); } else { // Scan the table to find the most that can be written for(int s = ProtocolStreamHeader_MaxEncodedSizeValue; s > 0; --s) { if(sProtocolStreamHeaderLengths[s] <= BytesInBlock) { header = s; writeSize = sProtocolStreamHeaderLengths[s]; break; } } } ASSERT(header > 0); BOX_TRACE("Sending header byte " << (int)header << " plus " << writeSize << " bytes to stream"); // Store the header Block[-1] = header; // Write everything out mrStream.Write(Block - 1, writeSize + 1); BOX_TRACE("Sent " << (writeSize+1) << " bytes to stream"); // move the remainer to the beginning of the block for the next time round if(writeSize != BytesInBlock) { ::memmove(Block, Block + writeSize, BytesInBlock - writeSize); } return writeSize; } // -------------------------------------------------------------------------- // // Function // Name: Protocol::InformStreamReceiving(u_int32_t) // Purpose: Informs sub classes about streams being received // Created: 2003/10/27 // // -------------------------------------------------------------------------- void Protocol::InformStreamReceiving(u_int32_t Size) { // Do nothing } // -------------------------------------------------------------------------- // // Function // Name: Protocol::InformStreamSending(u_int32_t) // Purpose: Informs sub classes about streams being sent // Created: 2003/10/27 // // -------------------------------------------------------------------------- void Protocol::InformStreamSending(u_int32_t Size) { // Do nothing } /* perl code to generate the table below #!/usr/bin/perl use strict; open OUT,">protolengths.txt"; my $len = 0; for(0 .. 255) { print OUT "\t$len,\t// $_\n"; my $inc = 1; $inc = 8 if $_ >= 64; $inc = 16 if $_ >= 96; $inc = 32 if $_ >= 112; $inc = 64 if $_ >= 128; $inc = 128 if $_ >= 135; $inc = 256 if $_ >= 147; $inc = 512 if $_ >= 159; $inc = 1024 if $_ >= 231; $len += $inc; } close OUT; */ const uint16_t Protocol::sProtocolStreamHeaderLengths[256] = { 0, // 0 1, // 1 2, // 2 3, // 3 4, // 4 5, // 5 6, // 6 7, // 7 8, // 8 9, // 9 10, // 10 11, // 11 12, // 12 13, // 13 14, // 14 15, // 15 16, // 16 17, // 17 18, // 18 19, // 19 20, // 20 21, // 21 22, // 22 23, // 23 24, // 24 25, // 25 26, // 26 27, // 27 28, // 28 29, // 29 30, // 30 31, // 31 32, // 32 33, // 33 34, // 34 35, // 35 36, // 36 37, // 37 38, // 38 39, // 39 40, // 40 41, // 41 42, // 42 43, // 43 44, // 44 45, // 45 46, // 46 47, // 47 48, // 48 49, // 49 50, // 50 51, // 51 52, // 52 53, // 53 54, // 54 55, // 55 56, // 56 57, // 57 58, // 58 59, // 59 60, // 60 61, // 61 62, // 62 63, // 63 64, // 64 72, // 65 80, // 66 88, // 67 96, // 68 104, // 69 112, // 70 120, // 71 128, // 72 136, // 73 144, // 74 152, // 75 160, // 76 168, // 77 176, // 78 184, // 79 192, // 80 200, // 81 208, // 82 216, // 83 224, // 84 232, // 85 240, // 86 248, // 87 256, // 88 264, // 89 272, // 90 280, // 91 288, // 92 296, // 93 304, // 94 312, // 95 320, // 96 336, // 97 352, // 98 368, // 99 384, // 100 400, // 101 416, // 102 432, // 103 448, // 104 464, // 105 480, // 106 496, // 107 512, // 108 528, // 109 544, // 110 560, // 111 576, // 112 608, // 113 640, // 114 672, // 115 704, // 116 736, // 117 768, // 118 800, // 119 832, // 120 864, // 121 896, // 122 928, // 123 960, // 124 992, // 125 1024, // 126 1056, // 127 1088, // 128 1152, // 129 1216, // 130 1280, // 131 1344, // 132 1408, // 133 1472, // 134 1536, // 135 1664, // 136 1792, // 137 1920, // 138 2048, // 139 2176, // 140 2304, // 141 2432, // 142 2560, // 143 2688, // 144 2816, // 145 2944, // 146 3072, // 147 3328, // 148 3584, // 149 3840, // 150 4096, // 151 4352, // 152 4608, // 153 4864, // 154 5120, // 155 5376, // 156 5632, // 157 5888, // 158 6144, // 159 6656, // 160 7168, // 161 7680, // 162 8192, // 163 8704, // 164 9216, // 165 9728, // 166 10240, // 167 10752, // 168 11264, // 169 11776, // 170 12288, // 171 12800, // 172 13312, // 173 13824, // 174 14336, // 175 14848, // 176 15360, // 177 15872, // 178 16384, // 179 16896, // 180 17408, // 181 17920, // 182 18432, // 183 18944, // 184 19456, // 185 19968, // 186 20480, // 187 20992, // 188 21504, // 189 22016, // 190 22528, // 191 23040, // 192 23552, // 193 24064, // 194 24576, // 195 25088, // 196 25600, // 197 26112, // 198 26624, // 199 27136, // 200 27648, // 201 28160, // 202 28672, // 203 29184, // 204 29696, // 205 30208, // 206 30720, // 207 31232, // 208 31744, // 209 32256, // 210 32768, // 211 33280, // 212 33792, // 213 34304, // 214 34816, // 215 35328, // 216 35840, // 217 36352, // 218 36864, // 219 37376, // 220 37888, // 221 38400, // 222 38912, // 223 39424, // 224 39936, // 225 40448, // 226 40960, // 227 41472, // 228 41984, // 229 42496, // 230 43008, // 231 44032, // 232 45056, // 233 46080, // 234 47104, // 235 48128, // 236 49152, // 237 50176, // 238 51200, // 239 52224, // 240 53248, // 241 54272, // 242 55296, // 243 56320, // 244 57344, // 245 58368, // 246 59392, // 247 60416, // 248 61440, // 249 62464, // 250 63488, // 251 64512, // 252 0, // 253 = 65536 / 64k 0, // 254 = special (reserved) 0 // 255 = special (reserved) }; boxbackup/lib/server/SSLLib.cpp0000664000175000017500000000527211126433077017174 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SSLLib.cpp // Purpose: Utility functions for dealing with the OpenSSL library // Created: 2003/08/06 // // -------------------------------------------------------------------------- #include "Box.h" #define TLS_CLASS_IMPLEMENTATION_CPP #include #include #include #ifdef WIN32 #include #endif #include "SSLLib.h" #include "ServerException.h" #include "MemLeakFindOn.h" #ifndef BOX_RELEASE_BUILD bool SSLLib__TraceErrors = false; #endif // -------------------------------------------------------------------------- // // Function // Name: SSLLib::Initialise() // Purpose: Initialise SSL library // Created: 2003/08/06 // // -------------------------------------------------------------------------- void SSLLib::Initialise() { if(!::SSL_library_init()) { LogError("initialising OpenSSL"); THROW_EXCEPTION(ServerException, SSLLibraryInitialisationError) } // More helpful error messages ::SSL_load_error_strings(); // Extra seeding over and above what's already done by the library #ifdef WIN32 HCRYPTPROV provider; if(!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { BOX_LOG_WIN_ERROR("Failed to acquire crypto context"); BOX_WARNING("No random device -- additional seeding of " "random number generator not performed."); } else { // must free provider BYTE buf[1024]; if(!CryptGenRandom(provider, sizeof(buf), buf)) { BOX_LOG_WIN_ERROR("Failed to get random data"); BOX_WARNING("No random device -- additional seeding of " "random number generator not performed."); } else { RAND_seed(buf, sizeof(buf)); } if(!CryptReleaseContext(provider, 0)) { BOX_LOG_WIN_ERROR("Failed to release crypto context"); } } #elif HAVE_RANDOM_DEVICE if(::RAND_load_file(RANDOM_DEVICE, 1024) != 1024) { THROW_EXCEPTION(ServerException, SSLRandomInitFailed) } #else BOX_WARNING("No random device -- additional seeding of " "random number generator not performed."); #endif } // -------------------------------------------------------------------------- // // Function // Name: SSLLib::LogError(const char *) // Purpose: Logs an error // Created: 2003/08/06 // // -------------------------------------------------------------------------- void SSLLib::LogError(const std::string& rErrorDuringAction) { unsigned long errcode; char errname[256]; // SSL docs say at least 120 bytes while((errcode = ERR_get_error()) != 0) { ::ERR_error_string_n(errcode, errname, sizeof(errname)); BOX_ERROR("SSL error while " << rErrorDuringAction << ": " << errname); } } boxbackup/lib/server/ProtocolWire.h0000664000175000017500000000152710347400657020202 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ProtocolWire.h // Purpose: On the wire structures for Protocol // Created: 2003/08/19 // // -------------------------------------------------------------------------- #ifndef PROTOCOLWIRE__H #define PROTOCOLWIRE__H #include // set packing to one byte #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "BeginStructPackForWire.h" #else BEGIN_STRUCTURE_PACKING_FOR_WIRE #endif typedef struct { char mIdent[32]; } PW_Handshake; typedef struct { u_int32_t mObjSize; u_int32_t mObjType; } PW_ObjectHeader; #define SPECIAL_STREAM_OBJECT_TYPE 0xffffffff // Use default packing #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "EndStructPackForWire.h" #else END_STRUCTURE_PACKING_FOR_WIRE #endif #endif // PROTOCOLWIRE__H boxbackup/lib/server/SocketStream.cpp0000664000175000017500000003016711157261425020511 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SocketStream.cpp // Purpose: I/O stream interface for sockets // Created: 2003/07/31 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #ifndef WIN32 #include #endif #ifdef HAVE_UCRED_H #include #endif #include "SocketStream.h" #include "ServerException.h" #include "CommonException.h" #include "Socket.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: SocketStream::SocketStream() // Purpose: Constructor (create stream ready for Open() call) // Created: 2003/07/31 // // -------------------------------------------------------------------------- SocketStream::SocketStream() : mSocketHandle(INVALID_SOCKET_VALUE), mReadClosed(false), mWriteClosed(false), mBytesRead(0), mBytesWritten(0) { } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::SocketStream(int) // Purpose: Create stream from existing socket handle // Created: 2003/07/31 // // -------------------------------------------------------------------------- SocketStream::SocketStream(int socket) : mSocketHandle(socket), mReadClosed(false), mWriteClosed(false), mBytesRead(0), mBytesWritten(0) { if(socket < 0) { THROW_EXCEPTION(ServerException, BadSocketHandle); } } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::SocketStream(const SocketStream &) // Purpose: Copy constructor (dup()s socket) // Created: 2003/07/31 // // -------------------------------------------------------------------------- SocketStream::SocketStream(const SocketStream &rToCopy) : mSocketHandle(::dup(rToCopy.mSocketHandle)), mReadClosed(rToCopy.mReadClosed), mWriteClosed(rToCopy.mWriteClosed), mBytesRead(rToCopy.mBytesRead), mBytesWritten(rToCopy.mBytesWritten) { if(rToCopy.mSocketHandle < 0) { THROW_EXCEPTION(ServerException, BadSocketHandle); } if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, DupError); } } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::~SocketStream() // Purpose: Destructor, closes stream if open // Created: 2003/07/31 // // -------------------------------------------------------------------------- SocketStream::~SocketStream() { if(mSocketHandle != INVALID_SOCKET_VALUE) { Close(); } } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::Attach(int) // Purpose: Attach a socket handle to this stream // Created: 11/12/03 // // -------------------------------------------------------------------------- void SocketStream::Attach(int socket) { if(mSocketHandle != INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, SocketAlreadyOpen) } ResetCounters(); mSocketHandle = socket; mReadClosed = false; mWriteClosed = false; } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::Open(Socket::Type, char *, int) // Purpose: Opens a connection to a listening socket (INET or UNIX) // Created: 2003/07/31 // // -------------------------------------------------------------------------- void SocketStream::Open(Socket::Type Type, const std::string& rName, int Port) { if(mSocketHandle != INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, SocketAlreadyOpen) } // Setup parameters based on type, looking up names if required int sockDomain = 0; SocketAllAddr addr; int addrLen = 0; Socket::NameLookupToSockAddr(addr, sockDomain, Type, rName, Port, addrLen); // Create the socket mSocketHandle = ::socket(sockDomain, SOCK_STREAM, 0 /* let OS choose protocol */); if(mSocketHandle == INVALID_SOCKET_VALUE) { BOX_LOG_SYS_ERROR("Failed to create a network socket"); THROW_EXCEPTION(ServerException, SocketOpenError) } // Connect it if(::connect(mSocketHandle, &addr.sa_generic, addrLen) == -1) { // Dispose of the socket #ifdef WIN32 DWORD err = WSAGetLastError(); ::closesocket(mSocketHandle); BOX_LOG_WIN_ERROR_NUMBER("Failed to connect to socket " "(type " << Type << ", name " << rName << ", port " << Port << ")", err); #else // !WIN32 BOX_LOG_SYS_ERROR("Failed to connect to socket (type " << Type << ", name " << rName << ", port " << Port << ")"); ::close(mSocketHandle); #endif // WIN32 mSocketHandle = INVALID_SOCKET_VALUE; THROW_EXCEPTION(ConnectionException, Conn_SocketConnectError) } ResetCounters(); mReadClosed = false; mWriteClosed = false; } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::Read(void *pBuffer, int NBytes) // Purpose: Reads data from stream. Maybe returns less than asked for. // Created: 2003/07/31 // // -------------------------------------------------------------------------- int SocketStream::Read(void *pBuffer, int NBytes, int Timeout) { if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, BadSocketHandle) } if(Timeout != IOStream::TimeOutInfinite) { struct pollfd p; p.fd = mSocketHandle; p.events = POLLIN; p.revents = 0; switch(::poll(&p, 1, (Timeout == IOStream::TimeOutInfinite)?INFTIM:Timeout)) { case -1: // error if(errno == EINTR) { // Signal. Just return 0 bytes return 0; } else { // Bad! BOX_LOG_SYS_ERROR("Failed to poll socket"); THROW_EXCEPTION(ServerException, SocketPollError) } break; case 0: // no data return 0; break; default: // good to go! break; } } #ifdef WIN32 int r = ::recv(mSocketHandle, (char*)pBuffer, NBytes, 0); #else int r = ::read(mSocketHandle, pBuffer, NBytes); #endif if(r == -1) { if(errno == EINTR) { // Nothing could be read return 0; } else { // Other error BOX_LOG_SYS_ERROR("Failed to read from socket"); THROW_EXCEPTION(ConnectionException, Conn_SocketReadError); } } // Closed for reading? if(r == 0) { mReadClosed = true; } mBytesRead += r; return r; } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::Write(void *pBuffer, int NBytes) // Purpose: Writes data, blocking until it's all done. // Created: 2003/07/31 // // -------------------------------------------------------------------------- void SocketStream::Write(const void *pBuffer, int NBytes) { if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, BadSocketHandle) } // Buffer in byte sized type. ASSERT(sizeof(char) == 1); const char *buffer = (char *)pBuffer; // Bytes left to send int bytesLeft = NBytes; while(bytesLeft > 0) { // Try to send. #ifdef WIN32 int sent = ::send(mSocketHandle, buffer, bytesLeft, 0); #else int sent = ::write(mSocketHandle, buffer, bytesLeft); #endif if(sent == -1) { // Error. mWriteClosed = true; // assume can't write again BOX_LOG_SYS_ERROR("Failed to write to socket"); THROW_EXCEPTION(ConnectionException, Conn_SocketWriteError); } // Knock off bytes sent bytesLeft -= sent; // Move buffer pointer buffer += sent; mBytesWritten += sent; // Need to wait until it can send again? if(bytesLeft > 0) { BOX_TRACE("Waiting to send data on socket " << mSocketHandle << " (" << bytesLeft << " of " << NBytes << " bytes left)"); // Wait for data to send. struct pollfd p; p.fd = mSocketHandle; p.events = POLLOUT; p.revents = 0; if(::poll(&p, 1, 16000 /* 16 seconds */) == -1) { // Don't exception if it's just a signal if(errno != EINTR) { BOX_LOG_SYS_ERROR("Failed to poll " "socket"); THROW_EXCEPTION(ServerException, SocketPollError) } } } } } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::Close() // Purpose: Closes connection to remote socket // Created: 2003/07/31 // // -------------------------------------------------------------------------- void SocketStream::Close() { if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, BadSocketHandle) } #ifdef WIN32 if(::closesocket(mSocketHandle) == -1) #else if(::close(mSocketHandle) == -1) #endif { BOX_LOG_SYS_ERROR("Failed to close socket"); // don't throw an exception here, assume that the socket was // already closed or closing. } mSocketHandle = INVALID_SOCKET_VALUE; } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::Shutdown(bool, bool) // Purpose: Shuts down a socket for further reading and/or writing // Created: 2003/07/31 // // -------------------------------------------------------------------------- void SocketStream::Shutdown(bool Read, bool Write) { if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, BadSocketHandle) } // Do anything? if(!Read && !Write) return; int how = SHUT_RDWR; if(Read && !Write) how = SHUT_RD; if(!Read && Write) how = SHUT_WR; // Shut it down! if(::shutdown(mSocketHandle, how) == -1) { BOX_LOG_SYS_ERROR("Failed to shutdown socket"); THROW_EXCEPTION(ConnectionException, Conn_SocketShutdownError) } } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::StreamDataLeft() // Purpose: Still capable of reading data? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool SocketStream::StreamDataLeft() { return !mReadClosed; } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::StreamClosed() // Purpose: Connection been closed? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool SocketStream::StreamClosed() { return mWriteClosed; } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::GetSocketHandle() // Purpose: Returns socket handle for this stream (derived classes only). // Will exception if there's no valid socket. // Created: 2003/08/06 // // -------------------------------------------------------------------------- tOSSocketHandle SocketStream::GetSocketHandle() { if(mSocketHandle == INVALID_SOCKET_VALUE) { THROW_EXCEPTION(ServerException, BadSocketHandle) } return mSocketHandle; } // -------------------------------------------------------------------------- // // Function // Name: SocketStream::GetPeerCredentials(uid_t &, gid_t &) // Purpose: Returns true if the peer credientials are available. // (will work on UNIX domain sockets only) // Created: 19/2/04 // // -------------------------------------------------------------------------- bool SocketStream::GetPeerCredentials(uid_t &rUidOut, gid_t &rGidOut) { #ifdef HAVE_GETPEEREID uid_t remoteEUID = 0xffff; gid_t remoteEGID = 0xffff; if(::getpeereid(mSocketHandle, &remoteEUID, &remoteEGID) == 0) { rUidOut = remoteEUID; rGidOut = remoteEGID; return true; } #endif #if HAVE_DECL_SO_PEERCRED struct ucred cred; socklen_t credLen = sizeof(cred); if(::getsockopt(mSocketHandle, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == 0) { rUidOut = cred.uid; rGidOut = cred.gid; return true; } BOX_LOG_SYS_ERROR("Failed to get peer credentials on socket"); #endif #if defined HAVE_UCRED_H && HAVE_GETPEERUCRED ucred_t *pucred = NULL; if(::getpeerucred(mSocketHandle, &pucred) == 0) { rUidOut = ucred_geteuid(pucred); rGidOut = ucred_getegid(pucred); ucred_free(pucred); if (rUidOut == -1 || rGidOut == -1) { BOX_ERROR("Failed to get peer credentials on " "socket: insufficient information"); return false; } return true; } BOX_LOG_SYS_ERROR("Failed to get peer credentials on socket"); #endif // Not available return false; } boxbackup/lib/server/ServerException.h0000664000175000017500000000411610347400657020674 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ServerException.h // Purpose: Exception // Created: 2003/07/08 // // -------------------------------------------------------------------------- #ifndef SERVEREXCEPTION__H #define SERVEREXCEPTION__H // Compatibility header #include "autogen_ServerException.h" #include "autogen_ConnectionException.h" // Rename old connection exception names to new names without Conn_ prefix // This is all because ConnectionException used to be derived from ServerException // with some funky magic with subtypes. Perhaps a little unreliable, and the // usefulness of it never really was used. #define Conn_SocketWriteError SocketWriteError #define Conn_SocketReadError SocketReadError #define Conn_SocketNameLookupError SocketNameLookupError #define Conn_SocketShutdownError SocketShutdownError #define Conn_SocketConnectError SocketConnectError #define Conn_TLSHandshakeFailed TLSHandshakeFailed #define Conn_TLSShutdownFailed TLSShutdownFailed #define Conn_TLSWriteFailed TLSWriteFailed #define Conn_TLSReadFailed TLSReadFailed #define Conn_TLSNoPeerCertificate TLSNoPeerCertificate #define Conn_TLSPeerCertificateInvalid TLSPeerCertificateInvalid #define Conn_TLSClosedWhenWriting TLSClosedWhenWriting #define Conn_TLSHandshakeTimedOut TLSHandshakeTimedOut #define Conn_Protocol_Timeout Protocol_Timeout #define Conn_Protocol_ObjTooBig Protocol_ObjTooBig #define Conn_Protocol_BadCommandRecieved Protocol_BadCommandRecieved #define Conn_Protocol_UnknownCommandRecieved Protocol_UnknownCommandRecieved #define Conn_Protocol_TriedToExecuteReplyCommand Protocol_TriedToExecuteReplyCommand #define Conn_Protocol_UnexpectedReply Protocol_UnexpectedReply #define Conn_Protocol_HandshakeFailed Protocol_HandshakeFailed #define Conn_Protocol_StreamWhenObjExpected Protocol_StreamWhenObjExpected #define Conn_Protocol_ObjWhenStreamExpected Protocol_ObjWhenStreamExpected #define Conn_Protocol_TimeOutWhenSendingStream Protocol_TimeOutWhenSendingStream #endif // SERVEREXCEPTION__H boxbackup/lib/server/LocalProcessStream.h0000664000175000017500000000101511345266202021302 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: LocalProcessStream.h // Purpose: Opens a process, and presents stdin/stdout as a stream. // Created: 12/3/04 // // -------------------------------------------------------------------------- #ifndef LOCALPROCESSSTREAM__H #define LOCALPROCESSSTREAM__H #include #include "IOStream.h" std::auto_ptr LocalProcessStream(const std::string& rCommandLine, pid_t &rPidOut); #endif // LOCALPROCESSSTREAM__H boxbackup/lib/server/WinNamedPipeStream.cpp0000664000175000017500000003535611071524474021607 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: WinNamedPipeStream.cpp // Purpose: I/O stream interface for Win32 named pipes // Created: 2005/12/07 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef WIN32 #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include "WinNamedPipeStream.h" #include "ServerException.h" #include "CommonException.h" #include "Socket.h" #include "MemLeakFindOn.h" std::string WinNamedPipeStream::sPipeNamePrefix = "\\\\.\\pipe\\"; // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::WinNamedPipeStream() // Purpose: Constructor (create stream ready for Open() call) // Created: 2005/12/07 // // -------------------------------------------------------------------------- WinNamedPipeStream::WinNamedPipeStream() : mSocketHandle(INVALID_HANDLE_VALUE), mReadableEvent(INVALID_HANDLE_VALUE), mBytesInBuffer(0), mReadClosed(false), mWriteClosed(false), mIsServer(false), mIsConnected(false) { } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::WinNamedPipeStream(HANDLE) // Purpose: Constructor (with already-connected pipe handle) // Created: 2008/10/01 // // -------------------------------------------------------------------------- WinNamedPipeStream::WinNamedPipeStream(HANDLE hNamedPipe) : mSocketHandle(hNamedPipe), mReadableEvent(INVALID_HANDLE_VALUE), mBytesInBuffer(0), mReadClosed(false), mWriteClosed(false), mIsServer(true), mIsConnected(true) { // create the Readable event mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (mReadableEvent == INVALID_HANDLE_VALUE) { BOX_ERROR("Failed to create the Readable event: " << GetErrorMessage(GetLastError())); Close(); THROW_EXCEPTION(CommonException, Internal) } // initialise the OVERLAPPED structure memset(&mReadOverlap, 0, sizeof(mReadOverlap)); mReadOverlap.hEvent = mReadableEvent; // start the first overlapped read if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), NULL, &mReadOverlap)) { DWORD err = GetLastError(); if (err != ERROR_IO_PENDING) { BOX_ERROR("Failed to start overlapped read: " << GetErrorMessage(err)); Close(); THROW_EXCEPTION(ConnectionException, Conn_SocketReadError) } } } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::~WinNamedPipeStream() // Purpose: Destructor, closes stream if open // Created: 2005/12/07 // // -------------------------------------------------------------------------- WinNamedPipeStream::~WinNamedPipeStream() { if (mSocketHandle != INVALID_HANDLE_VALUE) { try { Close(); } catch (std::exception &e) { BOX_ERROR("Caught exception while destroying " "named pipe, ignored: " << e.what()); } } } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::Accept(const std::string& rName) // Purpose: Creates a new named pipe with the given name, // and wait for a connection on it // Created: 2005/12/07 // // -------------------------------------------------------------------------- /* void WinNamedPipeStream::Accept() { if (mSocketHandle == INVALID_HANDLE_VALUE) { THROW_EXCEPTION(ServerException, BadSocketHandle); } if (mIsConnected) { THROW_EXCEPTION(ServerException, SocketAlreadyOpen); } bool connected = ConnectNamedPipe(mSocketHandle, (LPOVERLAPPED) NULL); if (!connected) { BOX_ERROR("Failed to ConnectNamedPipe(" << socket << "): " << GetErrorMessage(GetLastError())); Close(); THROW_EXCEPTION(ServerException, SocketOpenError) } mBytesInBuffer = 0; mReadClosed = false; mWriteClosed = false; mIsServer = true; // must flush and disconnect before closing mIsConnected = true; // create the Readable event mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (mReadableEvent == INVALID_HANDLE_VALUE) { BOX_ERROR("Failed to create the Readable event: " << GetErrorMessage(GetLastError())); Close(); THROW_EXCEPTION(CommonException, Internal) } // initialise the OVERLAPPED structure memset(&mReadOverlap, 0, sizeof(mReadOverlap)); mReadOverlap.hEvent = mReadableEvent; // start the first overlapped read if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), NULL, &mReadOverlap)) { DWORD err = GetLastError(); if (err != ERROR_IO_PENDING) { BOX_ERROR("Failed to start overlapped read: " << GetErrorMessage(err)); Close(); THROW_EXCEPTION(ConnectionException, Conn_SocketReadError) } } } */ // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::Connect(const std::string& rName) // Purpose: Opens a connection to a listening named pipe // Created: 2005/12/07 // // -------------------------------------------------------------------------- void WinNamedPipeStream::Connect(const std::string& rName) { if (mSocketHandle != INVALID_HANDLE_VALUE || mIsConnected) { THROW_EXCEPTION(ServerException, SocketAlreadyOpen) } std::string socket = sPipeNamePrefix + rName; mSocketHandle = CreateFileA( socket.c_str(), // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing NULL, // default security attributes OPEN_EXISTING, 0, // default attributes NULL); // no template file if (mSocketHandle == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); if (err == ERROR_PIPE_BUSY) { BOX_ERROR("Failed to connect to backup daemon: " "it is busy with another connection"); } else { BOX_ERROR("Failed to connect to backup daemon: " << GetErrorMessage(err)); } THROW_EXCEPTION(ServerException, SocketOpenError) } mReadClosed = false; mWriteClosed = false; mIsServer = false; // just close the socket mIsConnected = true; } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::Read(void *pBuffer, int NBytes) // Purpose: Reads data from stream. Maybe returns less than asked for. // Created: 2003/07/31 // // -------------------------------------------------------------------------- int WinNamedPipeStream::Read(void *pBuffer, int NBytes, int Timeout) { // TODO no support for timeouts yet if (!mIsServer && Timeout != IOStream::TimeOutInfinite) { THROW_EXCEPTION(CommonException, AssertFailed) } if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) { THROW_EXCEPTION(ServerException, BadSocketHandle) } if (mReadClosed) { THROW_EXCEPTION(ConnectionException, SocketShutdownError) } // ensure safe to cast NBytes to unsigned if (NBytes < 0) { THROW_EXCEPTION(CommonException, AssertFailed) } DWORD NumBytesRead; if (mIsServer) { // satisfy from buffer if possible, to avoid // blocking on read. bool needAnotherRead = false; if (mBytesInBuffer == 0) { // overlapped I/O completed successfully? // (wait if needed) DWORD waitResult = WaitForSingleObject( mReadOverlap.hEvent, Timeout); if (waitResult == WAIT_ABANDONED) { BOX_ERROR("Wait for command socket read " "abandoned by system"); THROW_EXCEPTION(ServerException, BadSocketHandle); } else if (waitResult == WAIT_TIMEOUT) { // wait timed out, nothing to read NumBytesRead = 0; } else if (waitResult != WAIT_OBJECT_0) { BOX_ERROR("Failed to wait for command " "socket read: unknown result " << waitResult); } // object is ready to read from else if (GetOverlappedResult(mSocketHandle, &mReadOverlap, &NumBytesRead, TRUE)) { needAnotherRead = true; } else { DWORD err = GetLastError(); if (err == ERROR_HANDLE_EOF) { mReadClosed = true; } else { if (err == ERROR_BROKEN_PIPE) { BOX_NOTICE("Control client " "disconnected"); } else { BOX_ERROR("Failed to wait for " "ReadFile to complete: " << GetErrorMessage(err)); } Close(); THROW_EXCEPTION(ConnectionException, Conn_SocketReadError) } } } else { NumBytesRead = 0; } size_t BytesToCopy = NumBytesRead + mBytesInBuffer; size_t BytesRemaining = 0; if (BytesToCopy > (size_t)NBytes) { BytesRemaining = BytesToCopy - NBytes; BytesToCopy = NBytes; } memcpy(pBuffer, mReadBuffer, BytesToCopy); memmove(mReadBuffer, mReadBuffer + BytesToCopy, BytesRemaining); mBytesInBuffer = BytesRemaining; NumBytesRead = BytesToCopy; if (needAnotherRead) { // reinitialise the OVERLAPPED structure memset(&mReadOverlap, 0, sizeof(mReadOverlap)); mReadOverlap.hEvent = mReadableEvent; } // start the next overlapped read if (needAnotherRead && !ReadFile(mSocketHandle, mReadBuffer + mBytesInBuffer, sizeof(mReadBuffer) - mBytesInBuffer, NULL, &mReadOverlap)) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { // Don't reset yet, there might be data // in the buffer waiting to be read, // will check below. // ResetEvent(mReadableEvent); } else if (err == ERROR_HANDLE_EOF) { mReadClosed = true; } else if (err == ERROR_BROKEN_PIPE) { BOX_ERROR("Control client disconnected"); mReadClosed = true; } else { BOX_ERROR("Failed to start overlapped read: " << GetErrorMessage(err)); Close(); THROW_EXCEPTION(ConnectionException, Conn_SocketReadError) } } } else { if (!ReadFile( mSocketHandle, // pipe handle pBuffer, // buffer to receive reply NBytes, // size of buffer &NumBytesRead, // number of bytes read NULL)) // not overlapped { DWORD err = GetLastError(); Close(); // ERROR_NO_DATA is a strange name for // "The pipe is being closed". No exception wanted. if (err == ERROR_NO_DATA || err == ERROR_PIPE_NOT_CONNECTED) { NumBytesRead = 0; } else { BOX_ERROR("Failed to read from control socket: " << GetErrorMessage(err)); THROW_EXCEPTION(ConnectionException, Conn_SocketReadError) } } // Closed for reading at EOF? if (NumBytesRead == 0) { mReadClosed = true; } } return NumBytesRead; } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::Write(void *pBuffer, int NBytes) // Purpose: Writes data, blocking until it's all done. // Created: 2003/07/31 // // -------------------------------------------------------------------------- void WinNamedPipeStream::Write(const void *pBuffer, int NBytes) { if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) { THROW_EXCEPTION(ServerException, BadSocketHandle) } // Buffer in byte sized type. ASSERT(sizeof(char) == 1); const char *pByteBuffer = (char *)pBuffer; int NumBytesWrittenTotal = 0; while (NumBytesWrittenTotal < NBytes) { DWORD NumBytesWrittenThisTime = 0; bool Success = WriteFile( mSocketHandle, // pipe handle pByteBuffer + NumBytesWrittenTotal, // message NBytes - NumBytesWrittenTotal, // message length &NumBytesWrittenThisTime, // bytes written this time NULL); // not overlapped if (!Success) { // ERROR_NO_DATA is a strange name for // "The pipe is being closed". DWORD err = GetLastError(); if (err != ERROR_NO_DATA) { BOX_ERROR("Failed to write to control " "socket: " << GetErrorMessage(err)); } Close(); THROW_EXCEPTION(ConnectionException, Conn_SocketWriteError) } NumBytesWrittenTotal += NumBytesWrittenThisTime; } } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::Close() // Purpose: Closes connection to remote socket // Created: 2003/07/31 // // -------------------------------------------------------------------------- void WinNamedPipeStream::Close() { if (mSocketHandle == INVALID_HANDLE_VALUE && mIsConnected) { BOX_ERROR("Named pipe: inconsistent connected state"); mIsConnected = false; } if (mSocketHandle == INVALID_HANDLE_VALUE) { THROW_EXCEPTION(ServerException, BadSocketHandle) } if (mIsServer) { if (!CancelIo(mSocketHandle)) { BOX_ERROR("Failed to cancel outstanding I/O: " << GetErrorMessage(GetLastError())); } if (mReadableEvent == INVALID_HANDLE_VALUE) { BOX_ERROR("Failed to destroy Readable event: " "invalid handle"); } else if (!CloseHandle(mReadableEvent)) { BOX_ERROR("Failed to destroy Readable event: " << GetErrorMessage(GetLastError())); } mReadableEvent = INVALID_HANDLE_VALUE; if (!FlushFileBuffers(mSocketHandle)) { BOX_ERROR("Failed to FlushFileBuffers: " << GetErrorMessage(GetLastError())); } if (!DisconnectNamedPipe(mSocketHandle)) { DWORD err = GetLastError(); if (err != ERROR_PIPE_NOT_CONNECTED) { BOX_ERROR("Failed to DisconnectNamedPipe: " << GetErrorMessage(err)); } } mIsServer = false; } bool result = CloseHandle(mSocketHandle); mSocketHandle = INVALID_HANDLE_VALUE; mIsConnected = false; mReadClosed = true; mWriteClosed = true; if (!result) { BOX_ERROR("Failed to CloseHandle: " << GetErrorMessage(GetLastError())); THROW_EXCEPTION(ServerException, SocketCloseError) } } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::StreamDataLeft() // Purpose: Still capable of reading data? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool WinNamedPipeStream::StreamDataLeft() { return !mReadClosed; } // -------------------------------------------------------------------------- // // Function // Name: WinNamedPipeStream::StreamClosed() // Purpose: Connection been closed? // Created: 2003/08/02 // // -------------------------------------------------------------------------- bool WinNamedPipeStream::StreamClosed() { return mWriteClosed; } // -------------------------------------------------------------------------- // // Function // Name: IOStream::WriteAllBuffered() // Purpose: Ensures that any data which has been buffered is written to the stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- void WinNamedPipeStream::WriteAllBuffered() { if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) { THROW_EXCEPTION(ServerException, BadSocketHandle) } if (!FlushFileBuffers(mSocketHandle)) { BOX_ERROR("Failed to FlushFileBuffers: " << GetErrorMessage(GetLastError())); } } #endif // WIN32 boxbackup/lib/server/ProtocolObject.h0000664000175000017500000000217310347400657020500 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ProtocolObject.h // Purpose: Protocol object base class // Created: 2003/08/19 // // -------------------------------------------------------------------------- #ifndef PROTOCOLOBJECT__H #define PROTOCOLOBJECT__H class Protocol; // -------------------------------------------------------------------------- // // Class // Name: ProtocolObject // Purpose: Basic object representation of objects to pass through a Protocol session // Created: 2003/08/19 // // -------------------------------------------------------------------------- class ProtocolObject { public: ProtocolObject(); virtual ~ProtocolObject(); ProtocolObject(const ProtocolObject &rToCopy); // Info about this object virtual int GetType() const; virtual bool IsError(int &rTypeOut, int &rSubTypeOut) const; virtual bool IsConversationEnd() const; // reading and writing with Protocol objects virtual void SetPropertiesFromStreamData(Protocol &rProtocol); virtual void WritePropertiesToStreamData(Protocol &rProtocol) const; }; #endif // PROTOCOLOBJECT__H boxbackup/lib/server/Socket.cpp0000664000175000017500000001075211127624055017332 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Socket.cpp // Purpose: Socket related stuff // Created: 2003/07/31 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #ifndef WIN32 #include #include #include #include #endif #include #include #include "Socket.h" #include "ServerException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: Socket::NameLookupToSockAddr(SocketAllAddr &, int, // char *, int) // Purpose: Sets up a sockaddr structure given a name and type // Created: 2003/07/31 // // -------------------------------------------------------------------------- void Socket::NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain, enum Type Type, const std::string& rName, int Port, int &rSockAddrLenOut) { int sockAddrLen = 0; switch(Type) { case TypeINET: sockDomain = AF_INET; { // Lookup hostname struct hostent *phost = ::gethostbyname(rName.c_str()); if(phost != NULL) { if(phost->h_addr_list[0] != 0) { sockAddrLen = sizeof(addr.sa_inet); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sa_inet.sin_len = sizeof(addr.sa_inet); #endif addr.sa_inet.sin_family = PF_INET; addr.sa_inet.sin_port = htons(Port); addr.sa_inet.sin_addr = *((in_addr*)phost->h_addr_list[0]); for(unsigned int l = 0; l < sizeof(addr.sa_inet.sin_zero); ++l) { addr.sa_inet.sin_zero[l] = 0; } } else { THROW_EXCEPTION(ConnectionException, Conn_SocketNameLookupError); } } else { THROW_EXCEPTION(ConnectionException, Conn_SocketNameLookupError); } } break; #ifndef WIN32 case TypeUNIX: sockDomain = AF_UNIX; { // Check length of name is OK unsigned int nameLen = rName.length(); if(nameLen >= (sizeof(addr.sa_unix.sun_path) - 1)) { THROW_EXCEPTION(ServerException, SocketNameUNIXPathTooLong); } sockAddrLen = nameLen + (((char*)(&(addr.sa_unix.sun_path[0]))) - ((char*)(&addr.sa_unix))); #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sa_unix.sun_len = sockAddrLen; #endif addr.sa_unix.sun_family = PF_UNIX; ::strncpy(addr.sa_unix.sun_path, rName.c_str(), sizeof(addr.sa_unix.sun_path) - 1); addr.sa_unix.sun_path[sizeof(addr.sa_unix.sun_path)-1] = 0; } break; #endif default: THROW_EXCEPTION(CommonException, BadArguments) break; } // Return size of structure to caller rSockAddrLenOut = sockAddrLen; } // -------------------------------------------------------------------------- // // Function // Name: Socket::LogIncomingConnection(const struct sockaddr *, socklen_t) // Purpose: Writes a message logging the connection to syslog // Created: 2003/08/01 // // -------------------------------------------------------------------------- void Socket::LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen) { if(addr == NULL) {THROW_EXCEPTION(CommonException, BadArguments)} switch(addr->sa_family) { case AF_UNIX: BOX_INFO("Incoming connection from local (UNIX socket)"); break; case AF_INET: { sockaddr_in *a = (sockaddr_in*)addr; BOX_INFO("Incoming connection from " << inet_ntoa(a->sin_addr) << " port " << ntohs(a->sin_port)); } break; default: BOX_WARNING("Incoming connection of unknown type"); break; } } // -------------------------------------------------------------------------- // // Function // Name: Socket::IncomingConnectionLogMessage(const struct sockaddr *, socklen_t) // Purpose: Returns a string for use in log messages // Created: 2003/08/01 // // -------------------------------------------------------------------------- std::string Socket::IncomingConnectionLogMessage(const struct sockaddr *addr, socklen_t addrlen) { if(addr == NULL) {THROW_EXCEPTION(CommonException, BadArguments)} switch(addr->sa_family) { case AF_UNIX: return std::string("Incoming connection from local (UNIX socket)"); break; case AF_INET: { char msg[256]; // more than enough sockaddr_in *a = (sockaddr_in*)addr; sprintf(msg, "Incoming connection from %s port %d", inet_ntoa(a->sin_addr), ntohs(a->sin_port)); return std::string(msg); } break; default: return std::string("Incoming connection of unknown type"); break; } // Dummy. return std::string(); } boxbackup/lib/server/OverlappedIO.h0000664000175000017500000000164611071517477020111 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: OverlappedIO.h // Purpose: Windows overlapped IO handle guard // Created: 2008/09/30 // // -------------------------------------------------------------------------- #ifndef OVERLAPPEDIO__H #define OVERLAPPEDIO__H class OverlappedIO { public: OVERLAPPED mOverlapped; OverlappedIO() { ZeroMemory(&mOverlapped, sizeof(mOverlapped)); mOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (mOverlapped.hEvent == INVALID_HANDLE_VALUE) { BOX_LOG_WIN_ERROR("Failed to create event for " "overlapped I/O"); THROW_EXCEPTION(ServerException, BadSocketHandle); } } ~OverlappedIO() { if (CloseHandle(mOverlapped.hEvent) != TRUE) { BOX_LOG_WIN_ERROR("Failed to delete event for " "overlapped I/O"); THROW_EXCEPTION(ServerException, BadSocketHandle); } } }; #endif // !OVERLAPPEDIO__H boxbackup/lib/server/SocketStreamTLS.cpp0000664000175000017500000002677211127624055021102 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SocketStreamTLS.cpp // Purpose: Socket stream encrpyted and authenticated by TLS // Created: 2003/08/06 // // -------------------------------------------------------------------------- #include "Box.h" #define TLS_CLASS_IMPLEMENTATION_CPP #include #include #include #include #ifndef WIN32 #include #endif #include "SocketStreamTLS.h" #include "SSLLib.h" #include "ServerException.h" #include "TLSContext.h" #include "BoxTime.h" #include "MemLeakFindOn.h" // Allow 5 minutes to handshake (in milliseconds) #define TLS_HANDSHAKE_TIMEOUT (5*60*1000) // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::SocketStreamTLS() // Purpose: Constructor // Created: 2003/08/06 // // -------------------------------------------------------------------------- SocketStreamTLS::SocketStreamTLS() : mpSSL(0), mpBIO(0) { ResetCounters(); } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::SocketStreamTLS(int) // Purpose: Constructor, taking previously connected socket // Created: 2003/08/06 // // -------------------------------------------------------------------------- SocketStreamTLS::SocketStreamTLS(int socket) : SocketStream(socket), mpSSL(0), mpBIO(0) { } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::~SocketStreamTLS() // Purpose: Destructor // Created: 2003/08/06 // // -------------------------------------------------------------------------- SocketStreamTLS::~SocketStreamTLS() { if(mpSSL) { // Attempt to close to avoid problems Close(); // And if that didn't work... if(mpSSL) { ::SSL_free(mpSSL); mpSSL = 0; mpBIO = 0; // implicity freed by the SSL_free call } } // If we only got to creating that BIO. if(mpBIO) { ::BIO_free(mpBIO); mpBIO = 0; } } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::Open(const TLSContext &, int, const char *, int) // Purpose: Open connection, and perform TLS handshake // Created: 2003/08/06 // // -------------------------------------------------------------------------- void SocketStreamTLS::Open(const TLSContext &rContext, Socket::Type Type, const std::string& rName, int Port) { SocketStream::Open(Type, rName, Port); Handshake(rContext); ResetCounters(); } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::Handshake(const TLSContext &, bool) // Purpose: Perform TLS handshake // Created: 2003/08/06 // // -------------------------------------------------------------------------- void SocketStreamTLS::Handshake(const TLSContext &rContext, bool IsServer) { if(mpBIO || mpSSL) {THROW_EXCEPTION(ServerException, TLSAlreadyHandshaked)} // Create a BIO for this socket mpBIO = ::BIO_new(::BIO_s_socket()); if(mpBIO == 0) { SSLLib::LogError("creating socket bio"); THROW_EXCEPTION(ServerException, TLSAllocationFailed) } tOSSocketHandle socket = GetSocketHandle(); BIO_set_fd(mpBIO, socket, BIO_NOCLOSE); // Then the SSL object mpSSL = ::SSL_new(rContext.GetRawContext()); if(mpSSL == 0) { SSLLib::LogError("creating SSL object"); THROW_EXCEPTION(ServerException, TLSAllocationFailed) } // Make the socket non-blocking so timeouts on Read work #ifdef WIN32 u_long nonblocking = 1; ioctlsocket(socket, FIONBIO, &nonblocking); #else // !WIN32 // This is more portable than using ioctl with FIONBIO int statusFlags = 0; if(::fcntl(socket, F_GETFL, &statusFlags) < 0 || ::fcntl(socket, F_SETFL, statusFlags | O_NONBLOCK) == -1) { THROW_EXCEPTION(ServerException, SocketSetNonBlockingFailed) } #endif // FIXME: This is less portable than the above. However, it MAY be needed // for cygwin, which has/had bugs with fcntl // // int nonblocking = true; // if(::ioctl(socket, FIONBIO, &nonblocking) == -1) // { // THROW_EXCEPTION(ServerException, SocketSetNonBlockingFailed) // } // Set the two to know about each other ::SSL_set_bio(mpSSL, mpBIO, mpBIO); bool waitingForHandshake = true; while(waitingForHandshake) { // Attempt to do the handshake int r = 0; if(IsServer) { r = ::SSL_accept(mpSSL); } else { r = ::SSL_connect(mpSSL); } // check return code int se; switch((se = ::SSL_get_error(mpSSL, r))) { case SSL_ERROR_NONE: // No error, handshake succeeded waitingForHandshake = false; break; case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: // wait for the requried data if(WaitWhenRetryRequired(se, TLS_HANDSHAKE_TIMEOUT) == false) { // timed out THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeTimedOut) } break; default: // (and SSL_ERROR_ZERO_RETURN) // Error occured if(IsServer) { SSLLib::LogError("accepting connection"); THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeFailed) } else { SSLLib::LogError("connecting"); THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeFailed) } } } // And that's it } // -------------------------------------------------------------------------- // // Function // Name: WaitWhenRetryRequired(int, int) // Purpose: Waits until the condition required by the TLS layer is met. // Returns true if the condition is met, false if timed out. // Created: 2003/08/15 // // -------------------------------------------------------------------------- bool SocketStreamTLS::WaitWhenRetryRequired(int SSLErrorCode, int Timeout) { struct pollfd p; p.fd = GetSocketHandle(); switch(SSLErrorCode) { case SSL_ERROR_WANT_READ: p.events = POLLIN; break; case SSL_ERROR_WANT_WRITE: p.events = POLLOUT; break; default: // Not good! THROW_EXCEPTION(ServerException, Internal) break; } p.revents = 0; int64_t start, end; start = BoxTimeToMilliSeconds(GetCurrentBoxTime()); end = start + Timeout; int result; do { int64_t now = BoxTimeToMilliSeconds(GetCurrentBoxTime()); int poll_timeout = (int)(end - now); if (poll_timeout < 0) poll_timeout = 0; if (Timeout == IOStream::TimeOutInfinite) { poll_timeout = INFTIM; } result = ::poll(&p, 1, poll_timeout); } while(result == -1 && errno == EINTR); switch(result) { case -1: // error - Bad! THROW_EXCEPTION(ServerException, SocketPollError) break; case 0: // Condition not met, timed out return false; break; default: // good to go! return true; break; } return true; } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::Read(void *, int, int Timeout) // Purpose: See base class // Created: 2003/08/06 // // -------------------------------------------------------------------------- int SocketStreamTLS::Read(void *pBuffer, int NBytes, int Timeout) { if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} // Make sure zero byte reads work as expected if(NBytes == 0) { return 0; } while(true) { int r = ::SSL_read(mpSSL, pBuffer, NBytes); int se; switch((se = ::SSL_get_error(mpSSL, r))) { case SSL_ERROR_NONE: // No error, return number of bytes read mBytesRead += r; return r; break; case SSL_ERROR_ZERO_RETURN: // Connection closed MarkAsReadClosed(); return 0; break; case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: // wait for the required data // Will only get once around this loop, so don't need to calculate timeout values if(WaitWhenRetryRequired(se, Timeout) == false) { // timed out return 0; } break; default: SSLLib::LogError("reading"); THROW_EXCEPTION(ConnectionException, Conn_TLSReadFailed) break; } } } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::Write(const void *, int) // Purpose: See base class // Created: 2003/08/06 // // -------------------------------------------------------------------------- void SocketStreamTLS::Write(const void *pBuffer, int NBytes) { if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} // Make sure zero byte writes work as expected if(NBytes == 0) { return; } // from man SSL_write // // SSL_write() will only return with success, when the // complete contents of buf of length num has been written. // // So no worries about partial writes and moving the buffer around while(true) { // try the write int r = ::SSL_write(mpSSL, pBuffer, NBytes); int se; switch((se = ::SSL_get_error(mpSSL, r))) { case SSL_ERROR_NONE: // No error, data sent, return success mBytesWritten += r; return; break; case SSL_ERROR_ZERO_RETURN: // Connection closed MarkAsWriteClosed(); THROW_EXCEPTION(ConnectionException, Conn_TLSClosedWhenWriting) break; case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: // wait for the requried data { #ifndef BOX_RELEASE_BUILD bool conditionmet = #endif WaitWhenRetryRequired(se, IOStream::TimeOutInfinite); ASSERT(conditionmet); } break; default: SSLLib::LogError("writing"); THROW_EXCEPTION(ConnectionException, Conn_TLSWriteFailed) break; } } } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::Close() // Purpose: See base class // Created: 2003/08/06 // // -------------------------------------------------------------------------- void SocketStreamTLS::Close() { if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} // Base class to close SocketStream::Close(); // Free resources ::SSL_free(mpSSL); mpSSL = 0; mpBIO = 0; // implicitly freed by SSL_free } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::Shutdown() // Purpose: See base class // Created: 2003/08/06 // // -------------------------------------------------------------------------- void SocketStreamTLS::Shutdown(bool Read, bool Write) { if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} if(::SSL_shutdown(mpSSL) < 0) { SSLLib::LogError("shutting down"); THROW_EXCEPTION(ConnectionException, Conn_TLSShutdownFailed) } // Don't ask the base class to shutdown -- BIO does this, apparently. } // -------------------------------------------------------------------------- // // Function // Name: SocketStreamTLS::GetPeerCommonName() // Purpose: Returns the common name of the other end of the connection // Created: 2003/08/06 // // -------------------------------------------------------------------------- std::string SocketStreamTLS::GetPeerCommonName() { if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} // Get certificate X509 *cert = ::SSL_get_peer_certificate(mpSSL); if(cert == 0) { ::X509_free(cert); THROW_EXCEPTION(ConnectionException, Conn_TLSNoPeerCertificate) } // Subject details X509_NAME *subject = ::X509_get_subject_name(cert); if(subject == 0) { ::X509_free(cert); THROW_EXCEPTION(ConnectionException, Conn_TLSPeerCertificateInvalid) } // Common name char commonName[256]; if(::X509_NAME_get_text_by_NID(subject, NID_commonName, commonName, sizeof(commonName)) <= 0) { ::X509_free(cert); THROW_EXCEPTION(ConnectionException, Conn_TLSPeerCertificateInvalid) } // Terminate just in case commonName[sizeof(commonName)-1] = '\0'; // Done. return std::string(commonName); } boxbackup/lib/server/ServerException.txt0000664000175000017500000000233010347400657021260 0ustar siretartsiretartEXCEPTION Server 3 # for historic reasons, some codes are not used Internal 0 FailedToLoadConfiguration 1 DaemoniseFailed 2 AlreadyDaemonConstructed 3 BadSocketHandle 4 DupError 5 SocketAlreadyOpen 8 SocketOpenError 10 SocketPollError 11 SocketCloseError 13 SocketNameUNIXPathTooLong 14 SocketBindError 16 Check the ListenAddresses directive in your config file -- must refer to local IP addresses only SocketAcceptError 17 ServerStreamBadListenAddrs 18 ServerForkError 19 ServerWaitOnChildError 20 TooManySocketsInMultiListen 21 There is a limit on how many addresses you can listen on simulatiously. ServerStreamTooManyListenAddresses 22 TLSContextNotInitialised 23 TLSAllocationFailed 24 TLSLoadCertificatesFailed 25 TLSLoadPrivateKeyFailed 26 TLSLoadTrustedCAsFailed 27 TLSSetCiphersFailed 28 SSLLibraryInitialisationError 29 TLSNoSSLObject 31 TLSAlreadyHandshaked 35 SocketSetNonBlockingFailed 40 Protocol_BadUsage 43 Protocol_UnsuitableStreamTypeForSending 51 CantWriteToProtocolUncertainStream 53 ProtocolUncertainStreamBadBlockHeader 54 SocketPairFailed 55 CouldNotChangePIDFileOwner 56 SSLRandomInitFailed 57 Read from /dev/*random device failed boxbackup/lib/server/Protocol.h0000664000175000017500000001237210347400657017353 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Protocol.h // Purpose: Generic protocol support // Created: 2003/08/19 // // -------------------------------------------------------------------------- #ifndef PROTOCOL__H #define PROTOCOL__H #include class IOStream; #include "ProtocolObject.h" #include #include #include // default timeout is 15 minutes #define PROTOCOL_DEFAULT_TIMEOUT (15*60*1000) // 16 default maximum object size -- should be enough #define PROTOCOL_DEFAULT_MAXOBJSIZE (16*1024) // -------------------------------------------------------------------------- // // Class // Name: Protocol // Purpose: Generic command / response protocol support // Created: 2003/08/19 // // -------------------------------------------------------------------------- class Protocol { public: Protocol(IOStream &rStream); virtual ~Protocol(); private: Protocol(const Protocol &rToCopy); public: void Handshake(); std::auto_ptr Receive(); void Send(const ProtocolObject &rObject); std::auto_ptr ReceiveStream(); void SendStream(IOStream &rStream); enum { NoError = -1, UnknownError = 0 }; bool GetLastError(int &rTypeOut, int &rSubTypeOut); // -------------------------------------------------------------------------- // // Function // Name: Protocol::SetTimeout(int) // Purpose: Sets the timeout for sending and reciving // Created: 2003/08/19 // // -------------------------------------------------------------------------- void SetTimeout(int NewTimeout) {mTimeout = NewTimeout;} // -------------------------------------------------------------------------- // // Function // Name: Protocol::GetTimeout() // Purpose: Get current timeout for sending and receiving // Created: 2003/09/06 // // -------------------------------------------------------------------------- int GetTimeout() {return mTimeout;} // -------------------------------------------------------------------------- // // Function // Name: Protocol::SetMaxObjectSize(int) // Purpose: Sets the maximum size of an object which will be accepted // Created: 2003/08/19 // // -------------------------------------------------------------------------- void SetMaxObjectSize(unsigned int NewMaxObjSize) {mMaxObjectSize = NewMaxObjSize;} // For ProtocolObject derived classes void Read(void *Buffer, int Size); void Read(std::string &rOut, int Size); void Read(int64_t &rOut); void Read(int32_t &rOut); void Read(int16_t &rOut); void Read(int8_t &rOut); void Read(bool &rOut) {int8_t read; Read(read); rOut = (read == true);} void Read(std::string &rOut); template void Read(type &rOut) { rOut.ReadFromProtocol(*this); } // -------------------------------------------------------------------------- // // Function // Name: Protocol::ReadVector(std::vector<> &) // Purpose: Reads a vector/list of items from the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- template void ReadVector(std::vector &rOut) { rOut.clear(); int16_t num = 0; Read(num); for(int16_t n = 0; n < num; ++n) { type v; Read(v); rOut.push_back(v); } } void Write(const void *Buffer, int Size); void Write(int64_t Value); void Write(int32_t Value); void Write(int16_t Value); void Write(int8_t Value); void Write(bool Value) {int8_t write = Value; Write(write);} void Write(const std::string &rValue); template void Write(const type &rValue) { rValue.WriteToProtocol(*this); } template // -------------------------------------------------------------------------- // // Function // Name: Protocol::WriteVector(const std::vector<> &) // Purpose: Writes a vector/list of items from the stream // Created: 2003/08/19 // // -------------------------------------------------------------------------- void WriteVector(const std::vector &rValue) { int16_t num = rValue.size(); Write(num); for(int16_t n = 0; n < num; ++n) { Write(rValue[n]); } } public: static const uint16_t sProtocolStreamHeaderLengths[256]; enum { ProtocolStreamHeader_EndOfStream = 0, ProtocolStreamHeader_MaxEncodedSizeValue = 252, ProtocolStreamHeader_SizeIs64k = 253, ProtocolStreamHeader_Reserved1 = 254, ProtocolStreamHeader_Reserved2 = 255 }; enum { ProtocolStream_SizeUncertain = 0xffffffff }; protected: virtual std::auto_ptr MakeProtocolObject(int ObjType) = 0; virtual const char *GetIdentString() = 0; void SetError(int Type, int SubType) {mLastErrorType = Type; mLastErrorSubType = SubType;} void CheckAndReadHdr(void *hdr); // don't use type here to avoid dependency // Will be used for logging virtual void InformStreamReceiving(u_int32_t Size); virtual void InformStreamSending(u_int32_t Size); private: void EnsureBufferAllocated(int Size); int SendStreamSendBlock(uint8_t *Block, int BytesInBlock); private: IOStream &mrStream; bool mHandshakeDone; unsigned int mMaxObjectSize; int mTimeout; char *mpBuffer; int mBufferSize; int mReadOffset; int mWriteOffset; int mValidDataSize; int mLastErrorType; int mLastErrorSubType; }; #endif // PROTOCOL__H boxbackup/lib/server/makeprotocol.pl.in0000775000175000017500000005456211102147075021044 0ustar siretartsiretart#!@PERL@ use strict; use lib "../../infrastructure"; use BoxPlatform; # Make protocol C++ classes from a protocol description file # built in type info (values are is basic type, C++ typename) # may get stuff added to it later if protocol uses extra types my %translate_type_info = ( 'int64' => [1, 'int64_t'], 'int32' => [1, 'int32_t'], 'int16' => [1, 'int16_t'], 'int8' => [1, 'int8_t'], 'bool' => [1, 'bool'], 'string' => [0, 'std::string'] ); # built in instructions for logging various types # may be added to my %log_display_types = ( 'int64' => ['0x%llx', 'VAR'], 'int32' => ['0x%x', 'VAR'], 'int16' => ['0x%x', 'VAR'], 'int8' => ['0x%x', 'VAR'], 'bool' => ['%s', '((VAR)?"true":"false")'], 'string' => ['%s', 'VAR.c_str()'] ); my ($type, $file) = @ARGV; if($type ne 'Server' && $type ne 'Client') { die "Neither Server or Client is specified on command line\n"; } open IN, $file or die "Can't open input file $file\n"; print "Making $type protocol classes from $file...\n"; my @extra_header_files; my $implement_syslog = 0; my $implement_filelog = 0; # read attributes my %attr; while() { # get and clean line my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\A\s+//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; last if $l eq 'BEGIN_OBJECTS'; my ($k,$v) = split /\s+/,$l,2; if($k eq 'ClientType') { add_type($v) if $type eq 'Client'; } elsif($k eq 'ServerType') { add_type($v) if $type eq 'Server'; } elsif($k eq 'ImplementLog') { my ($log_if_type,$log_type) = split /\s+/,$v; if($type eq $log_if_type) { if($log_type eq 'syslog') { $implement_syslog = 1; } elsif($log_type eq 'file') { $implement_filelog = 1; } else { printf("ERROR: Unknown log type for implementation: $log_type\n"); exit(1); } } } elsif($k eq 'LogTypeToText') { my ($log_if_type,$type_name,$printf_format,$arg_template) = split /\s+/,$v; if($type eq $log_if_type) { $log_display_types{$type_name} = [$printf_format,$arg_template] } } else { $attr{$k} = $v; } } sub add_type { my ($protocol_name, $cpp_name, $header_file) = split /\s+/,$_[0]; $translate_type_info{$protocol_name} = [0, $cpp_name]; push @extra_header_files, $header_file; } # check attributes for(qw/Name ServerContextClass IdentString/) { if(!exists $attr{$_}) { die "Attribute $_ is required, but not specified\n"; } } my $protocol_name = $attr{'Name'}; my ($context_class, $context_class_inc) = split /\s+/,$attr{'ServerContextClass'}; my $ident_string = $attr{'IdentString'}; my $current_cmd = ''; my %cmd_contents; my %cmd_attributes; my %cmd_constants; my %cmd_id; my @cmd_list; # read in the command definitions while() { # get and clean line my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; # definitions or new command thing? if($l =~ m/\A\s+/) { die "No command defined yet" if $current_cmd eq ''; # definition of component $l =~ s/\A\s+//; my ($type,$name,$value) = split /\s+/,$l; if($type eq 'CONSTANT') { push @{$cmd_constants{$current_cmd}},"$name = $value" } else { push @{$cmd_contents{$current_cmd}},$type,$name; } } else { # new command my ($name,$id,@attributes) = split /\s+/,$l; $cmd_attributes{$name} = [@attributes]; $cmd_id{$name} = int($id); $current_cmd = $name; push @cmd_list,$name; } } close IN; # open files my $h_filename = 'autogen_'.$protocol_name.'Protocol'.$type.'.h'; open CPP,'>autogen_'.$protocol_name.'Protocol'.$type.'.cpp'; open H,">$h_filename"; print CPP <<__E; // Auto-generated file -- do not edit #include "Box.h" #include #include "$h_filename" #include "IOStream.h" __E if($implement_syslog) { print H < #endif EOF } my $guardname = uc 'AUTOGEN_'.$protocol_name.'Protocol'.$type.'_H'; print H <<__E; // Auto-generated file -- do not edit #ifndef $guardname #define $guardname #include "Protocol.h" #include "ProtocolObject.h" #include "ServerException.h" class IOStream; __E if($implement_filelog) { print H qq~#include \n~; } # extra headers for(@extra_header_files) { print H qq~#include "$_"\n~ } print H "\n"; if($type eq 'Server') { # need utils file for the server print H '#include "Utils.h"',"\n\n" } my $derive_objects_from = 'ProtocolObject'; my $objects_extra_h = ''; my $objects_extra_cpp = ''; if($type eq 'Server') { # define the context print H "class $context_class;\n\n"; print CPP "#include \"$context_class_inc\"\n\n"; # change class we derive the objects from $derive_objects_from = $protocol_name.'ProtocolObject'; $objects_extra_h = <<__E; virtual std::auto_ptr DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext); __E $objects_extra_cpp = <<__E; std::auto_ptr ${derive_objects_from}::DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext) { THROW_EXCEPTION(ConnectionException, Conn_Protocol_TriedToExecuteReplyCommand) } __E } print CPP qq~#include "MemLeakFindOn.h"\n~; if($type eq 'Client' && ($implement_syslog || $implement_filelog)) { # change class we derive the objects from $derive_objects_from = $protocol_name.'ProtocolObjectCl'; } if($implement_syslog) { $objects_extra_h .= <<__E; virtual void LogSysLog(const char *Action) const = 0; __E } if($implement_filelog) { $objects_extra_h .= <<__E; virtual void LogFile(const char *Action, FILE *file) const = 0; __E } if($derive_objects_from ne 'ProtocolObject') { # output a definition for the protocol object derived class print H <<__E; class ${protocol_name}ProtocolServer; class $derive_objects_from : public ProtocolObject { public: $derive_objects_from(); virtual ~$derive_objects_from(); $derive_objects_from(const $derive_objects_from &rToCopy); $objects_extra_h }; __E # and some cpp definitions print CPP <<__E; ${derive_objects_from}::${derive_objects_from}() { } ${derive_objects_from}::~${derive_objects_from}() { } ${derive_objects_from}::${derive_objects_from}(const $derive_objects_from &rToCopy) { } $objects_extra_cpp __E } my $classname_base = $protocol_name.'Protocol'.$type; # output the classes for my $cmd (@cmd_list) { print H <<__E; class $classname_base$cmd : public $derive_objects_from { public: $classname_base$cmd(); $classname_base$cmd(const $classname_base$cmd &rToCopy); ~$classname_base$cmd(); int GetType() const; enum { TypeID = $cmd_id{$cmd} }; __E # constants if(exists $cmd_constants{$cmd}) { print H "\tenum\n\t{\n\t\t"; print H join(",\n\t\t",@{$cmd_constants{$cmd}}); print H "\n\t};\n"; } # flags if(obj_is_type($cmd,'EndsConversation')) { print H "\tbool IsConversationEnd() const;\n"; } if(obj_is_type($cmd,'IsError')) { print H "\tbool IsError(int &rTypeOut, int &rSubTypeOut) const;\n"; print H "\tstd::string GetMessage() const;\n"; } if($type eq 'Server' && obj_is_type($cmd, 'Command')) { print H "\tstd::auto_ptr DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext); // IMPLEMENT THIS\n" } # want to be able to read from streams? my $read_from_streams = (obj_is_type($cmd,'Command') && $type eq 'Server') || (obj_is_type($cmd,'Reply') && $type eq 'Client'); my $write_to_streams = (obj_is_type($cmd,'Command') && $type eq 'Client') || (obj_is_type($cmd,'Reply') && $type eq 'Server'); if($read_from_streams) { print H "\tvoid SetPropertiesFromStreamData(Protocol &rProtocol);\n"; # write Get functions for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); print H "\t".translate_type_to_arg_type($ty)." Get$nm() {return m$nm;}\n"; } } my $param_con_args = ''; if($write_to_streams) { # extra constructor? if($#{$cmd_contents{$cmd}} >= 0) { my @a; for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); push @a,translate_type_to_arg_type($ty)." $nm"; } $param_con_args = join(', ',@a); print H "\t$classname_base$cmd(".$param_con_args.");\n"; } print H "\tvoid WritePropertiesToStreamData(Protocol &rProtocol) const;\n"; # set functions for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); print H "\tvoid Set$nm(".translate_type_to_arg_type($ty)." $nm) {m$nm = $nm;}\n"; } } if($implement_syslog) { print H "\tvirtual void LogSysLog(const char *Action) const;\n"; } if($implement_filelog) { print H "\tvirtual void LogFile(const char *Action, FILE *file) const;\n"; } # write member variables and setup for cpp file my @def_constructor_list; my @copy_constructor_list; my @param_constructor_list; print H "private:\n"; for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); print H "\t".translate_type_to_member_type($ty)." m$nm;\n"; my ($basic,$typename) = translate_type($ty); if($basic) { push @def_constructor_list, "m$nm(0)"; } push @copy_constructor_list, "m$nm(rToCopy.m$nm)"; push @param_constructor_list, "m$nm($nm)"; } # finish off print H "};\n\n"; # now the cpp file... my $def_con_vars = join(",\n\t ",@def_constructor_list); $def_con_vars = "\n\t: ".$def_con_vars if $def_con_vars ne ''; my $copy_con_vars = join(",\n\t ",@copy_constructor_list); $copy_con_vars = "\n\t: ".$copy_con_vars if $copy_con_vars ne ''; my $param_con_vars = join(",\n\t ",@param_constructor_list); $param_con_vars = "\n\t: ".$param_con_vars if $param_con_vars ne ''; my $class = "$classname_base$cmd".'::'; print CPP <<__E; $class$classname_base$cmd()$def_con_vars { } $class$classname_base$cmd(const $classname_base$cmd &rToCopy)$copy_con_vars { } $class~$classname_base$cmd() { } int ${class}GetType() const { return $cmd_id{$cmd}; } __E if($read_from_streams) { print CPP "void ${class}SetPropertiesFromStreamData(Protocol &rProtocol)\n{\n"; for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); if($ty =~ m/\Avector/) { print CPP "\trProtocol.ReadVector(m$nm);\n"; } else { print CPP "\trProtocol.Read(m$nm);\n"; } } print CPP "}\n"; } if($write_to_streams) { # implement extra constructor? if($param_con_vars ne '') { print CPP "$class$classname_base$cmd($param_con_args)$param_con_vars\n{\n}\n"; } print CPP "void ${class}WritePropertiesToStreamData(Protocol &rProtocol) const\n{\n"; for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); if($ty =~ m/\Avector/) { print CPP "\trProtocol.WriteVector(m$nm);\n"; } else { print CPP "\trProtocol.Write(m$nm);\n"; } } print CPP "}\n"; } if(obj_is_type($cmd,'EndsConversation')) { print CPP "bool ${class}IsConversationEnd() const\n{\n\treturn true;\n}\n"; } if(obj_is_type($cmd,'IsError')) { # get parameters my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError'); print CPP <<__E; bool ${class}IsError(int &rTypeOut, int &rSubTypeOut) const { rTypeOut = m$mem_type; rSubTypeOut = m$mem_subtype; return true; } std::string ${class}GetMessage() const { switch(m$mem_subtype) { __E foreach my $const (@{$cmd_constants{$cmd}}) { next unless $const =~ /^Err_(.*)/; my $shortname = $1; $const =~ s/ = .*//; print CPP <<__E; case $const: return "$shortname"; __E } print CPP <<__E; default: std::ostringstream out; out << "Unknown subtype " << m$mem_subtype; return out.str(); } } __E } if($implement_syslog) { my ($log) = make_log_strings_framework($cmd); print CPP <<__E; void ${class}LogSysLog(const char *Action) const { BOX_TRACE($log); } __E } if($implement_filelog) { my ($log) = make_log_strings_framework($cmd); print CPP <<__E; void ${class}LogFile(const char *Action, FILE *File) const { std::ostringstream oss; oss << $log; ::fprintf(File, "%s\\n", oss.str().c_str()); ::fflush(File); } __E } } # finally, the protocol object itself print H <<__E; class $classname_base : public Protocol { public: $classname_base(IOStream &rStream); virtual ~$classname_base(); std::auto_ptr<$derive_objects_from> Receive(); void Send(const ${derive_objects_from} &rObject); __E if($implement_syslog) { print H "\tvoid SetLogToSysLog(bool Log = false) {mLogToSysLog = Log;}\n"; } if($implement_filelog) { print H "\tvoid SetLogToFile(FILE *File = 0) {mLogToFile = File;}\n"; } if($type eq 'Server') { # need to put in the conversation function print H "\tvoid DoServer($context_class &rContext);\n\n"; # and the send vector thing print H "\tvoid SendStreamAfterCommand(IOStream *pStream);\n\n"; } if($type eq 'Client') { # add plain object taking query functions my $with_params; for my $cmd (@cmd_list) { if(obj_is_type($cmd,'Command')) { my $has_stream = obj_is_type($cmd,'StreamWithCommand'); my $argextra = $has_stream?', IOStream &rStream':''; my $queryextra = $has_stream?', rStream':''; my $reply = obj_get_type_params($cmd,'Command'); print H "\tstd::auto_ptr<$classname_base$reply> Query(const $classname_base$cmd &rQuery$argextra);\n"; my @a; my @na; for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); push @a,translate_type_to_arg_type($ty)." $nm"; push @na,"$nm"; } my $ar = join(', ',@a); my $nar = join(', ',@na); $nar = "($nar)" if $nar ne ''; $with_params .= "\tinline std::auto_ptr<$classname_base$reply> Query$cmd($ar$argextra)\n\t{\n"; $with_params .= "\t\t$classname_base$cmd send$nar;\n"; $with_params .= "\t\treturn Query(send$queryextra);\n"; $with_params .= "\t}\n"; } } # quick hack to correct bad argument lists for commands with zero paramters but with streams $with_params =~ s/\(, /(/g; print H "\n",$with_params,"\n"; } print H <<__E; private: $classname_base(const $classname_base &rToCopy); __E if($type eq 'Server') { # need to put the streams to send vector print H "\tstd::vector mStreamsToSend;\n\tvoid DeleteStreamsToSend();\n"; } if($implement_filelog || $implement_syslog) { print H <<__E; virtual void InformStreamReceiving(u_int32_t Size); virtual void InformStreamSending(u_int32_t Size); __E } if($implement_syslog) { print H "private:\n\tbool mLogToSysLog;\n"; } if($implement_filelog) { print H "private:\n\tFILE *mLogToFile;\n"; } print H <<__E; protected: virtual std::auto_ptr MakeProtocolObject(int ObjType); virtual const char *GetIdentString(); }; __E my $constructor_extra = ''; $constructor_extra .= ', mLogToSysLog(false)' if $implement_syslog; $constructor_extra .= ', mLogToFile(0)' if $implement_filelog; my $destructor_extra = ($type eq 'Server')?"\n\tDeleteStreamsToSend();":''; my $prefix = $classname_base.'::'; print CPP <<__E; $prefix$classname_base(IOStream &rStream) : Protocol(rStream)$constructor_extra { } $prefix~$classname_base() {$destructor_extra } const char *${prefix}GetIdentString() { return "$ident_string"; } std::auto_ptr ${prefix}MakeProtocolObject(int ObjType) { switch(ObjType) { __E # do objects within this for my $cmd (@cmd_list) { print CPP <<__E; case $cmd_id{$cmd}: return std::auto_ptr(new $classname_base$cmd); break; __E } print CPP <<__E; default: THROW_EXCEPTION(ConnectionException, Conn_Protocol_UnknownCommandRecieved) } } __E # write receive and send functions print CPP <<__E; std::auto_ptr<$derive_objects_from> ${prefix}Receive() { std::auto_ptr<${derive_objects_from}> preply((${derive_objects_from}*)(Protocol::Receive().release())); __E if($implement_syslog) { print CPP <<__E; if(mLogToSysLog) { preply->LogSysLog("Receive"); } __E } if($implement_filelog) { print CPP <<__E; if(mLogToFile != 0) { preply->LogFile("Receive", mLogToFile); } __E } print CPP <<__E; return preply; } void ${prefix}Send(const ${derive_objects_from} &rObject) { __E if($implement_syslog) { print CPP <<__E; if(mLogToSysLog) { rObject.LogSysLog("Send"); } __E } if($implement_filelog) { print CPP <<__E; if(mLogToFile != 0) { rObject.LogFile("Send", mLogToFile); } __E } print CPP <<__E; Protocol::Send(rObject); } __E # write server function? if($type eq 'Server') { print CPP <<__E; void ${prefix}DoServer($context_class &rContext) { // Handshake with client Handshake(); // Command processing loop bool inProgress = true; while(inProgress) { // Get an object from the conversation std::auto_ptr<${derive_objects_from}> pobj(Receive()); // Run the command std::auto_ptr<${derive_objects_from}> preply((${derive_objects_from}*)(pobj->DoCommand(*this, rContext).release())); // Send the reply Send(*(preply.get())); // Send any streams for(unsigned int s = 0; s < mStreamsToSend.size(); s++) { // Send the streams SendStream(*mStreamsToSend[s]); } // Delete these streams DeleteStreamsToSend(); // Does this end the conversation? if(pobj->IsConversationEnd()) { inProgress = false; } } } void ${prefix}SendStreamAfterCommand(IOStream *pStream) { ASSERT(pStream != NULL); mStreamsToSend.push_back(pStream); } void ${prefix}DeleteStreamsToSend() { for(std::vector::iterator i(mStreamsToSend.begin()); i != mStreamsToSend.end(); ++i) { delete (*i); } mStreamsToSend.clear(); } __E } # write logging functions? if($implement_filelog || $implement_syslog) { my ($fR,$fS); if($implement_syslog) { $fR .= <<__E; if(mLogToSysLog) { if(Size==Protocol::ProtocolStream_SizeUncertain) { BOX_TRACE("Receiving stream, size uncertain"); } else { BOX_TRACE("Receiving stream, size " << Size); } } __E $fS .= <<__E; if(mLogToSysLog) { if(Size==Protocol::ProtocolStream_SizeUncertain) { BOX_TRACE("Sending stream, size uncertain"); } else { BOX_TRACE("Sending stream, size " << Size); } } __E } if($implement_filelog) { $fR .= <<__E; if(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain) ?"Receiving stream, size uncertain\\n" :"Receiving stream, size %d\\n", Size); ::fflush(mLogToFile); } __E $fS .= <<__E; if(mLogToFile) { ::fprintf(mLogToFile, (Size==Protocol::ProtocolStream_SizeUncertain) ?"Sending stream, size uncertain\\n" :"Sending stream, size %d\\n", Size); ::fflush(mLogToFile); } __E } print CPP <<__E; void ${prefix}InformStreamReceiving(u_int32_t Size) { $fR} void ${prefix}InformStreamSending(u_int32_t Size) { $fS} __E } # write client Query functions? if($type eq 'Client') { for my $cmd (@cmd_list) { if(obj_is_type($cmd,'Command')) { my $reply = obj_get_type_params($cmd,'Command'); my $reply_id = $cmd_id{$reply}; my $has_stream = obj_is_type($cmd,'StreamWithCommand'); my $argextra = $has_stream?', IOStream &rStream':''; my $send_stream_extra = ''; if($has_stream) { $send_stream_extra = <<__E; // Send stream after the command SendStream(rStream); __E } print CPP <<__E; std::auto_ptr<$classname_base$reply> ${classname_base}::Query(const $classname_base$cmd &rQuery$argextra) { // Send query Send(rQuery); $send_stream_extra // Wait for the reply std::auto_ptr<${derive_objects_from}> preply(Receive().release()); if(preply->GetType() == $reply_id) { // Correct response return std::auto_ptr<$classname_base$reply>(($classname_base$reply*)preply.release()); } else { // Set protocol error int type, subType; if(preply->IsError(type, subType)) { SetError(type, subType); BOX_WARNING("$cmd command failed: received error " << ((${classname_base}Error&)*preply).GetMessage()); } else { SetError(Protocol::UnknownError, Protocol::UnknownError); BOX_WARNING("$cmd command failed: received " "unexpected response type " << preply->GetType()); } // Throw an exception THROW_EXCEPTION(ConnectionException, Conn_Protocol_UnexpectedReply) } } __E } } } print H <<__E; #endif // $guardname __E # close files close H; close CPP; sub obj_is_type { my ($c,$ty) = @_; for(@{$cmd_attributes{$c}}) { return 1 if $_ =~ m/\A$ty/; } return 0; } sub obj_get_type_params { my ($c,$ty) = @_; for(@{$cmd_attributes{$c}}) { return $1 if $_ =~ m/\A$ty\((.+?)\)\Z/; } die "Can't find attribute $ty\n" } # returns (is basic type, typename) sub translate_type { my $ty = $_[0]; if($ty =~ m/\Avector\<(.+?)\>\Z/) { my $v_type = $1; my (undef,$v_ty) = translate_type($v_type); return (0, 'std::vector<'.$v_ty.'>') } else { if(!exists $translate_type_info{$ty}) { die "Don't know about type name $ty\n"; } return @{$translate_type_info{$ty}} } } sub translate_type_to_arg_type { my ($basic,$typename) = translate_type(@_); return $basic?$typename:'const '.$typename.'&' } sub translate_type_to_member_type { my ($basic,$typename) = translate_type(@_); return $typename } sub make_log_strings { my ($cmd) = @_; my @str; my @arg; for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); if(exists $log_display_types{$ty}) { # need to translate it my ($format,$arg) = @{$log_display_types{$ty}}; $arg =~ s/VAR/m$nm/g; if ($format eq "0x%llx" and $target_windows) { $format = "0x%I64x"; $arg = "(uint64_t)$arg"; } push @str,$format; push @arg,$arg; } else { # is opaque push @str,'OPAQUE'; } } return ($cmd.'('.join(',',@str).')', join(',','',@arg)); } sub make_log_strings_framework { my ($cmd) = @_; my @args; for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) { my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); if(exists $log_display_types{$ty}) { # need to translate it my ($format,$arg) = @{$log_display_types{$ty}}; $arg =~ s/VAR/m$nm/g; if ($format eq '\\"%s\\"') { $arg = "\"\\\"\" << $arg << \"\\\"\""; } elsif ($format =~ m'x$') { # my $width = 0; # $ty =~ /^int(\d+)$/ and $width = $1 / 4; $arg = "($arg == 0 ? \"0x\" : \"\") " . "<< std::hex " . "<< std::showbase " . # "<< std::setw($width) " . # "<< std::setfill('0') " . # "<< std::internal " . "<< $arg " . "<< std::dec"; } push @args, $arg; } else { # is opaque push @args, '"OPAQUE"'; } } my $log_cmd = "Action << \" $cmd(\" "; foreach my $arg (@args) { $arg = "<< $arg "; } $log_cmd .= join('<< "," ',@args); $log_cmd .= '<< ")"'; return $log_cmd; } boxbackup/lib/server/TLSContext.h0000664000175000017500000000170010347400657017552 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: TLSContext.h // Purpose: TLS (SSL) context for connections // Created: 2003/08/06 // // -------------------------------------------------------------------------- #ifndef TLSCONTEXT__H #define TLSCONTEXT__H #ifndef TLS_CLASS_IMPLEMENTATION_CPP class SSL_CTX; #endif // -------------------------------------------------------------------------- // // Class // Name: TLSContext // Purpose: TLS (SSL) context for connections // Created: 2003/08/06 // // -------------------------------------------------------------------------- class TLSContext { public: TLSContext(); ~TLSContext(); private: TLSContext(const TLSContext &); public: void Initialise(bool AsServer, const char *CertificatesFile, const char *PrivateKeyFile, const char *TrustedCAsFile); SSL_CTX *GetRawContext() const; private: SSL_CTX *mpContext; }; #endif // TLSCONTEXT__H boxbackup/lib/server/Daemon.h0000664000175000017500000000574111162210222016736 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Daemon.h // Purpose: Basic daemon functionality // Created: 2003/07/29 // // -------------------------------------------------------------------------- /* NOTE: will log to local6: include a line like local6.info /var/log/box in /etc/syslog.conf */ #ifndef DAEMON__H #define DAEMON__H #include #include "BoxTime.h" #include "Configuration.h" class ConfigurationVerify; // -------------------------------------------------------------------------- // // Class // Name: Daemon // Purpose: Basic daemon functionality // Created: 2003/07/29 // // -------------------------------------------------------------------------- class Daemon { public: Daemon(); virtual ~Daemon(); private: Daemon(const Daemon &rToCopy); public: virtual int Main(const char *DefaultConfigFile, int argc, const char *argv[]); /* override this Main() if you want custom option processing: */ virtual int Main(const std::string &rConfigFile); virtual void Run(); const Configuration &GetConfiguration() const; const std::string &GetConfigFileName() const {return mConfigFileName;} virtual const char *DaemonName() const; virtual std::string DaemonBanner() const; virtual const ConfigurationVerify *GetConfigVerify() const; virtual void Usage(); virtual bool Configure(const std::string& rConfigFileName); virtual bool Configure(const Configuration& rConfig); bool StopRun() {return mReloadConfigWanted | mTerminateWanted;} bool IsReloadConfigWanted() {return mReloadConfigWanted;} bool IsTerminateWanted() {return mTerminateWanted;} // To allow derived classes to get these signals in other ways void SetReloadConfigWanted() {mReloadConfigWanted = true;} void SetTerminateWanted() {mTerminateWanted = true;} virtual void EnterChild(); static void SetProcessTitle(const char *format, ...); void SetRunInForeground(bool foreground) { mRunInForeground = foreground; } void SetSingleProcess(bool value) { mSingleProcess = value; } protected: virtual void SetupInInitialProcess(); box_time_t GetLoadedConfigModifiedTime() const; bool IsSingleProcess() { return mSingleProcess; } virtual std::string GetOptionString(); virtual int ProcessOption(signed int option); private: static void SignalHandler(int sigraised); box_time_t GetConfigFileModifiedTime() const; std::string mConfigFileName; std::auto_ptr mapConfiguration; box_time_t mLoadedConfigModifiedTime; bool mReloadConfigWanted; bool mTerminateWanted; bool mSingleProcess; bool mRunInForeground; bool mKeepConsoleOpenAfterFork; bool mHaveConfigFile; int mLogLevel; // need an int to do math with static Daemon *spDaemon; std::string mAppName; }; #define DAEMON_VERIFY_SERVER_KEYS \ ConfigurationVerifyKey("PidFile", ConfigTest_Exists), \ ConfigurationVerifyKey("LogFacility", 0), \ ConfigurationVerifyKey("User", ConfigTest_LastEntry) #endif // DAEMON__H boxbackup/lib/server/SocketListen.h0000664000175000017500000001561011127624055020154 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SocketListen.h // Purpose: Stream based sockets for servers // Created: 2003/07/31 // // -------------------------------------------------------------------------- #ifndef SOCKETLISTEN__H #define SOCKETLISTEN__H #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_KQUEUE #include #include #endif #ifndef WIN32 #include #endif #include #include #include #include "Socket.h" #include "ServerException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Class // Name: _NoSocketLocking // Purpose: Default locking class for SocketListen // Created: 2003/07/31 // // -------------------------------------------------------------------------- class _NoSocketLocking { public: _NoSocketLocking(int sock) { } ~_NoSocketLocking() { } bool HaveLock() { return true; } private: _NoSocketLocking(const _NoSocketLocking &rToCopy) { } }; // -------------------------------------------------------------------------- // // Class // Name: SocketListen // Purpose: // Created: 2003/07/31 // // -------------------------------------------------------------------------- template class SocketListen { public: // Initialise SocketListen() : mSocketHandle(-1) { } // Close socket nicely ~SocketListen() { Close(); } private: SocketListen(const SocketListen &rToCopy) { } public: enum { MaxMultipleListenSockets = MaxMultiListenSockets }; void Close() { if(mSocketHandle != -1) { #ifdef WIN32 if(::closesocket(mSocketHandle) == -1) #else if(::close(mSocketHandle) == -1) #endif { BOX_LOG_SYS_ERROR("Failed to close network " "socket"); THROW_EXCEPTION(ServerException, SocketCloseError) } } mSocketHandle = -1; } // ------------------------------------------------------------------ // // Function // Name: SocketListen::Listen(int, char*, int) // Purpose: Initialises, starts the socket listening. // Created: 2003/07/31 // // ------------------------------------------------------------------ void Listen(Socket::Type Type, const char *Name, int Port = 0) { if(mSocketHandle != -1) { THROW_EXCEPTION(ServerException, SocketAlreadyOpen); } // Setup parameters based on type, looking up names if required int sockDomain = 0; SocketAllAddr addr; int addrLen = 0; Socket::NameLookupToSockAddr(addr, sockDomain, Type, Name, Port, addrLen); // Create the socket mSocketHandle = ::socket(sockDomain, SOCK_STREAM, 0 /* let OS choose protocol */); if(mSocketHandle == -1) { BOX_LOG_SYS_ERROR("Failed to create a network socket"); THROW_EXCEPTION(ServerException, SocketOpenError) } // Set an option to allow reuse (useful for -HUP situations!) #ifdef WIN32 if(::setsockopt(mSocketHandle, SOL_SOCKET, SO_REUSEADDR, "", 0) == -1) #else int option = true; if(::setsockopt(mSocketHandle, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) == -1) #endif { BOX_LOG_SYS_ERROR("Failed to set socket options"); THROW_EXCEPTION(ServerException, SocketOpenError) } // Bind it to the right port, and start listening if(::bind(mSocketHandle, &addr.sa_generic, addrLen) == -1 || ::listen(mSocketHandle, ListenBacklog) == -1) { // Dispose of the socket ::close(mSocketHandle); mSocketHandle = -1; THROW_EXCEPTION(ServerException, SocketBindError) } } // ------------------------------------------------------------------ // // Function // Name: SocketListen::Accept(int) // Purpose: Accepts a connection, returning a pointer to // a class of the specified type. May return a // null pointer if a signal happens, or there's // a timeout. Timeout specified in // milliseconds, defaults to infinite time. // Created: 2003/07/31 // // ------------------------------------------------------------------ std::auto_ptr Accept(int Timeout = INFTIM, std::string *pLogMsg = 0) { if(mSocketHandle == -1) { THROW_EXCEPTION(ServerException, BadSocketHandle); } // Do the accept, using the supplied locking type int sock; struct sockaddr addr; socklen_t addrlen = sizeof(addr); // BLOCK { SocketLockingType socklock(mSocketHandle); if(!socklock.HaveLock()) { // Didn't get the lock for some reason. // Wait a while, then return nothing. BOX_ERROR("Failed to get a lock on incoming " "connection"); ::sleep(1); return std::auto_ptr(); } // poll this socket struct pollfd p; p.fd = mSocketHandle; p.events = POLLIN; p.revents = 0; switch(::poll(&p, 1, Timeout)) { case -1: // signal? if(errno == EINTR) { BOX_ERROR("Failed to accept " "connection: interrupted by " "signal"); // return nothing return std::auto_ptr(); } else { BOX_LOG_SYS_ERROR("Failed to poll " "connection"); THROW_EXCEPTION(ServerException, SocketPollError) } break; case 0: // timed out return std::auto_ptr(); break; default: // got some thing... // control flows on... break; } sock = ::accept(mSocketHandle, &addr, &addrlen); } // Got socket (or error), unlock (implicit in destruction) if(sock == -1) { BOX_LOG_SYS_ERROR("Failed to accept connection"); THROW_EXCEPTION(ServerException, SocketAcceptError) } // Log it if(pLogMsg) { *pLogMsg = Socket::IncomingConnectionLogMessage(&addr, addrlen); } else { // Do logging ourselves Socket::LogIncomingConnection(&addr, addrlen); } return std::auto_ptr(new SocketType(sock)); } // Functions to allow adding to WaitForEvent class, for efficient waiting // on multiple sockets. #ifdef HAVE_KQUEUE // ------------------------------------------------------------------ // // Function // Name: SocketListen::FillInKEevent // Purpose: Fills in a kevent structure for this socket // Created: 9/3/04 // // ------------------------------------------------------------------ void FillInKEvent(struct kevent &rEvent, int Flags = 0) const { EV_SET(&rEvent, mSocketHandle, EVFILT_READ, 0, 0, 0, (void*)this); } #else // ------------------------------------------------------------------ // // Function // Name: SocketListen::FillInPoll // Purpose: Fills in the data necessary for a poll // operation // Created: 9/3/04 // // ------------------------------------------------------------------ void FillInPoll(int &fd, short &events, int Flags = 0) const { fd = mSocketHandle; events = POLLIN; } #endif private: int mSocketHandle; }; #include "MemLeakFindOff.h" #endif // SOCKETLISTEN__H boxbackup/lib/server/Makefile.extra0000664000175000017500000000060011345266370020153 0ustar siretartsiretart MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_ServerException.h autogen_ServerException.cpp: $(MAKEEXCEPTION) ServerException.txt $(_PERL) $(MAKEEXCEPTION) ServerException.txt # AUTOGEN SEEDING autogen_ConnectionException.h autogen_ConnectionException.cpp: $(MAKEEXCEPTION) ConnectionException.txt $(_PERL) $(MAKEEXCEPTION) ConnectionException.txt boxbackup/lib/server/ConnectionException.txt0000664000175000017500000000231610515501555022111 0ustar siretartsiretartEXCEPTION Connection 7 # for historic reasons not all numbers are used SocketWriteError 6 Probably a network issue between client and server. SocketReadError 7 Probably a network issue between client and server. SocketNameLookupError 9 Check hostname specified. SocketShutdownError 12 SocketConnectError 15 Probably a network issue between client and server, bad hostname, or server not running. TLSHandshakeFailed 30 TLSShutdownFailed 32 TLSWriteFailed 33 Probably a network issue between client and server. TLSReadFailed 34 Probably a network issue between client and server, or a problem with the server. TLSNoPeerCertificate 36 TLSPeerCertificateInvalid 37 Check certification process TLSClosedWhenWriting 38 TLSHandshakeTimedOut 39 Protocol_Timeout 41 Probably a network issue between client and server. Protocol_ObjTooBig 42 Protocol_BadCommandRecieved 44 Protocol_UnknownCommandRecieved 45 Protocol_TriedToExecuteReplyCommand 46 Protocol_UnexpectedReply 47 Server probably reported an error. Protocol_HandshakeFailed 48 Protocol_StreamWhenObjExpected 49 Protocol_ObjWhenStreamExpected 50 Protocol_TimeOutWhenSendingStream 52 Probably a network issue between client and server. boxbackup/lib/server/SSLLib.h0000664000175000017500000000161711126433077016640 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: SSLLib.h // Purpose: Utility functions for dealing with the OpenSSL library // Created: 2003/08/06 // // -------------------------------------------------------------------------- #ifndef SSLLIB__H #define SSLLIB__H #ifndef BOX_RELEASE_BUILD extern bool SSLLib__TraceErrors; #define SET_DEBUG_SSLLIB_TRACE_ERRORS {SSLLib__TraceErrors = true;} #else #define SET_DEBUG_SSLLIB_TRACE_ERRORS #endif // -------------------------------------------------------------------------- // // Namespace // Name: SSLLib // Purpose: Utility functions for dealing with the OpenSSL library // Created: 2003/08/06 // // -------------------------------------------------------------------------- namespace SSLLib { void Initialise(); void LogError(const std::string& rErrorDuringAction); }; #endif // SSLLIB__H boxbackup/lib/server/ProtocolObject.cpp0000664000175000017500000000700610347400657021033 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ProtocolObject.h // Purpose: Protocol object base class // Created: 2003/08/19 // // -------------------------------------------------------------------------- #include "Box.h" #include "ProtocolObject.h" #include "CommonException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::ProtocolObject() // Purpose: Default constructor // Created: 2003/08/19 // // -------------------------------------------------------------------------- ProtocolObject::ProtocolObject() { } // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::ProtocolObject() // Purpose: Destructor // Created: 2003/08/19 // // -------------------------------------------------------------------------- ProtocolObject::~ProtocolObject() { } // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::ProtocolObject() // Purpose: Copy constructor // Created: 2003/08/19 // // -------------------------------------------------------------------------- ProtocolObject::ProtocolObject(const ProtocolObject &rToCopy) { } // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::IsError(int &, int &) // Purpose: Does this represent an error, and if so, what is the type and subtype? // Created: 2003/08/19 // // -------------------------------------------------------------------------- bool ProtocolObject::IsError(int &rTypeOut, int &rSubTypeOut) const { return false; } // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::IsConversationEnd() // Purpose: Does this command end the conversation? // Created: 2003/08/19 // // -------------------------------------------------------------------------- bool ProtocolObject::IsConversationEnd() const { return false; } // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::GetType() // Purpose: Return type of the object // Created: 2003/08/19 // // -------------------------------------------------------------------------- int ProtocolObject::GetType() const { // This isn't implemented in the base class! THROW_EXCEPTION(CommonException, Internal) } // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::SetPropertiesFromStreamData(Protocol &) // Purpose: Set the properties of the object from the stream data ready in the Protocol object // Created: 2003/08/19 // // -------------------------------------------------------------------------- void ProtocolObject::SetPropertiesFromStreamData(Protocol &rProtocol) { // This isn't implemented in the base class! THROW_EXCEPTION(CommonException, Internal) } // -------------------------------------------------------------------------- // // Function // Name: ProtocolObject::WritePropertiesToStreamData(Protocol &) // Purpose: Write the properties of the object into the stream data in the Protocol object // Created: 2003/08/19 // // -------------------------------------------------------------------------- void ProtocolObject::WritePropertiesToStreamData(Protocol &rProtocol) const { // This isn't implemented in the base class! THROW_EXCEPTION(CommonException, Internal) } boxbackup/lib/server/ProtocolUncertainStream.h0000664000175000017500000000240010702536354022366 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: ProtocolUncertainStream.h // Purpose: Read part of another stream // Created: 2003/12/05 // // -------------------------------------------------------------------------- #ifndef PROTOCOLUNCERTAINSTREAM__H #define PROTOCOLUNCERTAINSTREAM__H #include "IOStream.h" // -------------------------------------------------------------------------- // // Class // Name: ProtocolUncertainStream // Purpose: Read part of another stream // Created: 2003/12/05 // // -------------------------------------------------------------------------- class ProtocolUncertainStream : public IOStream { public: ProtocolUncertainStream(IOStream &rSource); ~ProtocolUncertainStream(); private: // no copying allowed ProtocolUncertainStream(const IOStream &); ProtocolUncertainStream(const ProtocolUncertainStream &); public: virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual pos_type BytesLeftToRead(); virtual void Write(const void *pBuffer, int NBytes); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: IOStream &mrSource; int mBytesLeftInCurrentBlock; bool mFinished; }; #endif // PROTOCOLUNCERTAINSTREAM__H boxbackup/lib/server/Socket.h0000664000175000017500000000232711127624055016776 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Socket.h // Purpose: Socket related stuff // Created: 2003/07/31 // // -------------------------------------------------------------------------- #ifndef SOCKET__H #define SOCKET__H #ifdef WIN32 #include "emu.h" typedef int socklen_t; #else #include #include #include #endif #include typedef union { struct sockaddr sa_generic; struct sockaddr_in sa_inet; #ifndef WIN32 struct sockaddr_un sa_unix; #endif } SocketAllAddr; // -------------------------------------------------------------------------- // // Namespace // Name: Socket // Purpose: Socket utilities // Created: 2003/07/31 // // -------------------------------------------------------------------------- namespace Socket { enum Type { TypeINET = 1, TypeUNIX = 2 }; void NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain, enum Type type, const std::string& rName, int Port, int &rSockAddrLenOut); void LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen); std::string IncomingConnectionLogMessage(const struct sockaddr *addr, socklen_t addrlen); }; #endif // SOCKET__H boxbackup/lib/server/Daemon.cpp0000664000175000017500000005523011443516155017307 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: Daemon.cpp // Purpose: Basic daemon functionality // Created: 2003/07/29 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #ifdef HAVE_BSD_UNISTD_H #include #endif #ifdef WIN32 #include #endif #include #include "Daemon.h" #include "Configuration.h" #include "ServerException.h" #include "Guards.h" #include "UnixUser.h" #include "FileModificationTime.h" #include "Logging.h" #include "Utils.h" #include "MemLeakFindOn.h" Daemon *Daemon::spDaemon = 0; // -------------------------------------------------------------------------- // // Function // Name: Daemon::Daemon() // Purpose: Constructor // Created: 2003/07/29 // // -------------------------------------------------------------------------- Daemon::Daemon() : mReloadConfigWanted(false), mTerminateWanted(false), #ifdef WIN32 mSingleProcess(true), mRunInForeground(true), mKeepConsoleOpenAfterFork(true), #else mSingleProcess(false), mRunInForeground(false), mKeepConsoleOpenAfterFork(false), #endif mHaveConfigFile(false), mAppName(DaemonName()) { // In debug builds, switch on assert failure logging to syslog ASSERT_FAILS_TO_SYSLOG_ON // And trace goes to syslog too TRACE_TO_SYSLOG(true) } // -------------------------------------------------------------------------- // // Function // Name: Daemon::~Daemon() // Purpose: Destructor // Created: 2003/07/29 // // -------------------------------------------------------------------------- Daemon::~Daemon() { } // -------------------------------------------------------------------------- // // Function // Name: Daemon::GetOptionString() // Purpose: Returns the valid Getopt command-line options. // This should be overridden by subclasses to add // their own options, which should override // ProcessOption, handle their own, and delegate to // ProcessOption for the standard options. // Created: 2007/09/18 // // -------------------------------------------------------------------------- std::string Daemon::GetOptionString() { return "c:" #ifndef WIN32 "DFK" #endif "hkPqQt:TUvVW:"; } void Daemon::Usage() { std::cout << DaemonBanner() << "\n" "\n" "Usage: " << mAppName << " [options] [config file]\n" "\n" "Options:\n" " -c Use the specified configuration file. If -c is omitted, the last\n" " argument is the configuration file, or else the default \n" " [" << GetConfigFileName() << "]\n" #ifndef WIN32 " -D Debugging mode, do not fork, one process only, one client only\n" " -F Do not fork into background, but fork to serve multiple clients\n" #endif " -k Keep console open after fork, keep writing log messages to it\n" #ifndef WIN32 " -K Stop writing log messages to console while daemon is running\n" " -P Show process ID (PID) in console output\n" #endif " -q Run more quietly, reduce verbosity level by one, can repeat\n" " -Q Run at minimum verbosity, log nothing\n" " -v Run more verbosely, increase verbosity level by one, can repeat\n" " -V Run at maximum verbosity, log everything\n" " -W Set verbosity to error/warning/notice/info/trace/everything\n" " -t Tag console output with specified marker\n" " -T Timestamp console output\n" " -U Timestamp console output with microseconds\n"; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::ProcessOption(int option) // Purpose: Processes the supplied option (equivalent to the // return code from getopt()). Return zero if the // option was handled successfully, or nonzero to // abort the program with that return value. // Created: 2007/09/18 // // -------------------------------------------------------------------------- int Daemon::ProcessOption(signed int option) { switch(option) { case 'c': { mConfigFileName = optarg; mHaveConfigFile = true; } break; #ifndef WIN32 case 'D': { mSingleProcess = true; } break; case 'F': { mRunInForeground = true; } break; #endif // !WIN32 case 'k': { mKeepConsoleOpenAfterFork = true; } break; case 'K': { mKeepConsoleOpenAfterFork = false; } break; case 'h': { Usage(); return 2; } break; case 'P': { Console::SetShowPID(true); } break; case 'q': { if(mLogLevel == Log::NOTHING) { BOX_FATAL("Too many '-q': " "Cannot reduce logging " "level any more"); return 2; } mLogLevel--; } break; case 'Q': { mLogLevel = Log::NOTHING; } break; case 'v': { if(mLogLevel == Log::EVERYTHING) { BOX_FATAL("Too many '-v': " "Cannot increase logging " "level any more"); return 2; } mLogLevel++; } break; case 'V': { mLogLevel = Log::EVERYTHING; } break; case 'W': { mLogLevel = Logging::GetNamedLevel(optarg); if (mLogLevel == Log::INVALID) { BOX_FATAL("Invalid logging level"); return 2; } } break; case 't': { Logging::SetProgramName(optarg); Console::SetShowTag(true); } break; case 'T': { Console::SetShowTime(true); } break; case 'U': { Console::SetShowTime(true); Console::SetShowTimeMicros(true); } break; case '?': { BOX_FATAL("Unknown option on command line: " << "'" << (char)optopt << "'"); return 2; } break; default: { BOX_FATAL("Unknown error in getopt: returned " << "'" << option << "'"); return 1; } } return 0; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::Main(const char *, int, const char *[]) // Purpose: Parses command-line options, and then calls // Main(std::string& configFile, bool singleProcess) // to start the daemon. // Created: 2003/07/29 // // -------------------------------------------------------------------------- int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) { // Find filename of config file mConfigFileName = DefaultConfigFile; mAppName = argv[0]; #ifdef BOX_RELEASE_BUILD mLogLevel = Log::NOTICE; // need an int to do math with #else mLogLevel = Log::INFO; // need an int to do math with #endif if (argc == 2 && strcmp(argv[1], "/?") == 0) { Usage(); return 2; } signed int c; // reset getopt, just in case anybody used it before. // unfortunately glibc and BSD differ on this point! // http://www.ussg.iu.edu/hypermail/linux/kernel/0305.3/0262.html #if HAVE_DECL_OPTRESET == 1 || defined WIN32 optind = 1; optreset = 1; #elif defined __GLIBC__ optind = 0; #else // Solaris, any others? optind = 1; #endif while((c = getopt(argc, (char * const *)argv, GetOptionString().c_str())) != -1) { int returnCode = ProcessOption(c); if (returnCode != 0) { return returnCode; } } if (argc > optind && !mHaveConfigFile) { mConfigFileName = argv[optind]; optind++; mHaveConfigFile = true; } if (argc > optind && ::strcmp(argv[optind], "SINGLEPROCESS") == 0) { mSingleProcess = true; optind++; } if (argc > optind) { BOX_FATAL("Unknown parameter on command line: " << "'" << std::string(argv[optind]) << "'"); return 2; } Logging::FilterConsole((Log::Level)mLogLevel); Logging::FilterSyslog ((Log::Level)mLogLevel); return Main(mConfigFileName); } // -------------------------------------------------------------------------- // // Function // Name: Daemon::Configure(const std::string& rConfigFileName) // Purpose: Loads daemon configuration. Useful when you have // a local Daemon object and don't intend to fork() // or call Main(). // Created: 2008/04/19 // // -------------------------------------------------------------------------- bool Daemon::Configure(const std::string& rConfigFileName) { // Load the configuration file. std::string errors; std::auto_ptr apConfig; try { if (!FileExists(rConfigFileName.c_str())) { BOX_FATAL("The main configuration file for " << DaemonName() << " was not found: " << rConfigFileName); if (!mHaveConfigFile) { BOX_WARNING("The default configuration " "directory has changed from /etc/box " "to /etc/boxbackup"); } return false; } apConfig = Configuration::LoadAndVerify(rConfigFileName, GetConfigVerify(), errors); } catch(BoxException &e) { if(e.GetType() == CommonException::ExceptionType && e.GetSubType() == CommonException::OSFileOpenError) { BOX_ERROR("Failed to open configuration file: " << rConfigFileName); return false; } throw; } // Got errors? if(apConfig.get() == 0) { BOX_ERROR("Failed to load or verify configuration file"); return false; } if(!Configure(*apConfig)) { BOX_ERROR("Failed to verify configuration file"); return false; } // Store configuration mConfigFileName = rConfigFileName; mLoadedConfigModifiedTime = GetConfigFileModifiedTime(); return true; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::Configure(const Configuration& rConfig) // Purpose: Loads daemon configuration. Useful when you have // a local Daemon object and don't intend to fork() // or call Main(). // Created: 2008/08/12 // // -------------------------------------------------------------------------- bool Daemon::Configure(const Configuration& rConfig) { std::string errors; // Verify() may modify the configuration, e.g. adding default values // for required keys, so need to make a copy here std::auto_ptr apConf(new Configuration(rConfig)); apConf->Verify(*GetConfigVerify(), errors); // Got errors? if(!errors.empty()) { BOX_ERROR("Configuration errors: " << errors); return false; } // Store configuration mapConfiguration = apConf; // Let the derived class have a go at setting up stuff // in the initial process SetupInInitialProcess(); return true; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::Main(const std::string& rConfigFileName) // Purpose: Starts the daemon off -- equivalent of C main() function // Created: 2003/07/29 // // -------------------------------------------------------------------------- int Daemon::Main(const std::string &rConfigFileName) { // Banner (optional) { BOX_SYSLOG(Log::NOTICE, DaemonBanner()); } std::string pidFileName; bool asDaemon = !mSingleProcess && !mRunInForeground; try { if (!Configure(rConfigFileName)) { BOX_FATAL("Failed to start: failed to load " "configuration file: " << rConfigFileName); return 1; } // Server configuration const Configuration &serverConfig( mapConfiguration->GetSubConfiguration("Server")); if(serverConfig.KeyExists("LogFacility")) { std::string facility = serverConfig.GetKeyValue("LogFacility"); Logging::SetFacility(Syslog::GetNamedFacility(facility)); } // Open PID file for writing pidFileName = serverConfig.GetKeyValue("PidFile"); FileHandleGuard<(O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)> pidFile(pidFileName.c_str()); #ifndef WIN32 // Handle changing to a different user if(serverConfig.KeyExists("User")) { // Config file specifies an user -- look up UnixUser daemonUser(serverConfig.GetKeyValue("User").c_str()); // Change the owner on the PID file, so it can be deleted properly on termination if(::fchown(pidFile, daemonUser.GetUID(), daemonUser.GetGID()) != 0) { THROW_EXCEPTION(ServerException, CouldNotChangePIDFileOwner) } // Change the process ID daemonUser.ChangeProcessUser(); } if(asDaemon) { // Let's go... Daemonise... switch(::fork()) { case -1: // error THROW_EXCEPTION(ServerException, DaemoniseFailed) break; default: // parent // _exit(0); return 0; break; case 0: // child break; } // In child // Set new session if(::setsid() == -1) { BOX_LOG_SYS_ERROR("Failed to setsid()"); THROW_EXCEPTION(ServerException, DaemoniseFailed) } // Fork again... switch(::fork()) { case -1: // error BOX_LOG_SYS_ERROR("Failed to fork() a child"); THROW_EXCEPTION(ServerException, DaemoniseFailed) break; default: // parent _exit(0); return 0; break; case 0: // child break; } } #endif // !WIN32 // Must set spDaemon before installing signal handler, // otherwise the handler will crash if invoked too soon. if(spDaemon != NULL) { THROW_EXCEPTION(ServerException, AlreadyDaemonConstructed) } spDaemon = this; #ifndef WIN32 // Set signal handler // Don't do this in the parent, since it might be anything // (e.g. test/bbackupd) struct sigaction sa; sa.sa_handler = SignalHandler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); // macro if(::sigaction(SIGHUP, &sa, NULL) != 0 || ::sigaction(SIGTERM, &sa, NULL) != 0) { BOX_LOG_SYS_ERROR("Failed to set signal handlers"); THROW_EXCEPTION(ServerException, DaemoniseFailed) } #endif // !WIN32 // Write PID to file char pid[32]; int pidsize = sprintf(pid, "%d", (int)getpid()); if(::write(pidFile, pid, pidsize) != pidsize) { BOX_LOG_SYS_FATAL("Failed to write PID file: " << pidFileName); THROW_EXCEPTION(ServerException, DaemoniseFailed) } // Set up memory leak reporting #ifdef BOX_MEMORY_LEAK_TESTING { char filename[256]; sprintf(filename, "%s.memleaks", DaemonName()); memleakfinder_setup_exit_report(filename, DaemonName()); } #endif // BOX_MEMORY_LEAK_TESTING if(asDaemon && !mKeepConsoleOpenAfterFork) { #ifndef WIN32 // Close standard streams ::close(0); ::close(1); ::close(2); // Open and redirect them into /dev/null int devnull = ::open(PLATFORM_DEV_NULL, O_RDWR, 0); if(devnull == -1) { BOX_LOG_SYS_ERROR("Failed to open /dev/null"); THROW_EXCEPTION(CommonException, OSFileError); } // Then duplicate them to all three handles if(devnull != 0) dup2(devnull, 0); if(devnull != 1) dup2(devnull, 1); if(devnull != 2) dup2(devnull, 2); // Close the original handle if it was opened above the std* range if(devnull > 2) { ::close(devnull); } // And definitely don't try and send anything to those file descriptors // -- this has in the past sent text to something which isn't expecting it. TRACE_TO_STDOUT(false); #endif // ! WIN32 Logging::ToConsole(false); } // Log the start message BOX_NOTICE("Starting daemon, version: " << BOX_VERSION); BOX_NOTICE("Using configuration file: " << mConfigFileName); } catch(BoxException &e) { BOX_FATAL("Failed to start: exception " << e.what() << " (" << e.GetType() << "/" << e.GetSubType() << ")"); return 1; } catch(std::exception &e) { BOX_FATAL("Failed to start: exception " << e.what()); return 1; } catch(...) { BOX_FATAL("Failed to start: unknown error"); return 1; } #ifdef WIN32 // Under win32 we must initialise the Winsock library // before using sockets WSADATA info; if (WSAStartup(0x0101, &info) == SOCKET_ERROR) { // will not run without sockets BOX_FATAL("Failed to initialise Windows Sockets"); THROW_EXCEPTION(CommonException, Internal) } #endif int retcode = 0; // Main Daemon running try { while(!mTerminateWanted) { Run(); if(mReloadConfigWanted && !mTerminateWanted) { // Need to reload that config file... BOX_NOTICE("Reloading configuration file: " << mConfigFileName); std::string errors; std::auto_ptr pconfig( Configuration::LoadAndVerify( mConfigFileName.c_str(), GetConfigVerify(), errors)); // Got errors? if(pconfig.get() == 0 || !errors.empty()) { // Tell user about errors BOX_FATAL("Error in configuration " << "file: " << mConfigFileName << ": " << errors); // And give up retcode = 1; break; } // Store configuration mapConfiguration = pconfig; mLoadedConfigModifiedTime = GetConfigFileModifiedTime(); // Stop being marked for loading config again mReloadConfigWanted = false; } } // Delete the PID file ::unlink(pidFileName.c_str()); // Log BOX_NOTICE("Terminating daemon"); } catch(BoxException &e) { BOX_FATAL("Terminating due to exception " << e.what() << " (" << e.GetType() << "/" << e.GetSubType() << ")"); retcode = 1; } catch(std::exception &e) { BOX_FATAL("Terminating due to exception " << e.what()); retcode = 1; } catch(...) { BOX_FATAL("Terminating due to unknown exception"); retcode = 1; } #ifdef WIN32 WSACleanup(); #else // Should clean up here, but it breaks memory leak tests. /* if(asDaemon) { // we are running in the child by now, and should not return mapConfiguration.reset(); exit(0); } */ #endif ASSERT(spDaemon == this); spDaemon = NULL; return retcode; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::EnterChild() // Purpose: Sets up for a child task of the main server. Call // just after fork(). // Created: 2003/07/31 // // -------------------------------------------------------------------------- void Daemon::EnterChild() { #ifndef WIN32 // Unset signal handlers struct sigaction sa; sa.sa_handler = SIG_DFL; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); // macro ::sigaction(SIGHUP, &sa, NULL); ::sigaction(SIGTERM, &sa, NULL); #endif } // -------------------------------------------------------------------------- // // Function // Name: Daemon::SignalHandler(int) // Purpose: Signal handler // Created: 2003/07/29 // // -------------------------------------------------------------------------- void Daemon::SignalHandler(int sigraised) { #ifndef WIN32 if(spDaemon != 0) { switch(sigraised) { case SIGHUP: spDaemon->mReloadConfigWanted = true; break; case SIGTERM: spDaemon->mTerminateWanted = true; break; default: break; } } #endif } // -------------------------------------------------------------------------- // // Function // Name: Daemon::DaemonName() // Purpose: Returns name of the daemon // Created: 2003/07/29 // // -------------------------------------------------------------------------- const char *Daemon::DaemonName() const { return "generic-daemon"; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::DaemonBanner() // Purpose: Returns the text banner for this daemon's startup // Created: 1/1/04 // // -------------------------------------------------------------------------- std::string Daemon::DaemonBanner() const { return "Generic daemon using the Box Application Framework"; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::Run() // Purpose: Main run function after basic Daemon initialisation // Created: 2003/07/29 // // -------------------------------------------------------------------------- void Daemon::Run() { while(!StopRun()) { ::sleep(10); } } // -------------------------------------------------------------------------- // // Function // Name: Daemon::GetConfigVerify() // Purpose: Returns the configuration file verification structure for this daemon // Created: 2003/07/29 // // -------------------------------------------------------------------------- const ConfigurationVerify *Daemon::GetConfigVerify() const { static ConfigurationVerifyKey verifyserverkeys[] = { DAEMON_VERIFY_SERVER_KEYS }; static ConfigurationVerify verifyserver[] = { { "Server", 0, verifyserverkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; static ConfigurationVerify verify = { "root", verifyserver, 0, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; return &verify; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::GetConfiguration() // Purpose: Returns the daemon configuration object // Created: 2003/07/29 // // -------------------------------------------------------------------------- const Configuration &Daemon::GetConfiguration() const { if(mapConfiguration.get() == 0) { // Shouldn't get anywhere near this if a configuration file can't be loaded THROW_EXCEPTION(ServerException, Internal) } return *mapConfiguration; } // -------------------------------------------------------------------------- // // Function // Name: Daemon::SetupInInitialProcess() // Purpose: A chance for the daemon to do something initial // setting up in the process which initiates // everything, and after the configuration file has // been read and verified. // Created: 2003/08/20 // // -------------------------------------------------------------------------- void Daemon::SetupInInitialProcess() { // Base class doesn't do anything. } void Daemon::SetProcessTitle(const char *format, ...) { // On OpenBSD, setproctitle() sets the process title to imagename: (imagename) // -- make sure other platforms include the image name somewhere so ps listings give // useful information. #ifdef HAVE_SETPROCTITLE // optional arguments va_list args; va_start(args, format); // Make the string char title[256]; ::vsnprintf(title, sizeof(title), format, args); // Set process title ::setproctitle("%s", title); #endif // HAVE_SETPROCTITLE } // -------------------------------------------------------------------------- // // Function // Name: Daemon::GetConfigFileModifiedTime() // Purpose: Returns the timestamp when the configuration file // was last modified // // Created: 2006/01/29 // // -------------------------------------------------------------------------- box_time_t Daemon::GetConfigFileModifiedTime() const { EMU_STRUCT_STAT st; if(EMU_STAT(GetConfigFileName().c_str(), &st) != 0) { if (errno == ENOENT) { return 0; } BOX_LOG_SYS_ERROR("Failed to stat configuration file: " << GetConfigFileName()); THROW_EXCEPTION(CommonException, OSFileError) } return FileModificationTime(st); } // -------------------------------------------------------------------------- // // Function // Name: Daemon::GetLoadedConfigModifiedTime() // Purpose: Returns the timestamp when the configuration file // had been last modified, at the time when it was // loaded // // Created: 2006/01/29 // // -------------------------------------------------------------------------- box_time_t Daemon::GetLoadedConfigModifiedTime() const { return mLoadedConfigModifiedTime; } boxbackup/lib/server/WinNamedPipeListener.h0000664000175000017500000001307711071517477021607 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: WinNamedPipeListener.h // Purpose: Windows named pipe socket connection listener // for server // Created: 2008/09/30 // // -------------------------------------------------------------------------- #ifndef WINNAMEDPIPELISTENER__H #define WINNAMEDPIPELISTENER__H #include #include #include "ServerException.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Class // Name: WinNamedPipeListener // Purpose: // Created: 2008/09/30 // // -------------------------------------------------------------------------- template class WinNamedPipeListener { private: std::auto_ptr mapPipeName; std::auto_ptr mapOverlapConnect; HANDLE mPipeHandle; public: // Initialise WinNamedPipeListener() : mPipeHandle(INVALID_HANDLE_VALUE) { } private: WinNamedPipeListener(const WinNamedPipeListener &rToCopy) { /* forbidden */ } HANDLE CreatePipeHandle(const std::string& rName) { std::string socket = WinNamedPipeStream::sPipeNamePrefix + rName; HANDLE handle = CreateNamedPipeA( socket.c_str(), // pipe name PIPE_ACCESS_DUPLEX | // read/write access FILE_FLAG_OVERLAPPED, // enabled overlapped I/O PIPE_TYPE_BYTE | // message type pipe PIPE_READMODE_BYTE | // message-read mode PIPE_WAIT, // blocking mode ListenBacklog + 1, // max. instances 4096, // output buffer size 4096, // input buffer size NMPWAIT_USE_DEFAULT_WAIT, // client time-out NULL); // default security attribute if (handle == INVALID_HANDLE_VALUE) { BOX_LOG_WIN_ERROR("Failed to create named pipe " << socket); THROW_EXCEPTION(ServerException, SocketOpenError) } return handle; } public: ~WinNamedPipeListener() { Close(); } void Close() { if (mPipeHandle != INVALID_HANDLE_VALUE) { if (mapOverlapConnect.get()) { // outstanding connect in progress if (CancelIo(mPipeHandle) != TRUE) { BOX_LOG_WIN_ERROR("Failed to cancel " "outstanding connect request " "on named pipe"); } mapOverlapConnect.reset(); } if (CloseHandle(mPipeHandle) != TRUE) { BOX_LOG_WIN_ERROR("Failed to close named pipe " "handle"); } mPipeHandle = INVALID_HANDLE_VALUE; } } // ------------------------------------------------------------------ // // Function // Name: WinNamedPipeListener::Listen(std::string name) // Purpose: Initialises socket name // Created: 2003/07/31 // // ------------------------------------------------------------------ void Listen(const std::string& rName) { Close(); mapPipeName.reset(new std::string(rName)); mPipeHandle = CreatePipeHandle(rName); } // ------------------------------------------------------------------ // // Function // Name: WinNamedPipeListener::Accept(int) // Purpose: Accepts a connection, returning a pointer to // a class of the specified type. May return a // null pointer if a signal happens, or there's // a timeout. Timeout specified in // milliseconds, defaults to infinite time. // Created: 2003/07/31 // // ------------------------------------------------------------------ std::auto_ptr Accept(int Timeout = INFTIM, const char* pLogMsgOut = NULL) { if(!mapPipeName.get()) { THROW_EXCEPTION(ServerException, BadSocketHandle); } BOOL connected = FALSE; std::auto_ptr mapStream; if (!mapOverlapConnect.get()) { // start a new connect operation mapOverlapConnect.reset(new OverlappedIO()); connected = ConnectNamedPipe(mPipeHandle, &mapOverlapConnect->mOverlapped); if (connected == FALSE) { if (GetLastError() == ERROR_PIPE_CONNECTED) { connected = TRUE; } else if (GetLastError() != ERROR_IO_PENDING) { BOX_LOG_WIN_ERROR("Failed to connect " "named pipe"); THROW_EXCEPTION(ServerException, SocketAcceptError); } } } if (connected == FALSE) { // wait for connection DWORD result = WaitForSingleObject( mapOverlapConnect->mOverlapped.hEvent, (Timeout == INFTIM) ? INFINITE : Timeout); if (result == WAIT_OBJECT_0) { DWORD dummy; if (!GetOverlappedResult(mPipeHandle, &mapOverlapConnect->mOverlapped, &dummy, TRUE)) { BOX_LOG_WIN_ERROR("Failed to get " "overlapped connect result"); THROW_EXCEPTION(ServerException, SocketAcceptError); } connected = TRUE; } else if (result == WAIT_TIMEOUT) { return mapStream; // contains NULL } else if (result == WAIT_ABANDONED) { BOX_ERROR("Wait for named pipe connection " "was abandoned by the system"); THROW_EXCEPTION(ServerException, SocketAcceptError); } else if (result == WAIT_FAILED) { BOX_LOG_WIN_ERROR("Failed to wait for named " "pipe connection"); THROW_EXCEPTION(ServerException, SocketAcceptError); } else { BOX_ERROR("Failed to wait for named pipe " "connection: unknown return code " << result); THROW_EXCEPTION(ServerException, SocketAcceptError); } } ASSERT(connected == TRUE); mapStream.reset(new WinNamedPipeStream(mPipeHandle)); mPipeHandle = CreatePipeHandle(*mapPipeName); mapOverlapConnect.reset(); return mapStream; } }; #include "MemLeakFindOff.h" #endif // WINNAMEDPIPELISTENER__H boxbackup/lib/server/LocalProcessStream.cpp0000664000175000017500000001065111345266202021643 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: LocalProcessStream.cpp // Purpose: Opens a process, and presents stdin/stdout as a stream. // Created: 12/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include "LocalProcessStream.h" #include "autogen_ServerException.h" #include "Utils.h" #ifdef WIN32 #include "FileStream.h" #else #include "SocketStream.h" #endif #include "MemLeakFindOn.h" #define MAX_ARGUMENTS 64 // -------------------------------------------------------------------------- // // Function // Name: LocalProcessStream(const char *, pid_t &) // Purpose: Run a new process, and return a stream giving access // to its stdin and stdout (stdout and stderr on // Win32). Returns the PID of the new process -- this // must be waited on at some point to avoid zombies // (except on Win32). // Created: 12/3/04 // // -------------------------------------------------------------------------- std::auto_ptr LocalProcessStream(const std::string& rCommandLine, pid_t &rPidOut) { #ifndef WIN32 // Split up command std::vector command; SplitString(rCommandLine, ' ', command); // Build arguments char *args[MAX_ARGUMENTS + 4]; { int a = 0; std::vector::const_iterator i(command.begin()); while(a < MAX_ARGUMENTS && i != command.end()) { args[a++] = (char*)(*(i++)).c_str(); } args[a] = NULL; } // Create a socket pair to communicate over. int sv[2] = {-1,-1}; if(::socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) != 0) { THROW_EXCEPTION(ServerException, SocketPairFailed) } std::auto_ptr stream(new SocketStream(sv[0])); // Fork pid_t pid = 0; switch(pid = vfork()) { case -1: // error ::close(sv[0]); ::close(sv[1]); THROW_EXCEPTION(ServerException, ServerForkError) break; case 0: // child // Close end of the socket not being used ::close(sv[0]); // Duplicate the file handles to stdin and stdout if(sv[1] != 0) ::dup2(sv[1], 0); if(sv[1] != 1) ::dup2(sv[1], 1); // Close the now redundant socket if(sv[1] != 0 && sv[1] != 1) { ::close(sv[1]); } // Execute command! ::execv(args[0], args); ::_exit(127); // report error break; default: // just continue... break; } // Close the file descriptor not being used ::close(sv[1]); // Return the stream object and PID rPidOut = pid; return stream; #else // WIN32 SECURITY_ATTRIBUTES secAttr; secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); secAttr.bInheritHandle = TRUE; secAttr.lpSecurityDescriptor = NULL; HANDLE writeInChild, readFromChild; if(!CreatePipe(&readFromChild, &writeInChild, &secAttr, 0)) { BOX_ERROR("Failed to CreatePipe for child process: " << GetErrorMessage(GetLastError())); THROW_EXCEPTION(ServerException, SocketPairFailed) } SetHandleInformation(readFromChild, HANDLE_FLAG_INHERIT, 0); PROCESS_INFORMATION procInfo; STARTUPINFO startupInfo; ZeroMemory(&procInfo, sizeof(procInfo)); ZeroMemory(&startupInfo, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); startupInfo.hStdError = writeInChild; startupInfo.hStdOutput = writeInChild; startupInfo.hStdInput = INVALID_HANDLE_VALUE; startupInfo.dwFlags |= STARTF_USESTDHANDLES; CHAR* commandLineCopy = (CHAR*)malloc(rCommandLine.size() + 1); strcpy(commandLineCopy, rCommandLine.c_str()); BOOL result = CreateProcess(NULL, commandLineCopy, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &startupInfo, // STARTUPINFO pointer &procInfo); // receives PROCESS_INFORMATION free(commandLineCopy); if(!result) { BOX_ERROR("Failed to CreateProcess: '" << rCommandLine << "': " << GetErrorMessage(GetLastError())); CloseHandle(writeInChild); CloseHandle(readFromChild); THROW_EXCEPTION(ServerException, ServerForkError) } CloseHandle(procInfo.hProcess); CloseHandle(procInfo.hThread); CloseHandle(writeInChild); rPidOut = (int)(procInfo.dwProcessId); std::auto_ptr stream(new FileStream(readFromChild)); return stream; #endif // ! WIN32 } boxbackup/lib/backupclient/0000775000175000017500000000000011652362374016535 5ustar siretartsiretartboxbackup/lib/backupclient/BackupStoreFileEncodeStream.h0000664000175000017500000001046411053243116024212 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileEncodeStream.h // Purpose: Implement stream-based file encoding for the backup store // Created: 12/1/04 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREFILEENCODESTREAM__H #define BACKUPSTOREFILEENCODESTREAM__H #include #include "IOStream.h" #include "BackupStoreFilename.h" #include "CollectInBufferStream.h" #include "MD5Digest.h" #include "BackupStoreFile.h" #include "ReadLoggingStream.h" #include "RunStatusProvider.h" namespace BackupStoreFileCreation { // Diffing and creation of files share some implementation details. typedef struct _BlocksAvailableEntry { struct _BlocksAvailableEntry *mpNextInHashList; int32_t mSize; // size in clear uint32_t mWeakChecksum; // weak, rolling checksum uint8_t mStrongChecksum[MD5Digest::DigestLength]; // strong digest based checksum } BlocksAvailableEntry; } // -------------------------------------------------------------------------- // // Class // Name: BackupStoreFileEncodeStream // Purpose: Encode a file into a stream // Created: 8/12/03 // // -------------------------------------------------------------------------- class BackupStoreFileEncodeStream : public IOStream { public: BackupStoreFileEncodeStream(); ~BackupStoreFileEncodeStream(); typedef struct { int64_t mSpaceBefore; // amount of bytes which aren't taken out of blocks which go int32_t mBlocks; // number of block to reuse, starting at this one BackupStoreFileCreation::BlocksAvailableEntry *mpStartBlock; // may be null } RecipeInstruction; class Recipe : public std::vector { // NOTE: This class is rather tied in with the implementation of diffing. public: Recipe(BackupStoreFileCreation::BlocksAvailableEntry *pBlockIndex, int64_t NumBlocksInIndex, int64_t OtherFileID = 0); ~Recipe(); int64_t GetOtherFileID() {return mOtherFileID;} int64_t BlockPtrToIndex(BackupStoreFileCreation::BlocksAvailableEntry *pBlock) { return pBlock - mpBlockIndex; } private: BackupStoreFileCreation::BlocksAvailableEntry *mpBlockIndex; int64_t mNumBlocksInIndex; int64_t mOtherFileID; }; void Setup(const char *Filename, Recipe *pRecipe, int64_t ContainerID, const BackupStoreFilename &rStoreFilename, int64_t *pModificationTime, ReadLoggingStream::Logger* pLogger = NULL, RunStatusProvider* pRunStatusProvider = NULL); virtual int Read(void *pBuffer, int NBytes, int Timeout); virtual void Write(const void *pBuffer, int NBytes); virtual bool StreamDataLeft(); virtual bool StreamClosed(); private: enum { Status_Header = 0, Status_Blocks = 1, Status_BlockListing = 2, Status_Finished = 3 }; private: void EncodeCurrentBlock(); void CalculateBlockSizes(int64_t DataSize, int64_t &rNumBlocksOut, int32_t &rBlockSizeOut, int32_t &rLastBlockSizeOut); void SkipPreviousBlocksInInstruction(); void SetForInstruction(); void StoreBlockIndexEntry(int64_t WncSizeOrBlkIndex, int32_t ClearSize, uint32_t WeakChecksum, uint8_t *pStrongChecksum); private: Recipe *mpRecipe; IOStream *mpFile; // source file CollectInBufferStream mData; // buffer for header and index entries IOStream *mpLogging; RunStatusProvider* mpRunStatusProvider; int mStatus; bool mSendData; // true if there's file data to send (ie not a symlink) int64_t mTotalBlocks; // Total number of blocks in the file int64_t mAbsoluteBlockNumber; // The absolute block number currently being output // Instruction number int64_t mInstructionNumber; // All the below are within the current instruction int64_t mNumBlocks; // number of blocks. Last one will be a different size to the rest in most cases int64_t mCurrentBlock; int32_t mCurrentBlockEncodedSize; int32_t mPositionInCurrentBlock; // for reading out int32_t mBlockSize; // Basic block size of most of the blocks in the file int32_t mLastBlockSize; // the size (unencoded) of the last block in the file // Buffers uint8_t *mpRawBuffer; // buffer for raw data BackupStoreFile::EncodingBuffer mEncodedBuffer; // buffer for encoded data int32_t mAllocatedBufferSize; // size of above two allocated blocks uint64_t mEntryIVBase; // base for block entry IV }; #endif // BACKUPSTOREFILEENCODESTREAM__H boxbackup/lib/backupclient/BackupStoreObjectMagic.h0000664000175000017500000000201710347400657023215 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreObjectMagic.h // Purpose: Magic values for the start of objects in the backup store // Created: 19/11/03 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREOBJECTMAGIC__H #define BACKUPSTOREOBJECTMAGIC__H // Each of these values is the first 4 bytes of the object file. // Remember to swap from network to host byte order. // Magic value for file streams #define OBJECTMAGIC_FILE_MAGIC_VALUE_V1 0x66696C65 // Do not use v0 in any new code! #define OBJECTMAGIC_FILE_MAGIC_VALUE_V0 0x46494C45 // Magic for the block index at the file stream -- used to // ensure streams are reordered as expected #define OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 0x62696478 // Do not use v0 in any new code! #define OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0 0x46426C6B // Magic value for directory streams #define OBJECTMAGIC_DIR_MAGIC_VALUE 0x4449525F #endif // BACKUPSTOREOBJECTMAGIC__H boxbackup/lib/backupclient/RunStatusProvider.h0000664000175000017500000000144711053240535022364 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: RunStatusProvider.h // Purpose: Declares the RunStatusProvider interface. // Created: 2008/08/14 // // -------------------------------------------------------------------------- #ifndef RUNSTATUSPROVIDER__H #define RUNSTATUSPROVIDER__H // -------------------------------------------------------------------------- // // Class // Name: RunStatusProvider // Purpose: Provides a StopRun() method which returns true if // the current backup should be halted. // Created: 2005/11/15 // // -------------------------------------------------------------------------- class RunStatusProvider { public: virtual ~RunStatusProvider() { } virtual bool StopRun() = 0; }; #endif // RUNSTATUSPROVIDER__H boxbackup/lib/backupclient/BackupStoreObjectDump.cpp0000664000175000017500000001573711163676334023456 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreObjectDump.cpp // Purpose: Implementations of dumping objects to stdout/TRACE // Created: 3/5/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include "BackupStoreDirectory.h" #include "BackupStoreFile.h" #include "BackupStoreFileWire.h" #include "autogen_BackupStoreException.h" #include "BackupStoreFilename.h" #include "BackupClientFileAttributes.h" #include "BackupStoreObjectMagic.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: static void OutputLine(FILE *, bool, const char *, ...) // Purpose: Output a line for the object dumping, to file and/or trace... // Created: 3/5/04 // // -------------------------------------------------------------------------- static void OutputLine(FILE *file, bool ToTrace, const char *format, ...) { char text[512]; int r = 0; va_list ap; va_start(ap, format); r = vsnprintf(text, sizeof(text), format, ap); va_end(ap); if(file != 0) { ::fprintf(file, "%s", text); } if(ToTrace) { BOX_TRACE(text); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Dump(void *clibFileHandle, bool ToTrace) // Purpose: (first arg is FILE *, but avoid including stdio.h everywhere) // Dump the contents to a file, or trace. // Created: 3/5/04 // // -------------------------------------------------------------------------- void BackupStoreDirectory::Dump(void *clibFileHandle, bool ToTrace) { FILE *file = (FILE*)clibFileHandle; OutputLine(file, ToTrace, "Directory object.\nObject ID: %llx\nContainer ID: %llx\nNumber entries: %d\n"\ "Attributes mod time: %llx\nAttributes size: %d\n", mObjectID, mContainerID, mEntries.size(), mAttributesModTime, mAttributes.GetSize()); // So repeated filenames can be illustrated, even though they can't be decoded std::map nameNum; int nameNumI = 0; // Dump items OutputLine(file, ToTrace, "Items:\nID Size AttrHash AtSz NSz NIdx Flags\n"); for(std::vector::const_iterator i(mEntries.begin()); i != mEntries.end(); ++i) { // Choose file name index number for this file std::map::iterator nn(nameNum.find((*i)->GetName().GetEncodedFilename())); int ni = nameNumI; if(nn != nameNum.end()) { ni = nn->second; } else { nameNum[(*i)->GetName().GetEncodedFilename()] = nameNumI; ++nameNumI; } // Do dependencies char depends[128]; depends[0] = '\0'; int depends_l = 0; if((*i)->GetDependsNewer() != 0) { #ifdef _MSC_VER depends_l += ::sprintf(depends + depends_l, " depNew(%I64x)", (*i)->GetDependsNewer()); #else depends_l += ::sprintf(depends + depends_l, " depNew(%llx)", (long long)((*i)->GetDependsNewer())); #endif } if((*i)->GetDependsOlder() != 0) { #ifdef _MSC_VER depends_l += ::sprintf(depends + depends_l, " depOld(%I64x)", (*i)->GetDependsOlder()); #else depends_l += ::sprintf(depends + depends_l, " depOld(%llx)", (long long)((*i)->GetDependsOlder())); #endif } // Output item int16_t f = (*i)->GetFlags(); #ifdef WIN32 OutputLine(file, ToTrace, "%06I64x %4I64d %016I64x %4d %3d %4d%s%s%s%s%s%s\n", #else OutputLine(file, ToTrace, "%06llx %4lld %016llx %4d %3d %4d%s%s%s%s%s%s\n", #endif (*i)->GetObjectID(), (*i)->GetSizeInBlocks(), (*i)->GetAttributesHash(), (*i)->GetAttributes().GetSize(), (*i)->GetName().GetEncodedFilename().size(), ni, ((f & BackupStoreDirectory::Entry::Flags_File)?" file":""), ((f & BackupStoreDirectory::Entry::Flags_Dir)?" dir":""), ((f & BackupStoreDirectory::Entry::Flags_Deleted)?" del":""), ((f & BackupStoreDirectory::Entry::Flags_OldVersion)?" old":""), ((f & BackupStoreDirectory::Entry::Flags_RemoveASAP)?" removeASAP":""), depends); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DumpFile(void *, bool, IOStream &) // Purpose: (first arg is FILE *, but avoid including stdio.h everywhere) // Dump the contents to a file, or trace. // Created: 4/5/04 // // -------------------------------------------------------------------------- void BackupStoreFile::DumpFile(void *clibFileHandle, bool ToTrace, IOStream &rFile) { FILE *file = (FILE*)clibFileHandle; // Read header file_StreamFormat hdr; if(!rFile.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, IOStream::TimeOutInfinite)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } // Check and output header info if(hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V1) && hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V0)) { OutputLine(file, ToTrace, "File header doesn't have the correct magic, aborting dump\n"); return; } OutputLine(file, ToTrace, "File object.\nContainer ID: %llx\nModification time: %llx\n"\ "Max block clear size: %d\nOptions: %08x\nNum blocks: %d\n", box_ntoh64(hdr.mContainerID), box_ntoh64(hdr.mModificationTime), ntohl(hdr.mMaxBlockClearSize), ntohl(hdr.mOptions), box_ntoh64(hdr.mNumBlocks)); // Read the next two objects BackupStoreFilename fn; fn.ReadFromStream(rFile, IOStream::TimeOutInfinite); OutputLine(file, ToTrace, "Filename size: %d\n", fn.GetEncodedFilename().size()); BackupClientFileAttributes attr; attr.ReadFromStream(rFile, IOStream::TimeOutInfinite); OutputLine(file, ToTrace, "Attributes size: %d\n", attr.GetSize()); // Dump the blocks rFile.Seek(0, IOStream::SeekType_Absolute); BackupStoreFile::MoveStreamPositionToBlockIndex(rFile); // Read in header file_BlockIndexHeader bhdr; rFile.ReadFullBuffer(&bhdr, sizeof(bhdr), 0); if(bhdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) && bhdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0)) { OutputLine(file, ToTrace, "WARNING: Block header doesn't have the correct magic\n"); } // number of blocks int64_t nblocks = box_ntoh64(bhdr.mNumBlocks); OutputLine(file, ToTrace, "Other file ID (for block refs): %llx\nNum blocks (in blk hdr): %lld\n", box_ntoh64(bhdr.mOtherFileID), nblocks); // Dump info about each block OutputLine(file, ToTrace, "======== ===== ==========\n Index Where EncSz/Idx\n"); int64_t nnew = 0, nold = 0; for(int64_t b = 0; b < nblocks; ++b) { file_BlockIndexEntry en; if(!rFile.ReadFullBuffer(&en, sizeof(en), 0)) { OutputLine(file, ToTrace, "Didn't manage to read block %lld from file\n", b); continue; } int64_t s = box_ntoh64(en.mEncodedSize); if(s > 0) { nnew++; BOX_TRACE(std::setw(8) << b << " this s=" << std::setw(8) << s); } else { nold++; BOX_TRACE(std::setw(8) << b << " other i=" << std::setw(8) << 0 - s); } } BOX_TRACE("======== ===== =========="); } boxbackup/lib/backupclient/BackupStoreDirectory.h0000664000175000017500000002170610347400657023020 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreDirectory.h // Purpose: Representation of a backup directory // Created: 2003/08/26 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREDIRECTORY__H #define BACKUPSTOREDIRECTORY__H #include #include #include "BackupStoreFilenameClear.h" #include "StreamableMemBlock.h" #include "BoxTime.h" class IOStream; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreDirectory // Purpose: In memory representation of a directory // Created: 2003/08/26 // // -------------------------------------------------------------------------- class BackupStoreDirectory { public: BackupStoreDirectory(); BackupStoreDirectory(int64_t ObjectID, int64_t ContainerID); private: // Copying not allowed BackupStoreDirectory(const BackupStoreDirectory &rToCopy); public: ~BackupStoreDirectory(); class Entry { public: friend class BackupStoreDirectory; Entry(); ~Entry(); Entry(const Entry &rToCopy); Entry(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags, uint64_t AttributesHash); void ReadFromStream(IOStream &rStream, int Timeout); void WriteToStream(IOStream &rStream) const; const BackupStoreFilename &GetName() const {return mName;} box_time_t GetModificationTime() const {return mModificationTime;} int64_t GetObjectID() const {return mObjectID;} int64_t GetSizeInBlocks() const {return mSizeInBlocks;} int16_t GetFlags() const {return mFlags;} void AddFlags(int16_t Flags) {mFlags |= Flags;} void RemoveFlags(int16_t Flags) {mFlags &= ~Flags;} // Some things can be changed void SetName(const BackupStoreFilename &rNewName) {mName = rNewName;} void SetSizeInBlocks(int64_t SizeInBlocks) {mSizeInBlocks = SizeInBlocks;} // Attributes bool HasAttributes() const {return !mAttributes.IsEmpty();} void SetAttributes(const StreamableMemBlock &rAttr, uint64_t AttributesHash) {mAttributes.Set(rAttr); mAttributesHash = AttributesHash;} const StreamableMemBlock &GetAttributes() const {return mAttributes;} uint64_t GetAttributesHash() const {return mAttributesHash;} // Marks // The lowest mark number a version of a file of this name has ever had uint32_t GetMinMarkNumber() const {return mMinMarkNumber;} // The mark number on this file uint32_t GetMarkNumber() const {return mMarkNumber;} // Make sure these flags are synced with those in backupprocotol.txt // ListDirectory command enum { Flags_INCLUDE_EVERYTHING = -1, Flags_EXCLUDE_NOTHING = 0, Flags_EXCLUDE_EVERYTHING = 31, // make sure this is kept as sum of ones below! Flags_File = 1, Flags_Dir = 2, Flags_Deleted = 4, Flags_OldVersion = 8, Flags_RemoveASAP = 16 // if this flag is set, housekeeping will remove it as it is marked Deleted or OldVersion }; // characters for textual listing of files -- see bbackupquery/BackupQueries #define BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES "fdXoR" bool inline MatchesFlags(int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) { return ((FlagsMustBeSet == Flags_INCLUDE_EVERYTHING) || ((mFlags & FlagsMustBeSet) == FlagsMustBeSet)) && ((mFlags & FlagsNotToBeSet) == 0); }; // Get dependency info // new version this depends on int64_t GetDependsNewer() const {return mDependsNewer;} void SetDependsNewer(int64_t ObjectID) {mDependsNewer = ObjectID;} // older version which depends on this int64_t GetDependsOlder() const {return mDependsOlder;} void SetDependsOlder(int64_t ObjectID) {mDependsOlder = ObjectID;} // Dependency info saving bool HasDependencies() {return mDependsNewer != 0 || mDependsOlder != 0;} void ReadFromStreamDependencyInfo(IOStream &rStream, int Timeout); void WriteToStreamDependencyInfo(IOStream &rStream) const; private: BackupStoreFilename mName; box_time_t mModificationTime; int64_t mObjectID; int64_t mSizeInBlocks; int16_t mFlags; uint64_t mAttributesHash; StreamableMemBlock mAttributes; uint32_t mMinMarkNumber; uint32_t mMarkNumber; uint64_t mDependsNewer; // new version this depends on uint64_t mDependsOlder; // older version which depends on this }; void ReadFromStream(IOStream &rStream, int Timeout); void WriteToStream(IOStream &rStream, int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING, bool StreamAttributes = true, bool StreamDependencyInfo = true) const; Entry *AddEntry(const Entry &rEntryToCopy); Entry *AddEntry(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags, box_time_t AttributesModTime); void DeleteEntry(int64_t ObjectID); Entry *FindEntryByID(int64_t ObjectID) const; int64_t GetObjectID() const {return mObjectID;} int64_t GetContainerID() const {return mContainerID;} // Need to be able to update the container ID when moving objects void SetContainerID(int64_t ContainerID) {mContainerID = ContainerID;} // Purely for use of server -- not serialised into streams int64_t GetRevisionID() const {return mRevisionID;} void SetRevisionID(int64_t RevisionID) {mRevisionID = RevisionID;} unsigned int GetNumberOfEntries() const {return mEntries.size();} // User info -- not serialised into streams int64_t GetUserInfo1_SizeInBlocks() const {return mUserInfo1;} void SetUserInfo1_SizeInBlocks(int64_t UserInfo1) {mUserInfo1 = UserInfo1;} // Attributes bool HasAttributes() const {return !mAttributes.IsEmpty();} void SetAttributes(const StreamableMemBlock &rAttr, box_time_t AttributesModTime) {mAttributes.Set(rAttr); mAttributesModTime = AttributesModTime;} const StreamableMemBlock &GetAttributes() const {return mAttributes;} box_time_t GetAttributesModTime() const {return mAttributesModTime;} class Iterator { public: Iterator(const BackupStoreDirectory &rDir) : mrDir(rDir), i(rDir.mEntries.begin()) { } BackupStoreDirectory::Entry *Next(int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING) { // Skip over things which don't match the required flags while(i != mrDir.mEntries.end() && !(*i)->MatchesFlags(FlagsMustBeSet, FlagsNotToBeSet)) { ++i; } // Not the last one? if(i == mrDir.mEntries.end()) { return 0; } // Return entry, and increment return (*(i++)); } // WARNING: This function is really very inefficient. // Only use when you want to look up ONE filename, not in a loop looking up lots. // In a looping situation, cache the decrypted filenames in another memory structure. BackupStoreDirectory::Entry *FindMatchingClearName(const BackupStoreFilenameClear &rFilename, int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING) { // Skip over things which don't match the required flags or filename while( (i != mrDir.mEntries.end()) && ( (!(*i)->MatchesFlags(FlagsMustBeSet, FlagsNotToBeSet)) || (BackupStoreFilenameClear((*i)->GetName()).GetClearFilename() != rFilename.GetClearFilename()) ) ) { ++i; } // Not the last one? if(i == mrDir.mEntries.end()) { return 0; } // Return entry, and increment return (*(i++)); } private: const BackupStoreDirectory &mrDir; std::vector::const_iterator i; }; friend class Iterator; class ReverseIterator { public: ReverseIterator(const BackupStoreDirectory &rDir) : mrDir(rDir), i(rDir.mEntries.rbegin()) { } BackupStoreDirectory::Entry *Next(int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING) { // Skip over things which don't match the required flags while(i != mrDir.mEntries.rend() && !(*i)->MatchesFlags(FlagsMustBeSet, FlagsNotToBeSet)) { ++i; } // Not the last one? if(i == mrDir.mEntries.rend()) { return 0; } // Return entry, and increment return (*(i++)); } private: const BackupStoreDirectory &mrDir; std::vector::const_reverse_iterator i; }; friend class ReverseIterator; // For recovery of the store // Implemented in BackupStoreCheck2.cpp bool CheckAndFix(); void AddUnattactedObject(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags); bool NameInUse(const BackupStoreFilename &rName); // Don't use these functions in normal code! // For testing void TESTONLY_SetObjectID(int64_t ObjectID) {mObjectID = ObjectID;} // Debug and diagonistics void Dump(void *clibFileHandle, bool ToTrace); // first arg is FILE *, but avoid including stdio.h everywhere private: int64_t mRevisionID; int64_t mObjectID; int64_t mContainerID; std::vector mEntries; box_time_t mAttributesModTime; StreamableMemBlock mAttributes; int64_t mUserInfo1; }; #endif // BACKUPSTOREDIRECTORY__H boxbackup/lib/backupclient/BackupStoreFilenameClear.h0000664000175000017500000000367410347400657023547 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFilenameClear.h // Purpose: BackupStoreFilenames in the clear // Created: 2003/08/26 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREFILENAMECLEAR__H #define BACKUPSTOREFILENAMECLEAR__H #include "BackupStoreFilename.h" class CipherContext; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreFilenameClear // Purpose: BackupStoreFilenames, handling conversion from and to the in the clear version // Created: 2003/08/26 // // -------------------------------------------------------------------------- class BackupStoreFilenameClear : public BackupStoreFilename { public: BackupStoreFilenameClear(); BackupStoreFilenameClear(const std::string &rToEncode); BackupStoreFilenameClear(const BackupStoreFilenameClear &rToCopy); BackupStoreFilenameClear(const BackupStoreFilename &rToCopy); virtual ~BackupStoreFilenameClear(); // Because we need to use a different allocator for this class to avoid // nasty things happening, can't return this as a reference. Which is a // pity. But probably not too bad. #ifdef BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE const std::string GetClearFilename() const; #else const std::string &GetClearFilename() const; #endif void SetClearFilename(const std::string &rToEncode); // Setup for encryption of filenames static void SetBlowfishKey(const void *pKey, int KeyLength, const void *pIV, int IVLength); static void SetEncodingMethod(int Method); protected: void MakeClearAvailable() const; virtual void EncodedFilenameChanged(); void EncryptClear(const std::string &rToEncode, CipherContext &rCipherContext, int StoreAsEncoding); void DecryptEncoded(CipherContext &rCipherContext) const; private: mutable BackupStoreFilename_base mClearFilename; }; #endif // BACKUPSTOREFILENAMECLEAR__H boxbackup/lib/backupclient/BackupStoreFileCombine.cpp0000664000175000017500000003035010347400657023556 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileCombine.cpp // Purpose: File combining for BackupStoreFile // Created: 16/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupStoreFile.h" #include "BackupStoreFileWire.h" #include "BackupStoreObjectMagic.h" #include "BackupStoreException.h" #include "BackupStoreConstants.h" #include "BackupStoreFilename.h" #include "FileStream.h" #include "MemLeakFindOn.h" typedef struct { int64_t mFilePosition; } FromIndexEntry; static void LoadFromIndex(IOStream &rFrom, FromIndexEntry *pIndex, int64_t NumEntries); static void CopyData(IOStream &rDiffData, IOStream &rDiffIndex, int64_t DiffNumBlocks, IOStream &rFrom, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut); static void WriteNewIndex(IOStream &rDiff, int64_t DiffNumBlocks, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut); // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::CombineFile(IOStream &, IOStream &, IOStream &) // Purpose: Where rDiff is a store file which is incomplete as a result of a // diffing operation, rFrom is the file it is diffed from, and // rOut is the stream in which to place the result, the old file // and new file are combined into a file containing all the data. // rDiff2 is the same file as rDiff, opened again to get two // independent streams to the same file. // Created: 16/1/04 // // -------------------------------------------------------------------------- void BackupStoreFile::CombineFile(IOStream &rDiff, IOStream &rDiff2, IOStream &rFrom, IOStream &rOut) { // Read and copy the header. file_StreamFormat hdr; if(!rDiff.ReadFullBuffer(&hdr, sizeof(hdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Copy rOut.Write(&hdr, sizeof(hdr)); // Copy over filename and attributes // BLOCK { BackupStoreFilename filename; filename.ReadFromStream(rDiff, IOStream::TimeOutInfinite); filename.WriteToStream(rOut); StreamableMemBlock attr; attr.ReadFromStream(rDiff, IOStream::TimeOutInfinite); attr.WriteToStream(rOut); } // Read the header for the From file file_StreamFormat fromHdr; if(!rFrom.ReadFullBuffer(&fromHdr, sizeof(fromHdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(fromHdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Skip over the filename and attributes of the From file // BLOCK { BackupStoreFilename filename2; filename2.ReadFromStream(rFrom, IOStream::TimeOutInfinite); int32_t size_s; if(!rFrom.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) } int size = ntohl(size_s); // Skip forward the size rFrom.Seek(size, IOStream::SeekType_Relative); } // Allocate memory for the block index of the From file int64_t fromNumBlocks = box_ntoh64(fromHdr.mNumBlocks); // NOTE: An extra entry is required so that the length of the last block can be calculated FromIndexEntry *pFromIndex = (FromIndexEntry*)::malloc((fromNumBlocks+1) * sizeof(FromIndexEntry)); if(pFromIndex == 0) { throw std::bad_alloc(); } try { // Load the index from the From file, calculating the offsets in the // file as we go along, and enforce that everything should be present. LoadFromIndex(rFrom, pFromIndex, fromNumBlocks); // Read in the block index of the Diff file in small chunks, and output data // for each block, either from this file, or the other file. int64_t diffNumBlocks = box_ntoh64(hdr.mNumBlocks); CopyData(rDiff /* positioned at start of data */, rDiff2, diffNumBlocks, rFrom, pFromIndex, fromNumBlocks, rOut); // Read in the block index again, and output the new block index, simply // filling in the sizes of blocks from the old file. WriteNewIndex(rDiff, diffNumBlocks, pFromIndex, fromNumBlocks, rOut); // Free buffers ::free(pFromIndex); pFromIndex = 0; } catch(...) { // Clean up if(pFromIndex != 0) { ::free(pFromIndex); pFromIndex = 0; } throw; } } // -------------------------------------------------------------------------- // // Function // Name: static LoadFromIndex(IOStream &, FromIndexEntry *, int64_t) // Purpose: Static. Load the index from the From file // Created: 16/1/04 // // -------------------------------------------------------------------------- static void LoadFromIndex(IOStream &rFrom, FromIndexEntry *pIndex, int64_t NumEntries) { ASSERT(pIndex != 0); ASSERT(NumEntries >= 0); // Get the starting point in the file int64_t filePos = rFrom.GetPosition(); // Jump to the end of the file to read the index rFrom.Seek(0 - ((NumEntries * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End); // Read block index header file_BlockIndexHeader blkhdr; if(!rFrom.ReadFullBuffer(&blkhdr, sizeof(blkhdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 || (int64_t)box_ntoh64(blkhdr.mNumBlocks) != NumEntries) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // And then the block entries for(int64_t b = 0; b < NumEntries; ++b) { // Read file_BlockIndexEntry en; if(!rFrom.ReadFullBuffer(&en, sizeof(en), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } // Add to list pIndex[b].mFilePosition = filePos; // Encoded size? int64_t encodedSize = box_ntoh64(en.mEncodedSize); // Check that the block is actually there if(encodedSize <= 0) { THROW_EXCEPTION(BackupStoreException, OnCombineFromFileIsIncomplete) } // Move file pointer on filePos += encodedSize; } // Store the position in the very last entry, so the size of the last entry can be calculated pIndex[NumEntries].mFilePosition = filePos; } // -------------------------------------------------------------------------- // // Function // Name: static CopyData(IOStream &, IOStream &, int64_t, IOStream &, FromIndexEntry *, int64_t, IOStream &) // Purpose: Static. Copy data from the Diff and From file to the out file. // rDiffData is at beginning of data. // rDiffIndex at any position. // rFrom is at any position. // rOut is after the header, ready for data // Created: 16/1/04 // // -------------------------------------------------------------------------- static void CopyData(IOStream &rDiffData, IOStream &rDiffIndex, int64_t DiffNumBlocks, IOStream &rFrom, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut) { // Jump to the end of the diff file to read the index rDiffIndex.Seek(0 - ((DiffNumBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End); // Read block index header file_BlockIndexHeader diffBlkhdr; if(!rDiffIndex.ReadFullBuffer(&diffBlkhdr, sizeof(diffBlkhdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(diffBlkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 || (int64_t)box_ntoh64(diffBlkhdr.mNumBlocks) != DiffNumBlocks) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Record where the From file is int64_t fromPos = rFrom.GetPosition(); // Buffer data void *buffer = 0; int bufferSize = 0; try { // Read the blocks in! for(int64_t b = 0; b < DiffNumBlocks; ++b) { // Read file_BlockIndexEntry en; if(!rDiffIndex.ReadFullBuffer(&en, sizeof(en), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } // What's the size value stored in the entry int64_t encodedSize = box_ntoh64(en.mEncodedSize); // How much data will be read? int32_t blockSize = 0; if(encodedSize > 0) { // The block is actually in the diff file blockSize = encodedSize; } else { // It's in the from file. First, check to see if it's valid int64_t blockIdx = (0 - encodedSize); if(blockIdx > FromNumBlocks) { // References a block which doesn't actually exist THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Calculate size. This operation is safe because of the extra entry at the end blockSize = pFromIndex[blockIdx + 1].mFilePosition - pFromIndex[blockIdx].mFilePosition; } ASSERT(blockSize > 0); // Make sure there's memory available to copy this if(bufferSize < blockSize || buffer == 0) { // Free old block if(buffer != 0) { ::free(buffer); buffer = 0; bufferSize = 0; } // Allocate new block buffer = ::malloc(blockSize); if(buffer == 0) { throw std::bad_alloc(); } bufferSize = blockSize; } ASSERT(bufferSize >= blockSize); // Load in data from one of the files if(encodedSize > 0) { // Load from diff file if(!rDiffData.ReadFullBuffer(buffer, blockSize, 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } } else { // Locate and read the data from the from file int64_t blockIdx = (0 - encodedSize); // Seek if necessary if(fromPos != pFromIndex[blockIdx].mFilePosition) { rFrom.Seek(pFromIndex[blockIdx].mFilePosition, IOStream::SeekType_Absolute); fromPos = pFromIndex[blockIdx].mFilePosition; } // Read if(!rFrom.ReadFullBuffer(buffer, blockSize, 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } // Update fromPos to current position fromPos += blockSize; } // Write data to out file rOut.Write(buffer, blockSize); } // Free buffer, if allocated if(buffer != 0) { ::free(buffer); buffer = 0; } } catch(...) { if(buffer != 0) { ::free(buffer); buffer = 0; } throw; } } // -------------------------------------------------------------------------- // // Function // Name: static WriteNewIndex(IOStream &, int64_t, FromIndexEntry *, int64_t, IOStream &) // Purpose: Write the index to the out file, just copying from the diff file and // adjusting the entries. // Created: 16/1/04 // // -------------------------------------------------------------------------- static void WriteNewIndex(IOStream &rDiff, int64_t DiffNumBlocks, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut) { // Jump to the end of the diff file to read the index rDiff.Seek(0 - ((DiffNumBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End); // Read block index header file_BlockIndexHeader diffBlkhdr; if(!rDiff.ReadFullBuffer(&diffBlkhdr, sizeof(diffBlkhdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(diffBlkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 || (int64_t)box_ntoh64(diffBlkhdr.mNumBlocks) != DiffNumBlocks) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Write it out with a blanked out other file ID diffBlkhdr.mOtherFileID = box_hton64(0); rOut.Write(&diffBlkhdr, sizeof(diffBlkhdr)); // Rewrite the index for(int64_t b = 0; b < DiffNumBlocks; ++b) { file_BlockIndexEntry en; if(!rDiff.ReadFullBuffer(&en, sizeof(en), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } // What's the size value stored in the entry int64_t encodedSize = box_ntoh64(en.mEncodedSize); // Need to adjust it? if(encodedSize <= 0) { // This actually refers to a block in the from file. So rewrite this. int64_t blockIdx = (0 - encodedSize); if(blockIdx > FromNumBlocks) { // References a block which doesn't actually exist THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Calculate size. This operation is safe because of the extra entry at the end int32_t blockSize = pFromIndex[blockIdx + 1].mFilePosition - pFromIndex[blockIdx].mFilePosition; // Then replace entry en.mEncodedSize = box_hton64(((uint64_t)blockSize)); } // Write entry rOut.Write(&en, sizeof(en)); } } boxbackup/lib/backupclient/BackupStoreFileCmbIdx.cpp0000664000175000017500000002203710347400657023353 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileCmbIdx.cpp // Purpose: Combine indicies of a delta file and the file it's a diff from. // Created: 8/7/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "BackupStoreFile.h" #include "BackupStoreFileWire.h" #include "BackupStoreObjectMagic.h" #include "BackupStoreException.h" #include "BackupStoreConstants.h" #include "BackupStoreFilename.h" #include "MemLeakFindOn.h" // Hide from outside world namespace { class BSFCombinedIndexStream : public IOStream { public: BSFCombinedIndexStream(IOStream *pDiff); ~BSFCombinedIndexStream(); virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); virtual void Write(const void *pBuffer, int NBytes); virtual bool StreamDataLeft(); virtual bool StreamClosed(); virtual void Initialise(IOStream &rFrom); private: IOStream *mpDiff; bool mIsInitialised; bool mHeaderWritten; file_BlockIndexHeader mHeader; int64_t mNumEntriesToGo; int64_t mNumEntriesInFromFile; int64_t *mFromBlockSizes; // NOTE: Entries in network byte order }; }; // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::CombineFileIndices(IOStream &, IOStream &, bool) // Purpose: Given a diff file and the file it's a diff from, return a stream from which // can be read the index of the combined file, without actually combining them. // The stream of the diff must have a lifetime greater than or equal to the // lifetime of the returned stream object. The full "from" file stream // only needs to exist during the actual function call. // If you pass in dodgy files which aren't related, then you will either // get an error or bad results. So don't do that. // If DiffIsIndexOnly is true, then rDiff is assumed to be a stream positioned // at the beginning of the block index. Similarly for FromIsIndexOnly. // WARNING: Reads of the returned streams with buffer sizes less than 64 bytes // will not return any data. // Created: 8/7/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreFile::CombineFileIndices(IOStream &rDiff, IOStream &rFrom, bool DiffIsIndexOnly, bool FromIsIndexOnly) { // Reposition file pointers? if(!DiffIsIndexOnly) { MoveStreamPositionToBlockIndex(rDiff); } if(!FromIsIndexOnly) { MoveStreamPositionToBlockIndex(rFrom); } // Create object std::auto_ptr stream(new BSFCombinedIndexStream(&rDiff)); // Initialise it ((BSFCombinedIndexStream *)stream.get())->Initialise(rFrom); // And return the stream return stream; } // -------------------------------------------------------------------------- // // Function // Name: BSFCombinedIndexStream::BSFCombinedIndexStream() // Purpose: Private class. Constructor. // Created: 8/7/04 // // -------------------------------------------------------------------------- BSFCombinedIndexStream::BSFCombinedIndexStream(IOStream *pDiff) : mpDiff(pDiff), mIsInitialised(false), mHeaderWritten(false), mNumEntriesToGo(0), mNumEntriesInFromFile(0), mFromBlockSizes(0) { ASSERT(mpDiff != 0); } // -------------------------------------------------------------------------- // // Function // Name: BSFCombinedIndexStream::~BSFCombinedIndexStream() // Purpose: Private class. Destructor. // Created: 8/7/04 // // -------------------------------------------------------------------------- BSFCombinedIndexStream::~BSFCombinedIndexStream() { if(mFromBlockSizes != 0) { ::free(mFromBlockSizes); mFromBlockSizes = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BSFCombinedIndexStream::Initialise(IOStream &) // Purpose: Private class. Initalise from the streams (diff passed in constructor). // Both streams must have file pointer positioned at the block index. // Created: 8/7/04 // // -------------------------------------------------------------------------- void BSFCombinedIndexStream::Initialise(IOStream &rFrom) { // Paranoia is good. if(mIsInitialised) { THROW_EXCEPTION(BackupStoreException, Internal) } // Look at the diff file: Read in the header if(!mpDiff->ReadFullBuffer(&mHeader, sizeof(mHeader), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } if(ntohl(mHeader.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Read relevant data. mNumEntriesToGo = box_ntoh64(mHeader.mNumBlocks); // Adjust a bit to reflect the fact it's no longer a diff mHeader.mOtherFileID = box_hton64(0); // Now look at the from file: Read header file_BlockIndexHeader fromHdr; if(!rFrom.ReadFullBuffer(&fromHdr, sizeof(fromHdr), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } if(ntohl(fromHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Then... allocate memory for the list of sizes mNumEntriesInFromFile = box_ntoh64(fromHdr.mNumBlocks); mFromBlockSizes = (int64_t*)::malloc(mNumEntriesInFromFile * sizeof(int64_t)); if(mFromBlockSizes == 0) { throw std::bad_alloc(); } // And read them all in! for(int64_t b = 0; b < mNumEntriesInFromFile; ++b) { file_BlockIndexEntry e; if(!rFrom.ReadFullBuffer(&e, sizeof(e), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Check that the from file isn't a delta in itself if(box_ntoh64(e.mEncodedSize) <= 0) { THROW_EXCEPTION(BackupStoreException, OnCombineFromFileIsIncomplete) } // Store size (in network byte order) mFromBlockSizes[b] = e.mEncodedSize; } // Flag as initialised mIsInitialised = true; } // -------------------------------------------------------------------------- // // Function // Name: BSFCombinedIndexStream::Read(void *, int, int) // Purpose: Private class. As interface. // Created: 8/7/04 // // -------------------------------------------------------------------------- int BSFCombinedIndexStream::Read(void *pBuffer, int NBytes, int Timeout) { // Paranoia is good. if(!mIsInitialised || mFromBlockSizes == 0 || mpDiff == 0) { THROW_EXCEPTION(BackupStoreException, Internal) } int written = 0; // Header output yet? if(!mHeaderWritten) { // Enough space? if(NBytes < (int)sizeof(mHeader)) return 0; // Copy in ::memcpy(pBuffer, &mHeader, sizeof(mHeader)); NBytes -= sizeof(mHeader); written += sizeof(mHeader); // Flag it's done mHeaderWritten = true; } // How many entries can be written? int entriesToWrite = NBytes / sizeof(file_BlockIndexEntry); if(entriesToWrite > mNumEntriesToGo) { entriesToWrite = mNumEntriesToGo; } // Setup ready to go file_BlockIndexEntry *poutput = (file_BlockIndexEntry*)(((uint8_t*)pBuffer) + written); // Write entries for(int b = 0; b < entriesToWrite; ++b) { if(!mpDiff->ReadFullBuffer(&(poutput[b]), sizeof(file_BlockIndexEntry), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Does this need adjusting? int s = box_ntoh64(poutput[b].mEncodedSize); if(s <= 0) { // A reference to a block in the from file int block = 0 - s; ASSERT(block >= 0); if(block >= mNumEntriesInFromFile) { // That's not good, the block doesn't exist THROW_EXCEPTION(BackupStoreException, OnCombineFromFileIsIncomplete) } // Adjust the entry in the buffer poutput[b].mEncodedSize = mFromBlockSizes[block]; // stored in network byte order, no translation necessary } } // Update written count written += entriesToWrite * sizeof(file_BlockIndexEntry); mNumEntriesToGo -= entriesToWrite; return written; } // -------------------------------------------------------------------------- // // Function // Name: BSFCombinedIndexStream::Write(const void *, int) // Purpose: Private class. As interface. // Created: 8/7/04 // // -------------------------------------------------------------------------- void BSFCombinedIndexStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures) } // -------------------------------------------------------------------------- // // Function // Name: BSFCombinedIndexStream::StreamDataLeft() // Purpose: Private class. As interface // Created: 8/7/04 // // -------------------------------------------------------------------------- bool BSFCombinedIndexStream::StreamDataLeft() { return (!mHeaderWritten) || (mNumEntriesToGo > 0); } // -------------------------------------------------------------------------- // // Function // Name: BSFCombinedIndexStream::StreamClosed() // Purpose: Private class. As interface. // Created: 8/7/04 // // -------------------------------------------------------------------------- bool BSFCombinedIndexStream::StreamClosed() { return true; // doesn't do writing } boxbackup/lib/backupclient/BackupDaemonConfigVerify.cpp0000664000175000017500000001026511436002351024073 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupDaemonConfigVerify.cpp // Purpose: Configuration file definition for bbackupd // Created: 2003/10/10 // // -------------------------------------------------------------------------- #include "Box.h" #include "BackupDaemonConfigVerify.h" #include "Daemon.h" #include "BoxPortsAndFiles.h" #include "MemLeakFindOn.h" static const ConfigurationVerifyKey backuplocationkeys[] = { ConfigurationVerifyKey("ExcludeFile", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("ExcludeFilesRegex", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("ExcludeDir", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("ExcludeDirsRegex", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("AlwaysIncludeFile", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("AlwaysIncludeFilesRegex", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("AlwaysIncludeDir", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("AlwaysIncludeDirsRegex", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("Path", ConfigTest_Exists | ConfigTest_LastEntry) }; static const ConfigurationVerify backuplocations[] = { { "*", 0, backuplocationkeys, ConfigTest_LastEntry, 0 } }; static const ConfigurationVerifyKey verifyserverkeys[] = { DAEMON_VERIFY_SERVER_KEYS }; static const ConfigurationVerify verifyserver[] = { { "Server", 0, verifyserverkeys, ConfigTest_Exists, 0 }, { "BackupLocations", backuplocations, 0, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; static const ConfigurationVerifyKey verifyrootkeys[] = { ConfigurationVerifyKey("AccountNumber", ConfigTest_Exists | ConfigTest_IsUint32), ConfigurationVerifyKey("UpdateStoreInterval", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("MinimumFileAge", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("MaxUploadWait", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("MaxFileTimeInFuture", ConfigTest_IsInt, 172800), // file is uploaded if the file is this much in the future // (2 days default) ConfigurationVerifyKey("AutomaticBackup", ConfigTest_IsBool, true), ConfigurationVerifyKey("SyncAllowScript", 0), // script that returns "now" if backup is allowed now, or a number // of seconds to wait before trying again if not ConfigurationVerifyKey("MaximumDiffingTime", ConfigTest_IsInt), ConfigurationVerifyKey("DeleteRedundantLocationsAfter", ConfigTest_IsInt, 172800), ConfigurationVerifyKey("FileTrackingSizeThreshold", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("DiffingUploadSizeThreshold", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("StoreHostname", ConfigTest_Exists), ConfigurationVerifyKey("StorePort", ConfigTest_IsInt, BOX_PORT_BBSTORED), ConfigurationVerifyKey("ExtendedLogging", ConfigTest_IsBool, false), // extended log to syslog ConfigurationVerifyKey("ExtendedLogFile", 0), // extended log to a file ConfigurationVerifyKey("LogAllFileAccess", ConfigTest_IsBool, false), // enable logging reasons why each file is backed up or not ConfigurationVerifyKey("LogFile", 0), // enable logging to a file ConfigurationVerifyKey("LogFileLevel", 0), // set the level of verbosity of file logging ConfigurationVerifyKey("CommandSocket", 0), // not compulsory to have this ConfigurationVerifyKey("KeepAliveTime", ConfigTest_IsInt), ConfigurationVerifyKey("StoreObjectInfoFile", 0), // optional ConfigurationVerifyKey("NotifyScript", 0), // optional script to run when backup needs attention, eg store full ConfigurationVerifyKey("NotifyAlways", ConfigTest_IsBool, false), // option to disable the suppression of duplicate notifications ConfigurationVerifyKey("CertificateFile", ConfigTest_Exists), ConfigurationVerifyKey("PrivateKeyFile", ConfigTest_Exists), ConfigurationVerifyKey("TrustedCAsFile", ConfigTest_Exists), ConfigurationVerifyKey("KeysFile", ConfigTest_Exists), ConfigurationVerifyKey("DataDirectory", ConfigTest_Exists | ConfigTest_LastEntry), }; const ConfigurationVerify BackupDaemonConfigVerify = { "root", verifyserver, verifyrootkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; boxbackup/lib/backupclient/BackupStoreFileCryptVar.h0000664000175000017500000000220510347400657023417 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileCryptVar.h // Purpose: Cryptographic keys for backup store files // Created: 12/1/04 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREFILECRYPTVAR__H #define BACKUPSTOREFILECRYPTVAR__H #include "CipherContext.h" // Hide private static variables from the rest of the world by putting them // as static variables in a namespace. // -- don't put them as static class variables to avoid openssl/evp.h being // included all over the project. namespace BackupStoreFileCryptVar { // Keys for the main file data extern CipherContext sBlowfishEncrypt; extern CipherContext sBlowfishDecrypt; // Use AES when available #ifndef HAVE_OLD_SSL extern CipherContext sAESEncrypt; extern CipherContext sAESDecrypt; #endif // How encoding will be done extern CipherContext *spEncrypt; extern uint8_t sEncryptCipherType; // Keys for the block indicies extern CipherContext sBlowfishEncryptBlockEntry; extern CipherContext sBlowfishDecryptBlockEntry; } #endif // BACKUPSTOREFILECRYPTVAR__H boxbackup/lib/backupclient/BackupStoreFileDiff.cpp0000664000175000017500000007722511165365160023064 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileDiff.cpp // Purpose: Functions relating to diffing BackupStoreFiles // Created: 12/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #ifdef HAVE_TIME_H #include #elif HAVE_SYS_TIME_H #include #endif #include "BackupStoreConstants.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" #include "BackupStoreFileCryptVar.h" #include "BackupStoreFileEncodeStream.h" #include "BackupStoreFileWire.h" #include "BackupStoreObjectMagic.h" #include "CommonException.h" #include "FileStream.h" #include "MD5Digest.h" #include "RollingChecksum.h" #include "Timer.h" #include "MemLeakFindOn.h" #include using namespace BackupStoreFileCryptVar; using namespace BackupStoreFileCreation; // By default, don't trace out details of the diff as we go along -- would fill up logs significantly. // But it's useful for the test. #ifndef BOX_RELEASE_BUILD bool BackupStoreFile::TraceDetailsOfDiffProcess = false; #endif static void LoadIndex(IOStream &rBlockIndex, int64_t ThisID, BlocksAvailableEntry **ppIndex, int64_t &rNumBlocksOut, int Timeout, bool &rCanDiffFromThis); static void FindMostUsedSizes(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES]); static void SearchForMatchingBlocks(IOStream &rFile, std::map &rFoundBlocks, BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES], DiffTimer *pDiffTimer); static void SetupHashTable(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t BlockSize, BlocksAvailableEntry **pHashTable); static bool SecondStageMatch(BlocksAvailableEntry *pFirstInHashList, RollingChecksum &fastSum, uint8_t *pBeginnings, uint8_t *pEndings, int Offset, int32_t BlockSize, int64_t FileBlockNumber, BlocksAvailableEntry *pIndex, std::map &rFoundBlocks); static void GenerateRecipe(BackupStoreFileEncodeStream::Recipe &rRecipe, BlocksAvailableEntry *pIndex, int64_t NumBlocks, std::map &rFoundBlocks, int64_t SizeOfInputFile); // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::MoveStreamPositionToBlockIndex(IOStream &) // Purpose: Move the file pointer in this stream to just before the block index. // Assumes that the stream is at the beginning, seekable, and // reading from the stream is OK. // Created: 12/1/04 // // -------------------------------------------------------------------------- void BackupStoreFile::MoveStreamPositionToBlockIndex(IOStream &rStream) { // Size of file int64_t fileSize = rStream.BytesLeftToRead(); // Get header file_StreamFormat hdr; // Read the header if(!rStream.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, IOStream::TimeOutInfinite)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Check magic number if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1 #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0 #endif ) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Work out where the index is int64_t numBlocks = box_ntoh64(hdr.mNumBlocks); int64_t blockHeaderPosFromEnd = ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)); // Sanity check if(blockHeaderPosFromEnd > static_cast(fileSize - sizeof(file_StreamFormat))) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Seek to that position rStream.Seek(0 - blockHeaderPosFromEnd, IOStream::SeekType_End); // Done. Stream now in right position (as long as the file is formatted correctly) } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::EncodeFileDiff(const char *, int64_t, const BackupStoreFilename &, int64_t, IOStream &, int64_t *) // Purpose: Similar to EncodeFile, but takes the object ID of the file it's // diffing from, and the index of the blocks in a stream. It'll then // calculate which blocks can be reused from that old file. // The timeout is the timeout value for reading the diff block index. // If pIsCompletelyDifferent != 0, it will be set to true if the // the two files are completely different (do not share any block), false otherwise. // // Created: 12/1/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreFile::EncodeFileDiff ( const char *Filename, int64_t ContainerID, const BackupStoreFilename &rStoreFilename, int64_t DiffFromObjectID, IOStream &rDiffFromBlockIndex, int Timeout, DiffTimer *pDiffTimer, int64_t *pModificationTime, bool *pIsCompletelyDifferent) { // Is it a symlink? { EMU_STRUCT_STAT st; if(EMU_LSTAT(Filename, &st) != 0) { THROW_EXCEPTION(CommonException, OSFileError) } if((st.st_mode & S_IFLNK) == S_IFLNK) { // Don't do diffs for symlinks if(pIsCompletelyDifferent != 0) { *pIsCompletelyDifferent = true; } return EncodeFile(Filename, ContainerID, rStoreFilename, pModificationTime); } } // Load in the blocks BlocksAvailableEntry *pindex = 0; int64_t blocksInIndex = 0; bool canDiffFromThis = false; LoadIndex(rDiffFromBlockIndex, DiffFromObjectID, &pindex, blocksInIndex, Timeout, canDiffFromThis); // BOX_TRACE("Diff: Blocks in index: " << blocksInIndex); if(!canDiffFromThis) { // Don't do diffing... if(pIsCompletelyDifferent != 0) { *pIsCompletelyDifferent = true; } return EncodeFile(Filename, ContainerID, rStoreFilename, pModificationTime); } // Pointer to recipe we're going to create BackupStoreFileEncodeStream::Recipe *precipe = 0; try { // Find which sizes should be scanned int32_t sizesToScan[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES]; FindMostUsedSizes(pindex, blocksInIndex, sizesToScan); // Flag for reporting to the user bool completelyDifferent; // BLOCK { // Search the file to find matching blocks std::map foundBlocks; // map of offset in file to index in block index int64_t sizeOfInputFile = 0; // BLOCK { FileStream file(Filename); // Get size of file sizeOfInputFile = file.BytesLeftToRead(); // Find all those lovely matching blocks SearchForMatchingBlocks(file, foundBlocks, pindex, blocksInIndex, sizesToScan, pDiffTimer); // Is it completely different? completelyDifferent = (foundBlocks.size() == 0); } // Create a recipe -- if the two files are completely different, don't put the from file ID in the recipe. precipe = new BackupStoreFileEncodeStream::Recipe(pindex, blocksInIndex, completelyDifferent?(0):(DiffFromObjectID)); BlocksAvailableEntry *pindexKeptRef = pindex; // we need this later, but must set pindex == 0 now, because of exceptions pindex = 0; // Recipe now has ownership // Fill it in GenerateRecipe(*precipe, pindexKeptRef, blocksInIndex, foundBlocks, sizeOfInputFile); } // foundBlocks no longer required // Create the stream std::auto_ptr stream(new BackupStoreFileEncodeStream); // Do the initial setup ((BackupStoreFileEncodeStream*)stream.get())->Setup(Filename, precipe, ContainerID, rStoreFilename, pModificationTime); precipe = 0; // Stream has taken ownership of this // Tell user about completely different status? if(pIsCompletelyDifferent != 0) { *pIsCompletelyDifferent = completelyDifferent; } // Return the stream for the caller return stream; } catch(...) { // cleanup if(pindex != 0) { ::free(pindex); pindex = 0; } if(precipe != 0) { delete precipe; precipe = 0; } throw; } } // -------------------------------------------------------------------------- // // Function // Name: static LoadIndex(IOStream &, int64_t, BlocksAvailableEntry **, int64_t, bool &) // Purpose: Read in an index, and decrypt, and store in the in memory block format. // rCanDiffFromThis is set to false if the version of the from file is too old. // Created: 12/1/04 // // -------------------------------------------------------------------------- static void LoadIndex(IOStream &rBlockIndex, int64_t ThisID, BlocksAvailableEntry **ppIndex, int64_t &rNumBlocksOut, int Timeout, bool &rCanDiffFromThis) { // Reset rNumBlocksOut = 0; rCanDiffFromThis = false; // Read header file_BlockIndexHeader hdr; if(!rBlockIndex.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, Timeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE // Check against backwards comptaibility stuff if(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0)) { // Won't diff against old version // Absorb rest of stream char buffer[2048]; while(rBlockIndex.StreamDataLeft()) { rBlockIndex.Read(buffer, sizeof(buffer), 1000 /* 1 sec timeout */); } // Tell caller rCanDiffFromThis = false; return; } #endif // Check magic if(hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Check that we're not trying to diff against a file which references blocks from another file if(((int64_t)box_ntoh64(hdr.mOtherFileID)) != 0) { THROW_EXCEPTION(BackupStoreException, CannotDiffAnIncompleteStoreFile) } // Mark as an acceptable diff. rCanDiffFromThis = true; // Get basic information int64_t numBlocks = box_ntoh64(hdr.mNumBlocks); uint64_t entryIVBase = box_ntoh64(hdr.mEntryIVBase); //TODO: Verify that these sizes look reasonable // Allocate space for the index BlocksAvailableEntry *pindex = (BlocksAvailableEntry*)::malloc(sizeof(BlocksAvailableEntry) * numBlocks); if(pindex == 0) { throw std::bad_alloc(); } try { for(int64_t b = 0; b < numBlocks; ++b) { // Read an entry from the stream file_BlockIndexEntry entry; if(!rBlockIndex.ReadFullBuffer(&entry, sizeof(entry), 0 /* not interested in bytes read if this fails */, Timeout)) { // Couldn't read entry THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Calculate IV for this entry uint64_t iv = entryIVBase; iv += b; // Network byte order iv = box_hton64(iv); sBlowfishDecryptBlockEntry.SetIV(&iv); // Decrypt the encrypted section file_BlockIndexEntryEnc entryEnc; int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc), entry.mEnEnc, sizeof(entry.mEnEnc)); if(sectionSize != sizeof(entryEnc)) { THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength) } // Check that we're not trying to diff against a file which references blocks from another file if(((int64_t)box_ntoh64(entry.mEncodedSize)) <= 0) { THROW_EXCEPTION(BackupStoreException, CannotDiffAnIncompleteStoreFile) } // Store all the required information pindex[b].mpNextInHashList = 0; // hash list not set up yet pindex[b].mSize = ntohl(entryEnc.mSize); pindex[b].mWeakChecksum = ntohl(entryEnc.mWeakChecksum); ::memcpy(pindex[b].mStrongChecksum, entryEnc.mStrongChecksum, sizeof(pindex[b].mStrongChecksum)); } // Store index pointer for called ASSERT(ppIndex != 0); *ppIndex = pindex; // Store number of blocks for caller rNumBlocksOut = numBlocks; } catch(...) { // clean up and send the exception along its way ::free(pindex); throw; } } // -------------------------------------------------------------------------- // // Function // Name: static FindMostUsedSizes(BlocksAvailableEntry *, int64_t, int32_t[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES]) // Purpose: Finds the most commonly used block sizes in the index // Created: 12/1/04 // // -------------------------------------------------------------------------- static void FindMostUsedSizes(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES]) { // Array for lengths int64_t sizeCounts[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES]; // Set arrays to lots of zeros (= unused entries) for(int l = 0; l < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++l) { Sizes[l] = 0; sizeCounts[l] = 0; } // Array for collecting sizes std::map foundSizes; // Run through blocks and make a count of the entries for(int64_t b = 0; b < NumBlocks; ++b) { // Only if the block size is bigger than the minimum size we'll scan for if(pIndex[b].mSize > BACKUP_FILE_DIFF_MIN_BLOCK_SIZE) { // Find entry? std::map::const_iterator f(foundSizes.find(pIndex[b].mSize)); if(f != foundSizes.end()) { // Increment existing entry foundSizes[pIndex[b].mSize] = foundSizes[pIndex[b].mSize] + 1; } else { // New entry foundSizes[pIndex[b].mSize] = 1; } } } // Make the block sizes for(std::map::const_iterator i(foundSizes.begin()); i != foundSizes.end(); ++i) { // Find the position of the size in the array for(int t = 0; t < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++t) { // Instead of sorting on the raw count of blocks, // take the file area covered by this block size. if(i->second * i->first > sizeCounts[t] * Sizes[t]) { // Then this size belong before this entry -- shuffle them up for(int s = (BACKUP_FILE_DIFF_MAX_BLOCK_SIZES - 1); s >= t; --s) { Sizes[s] = Sizes[s-1]; sizeCounts[s] = sizeCounts[s-1]; } // Insert this size Sizes[t] = i->first; sizeCounts[t] = i->second; // Shouldn't do any more searching break; } } } // trace the size table in debug builds #ifndef BOX_RELEASE_BUILD if(BackupStoreFile::TraceDetailsOfDiffProcess) { for(int t = 0; t < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++t) { BOX_TRACE("Diff block size " << t << ": " << Sizes[t] << " (count = " << sizeCounts[t] << ")"); } } #endif } // -------------------------------------------------------------------------- // // Function // Name: static SearchForMatchingBlocks(IOStream &, std::map &, BlocksAvailableEntry *, int64_t, int32_t[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES]) // Purpose: Find the matching blocks within the file. // Created: 12/1/04 // // -------------------------------------------------------------------------- static void SearchForMatchingBlocks(IOStream &rFile, std::map &rFoundBlocks, BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES], DiffTimer *pDiffTimer) { Timer maximumDiffingTime(0, "MaximumDiffingTime"); if(pDiffTimer && pDiffTimer->IsManaged()) { maximumDiffingTime = Timer(pDiffTimer->GetMaximumDiffingTime(), "MaximumDiffingTime"); } std::map goodnessOfFit; // Allocate the hash lookup table BlocksAvailableEntry **phashTable = (BlocksAvailableEntry **)::malloc(sizeof(BlocksAvailableEntry *) * (64*1024)); // Choose a size for the buffer, just a little bit more than the maximum block size int32_t bufSize = Sizes[0]; for(int z = 1; z < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++z) { if(Sizes[z] > bufSize) bufSize = Sizes[z]; } bufSize += 4; ASSERT(bufSize > Sizes[0]); ASSERT(bufSize > 0); if(bufSize > (BACKUP_FILE_MAX_BLOCK_SIZE + 1024)) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // TODO: Because we read in the file a scanned block size at a time, // it is likely to be inefficient. Probably will be much better to // calculate checksums for all block sizes in a single pass. // Allocate the buffers. uint8_t *pbuffer0 = (uint8_t *)::malloc(bufSize); uint8_t *pbuffer1 = (uint8_t *)::malloc(bufSize); try { // Check buffer allocation if(pbuffer0 == 0 || pbuffer1 == 0 || phashTable == 0) { // If a buffer got allocated, it will be cleaned up in the catch block throw std::bad_alloc(); } // Flag to abort the run, if too many blocks are found -- avoid using // huge amounts of processor time when files contain many similar blocks. bool abortSearch = false; // Search for each block size in turn // NOTE: Do the smallest size first, so that the scheme for adding // entries in the found list works as expected and replaces smallers block // with larger blocks when it finds matches at the same offset in the file. for(int s = BACKUP_FILE_DIFF_MAX_BLOCK_SIZES - 1; s >= 0; --s) { ASSERT(Sizes[s] <= bufSize); BOX_TRACE("Diff pass " << s << ", for block size " << Sizes[s]); // Check we haven't finished if(Sizes[s] == 0) { // empty entry, try next size continue; } // Set up the hash table entries SetupHashTable(pIndex, NumBlocks, Sizes[s], phashTable); // Shift file position to beginning rFile.Seek(0, IOStream::SeekType_Absolute); // Read first block if(rFile.Read(pbuffer0, Sizes[s]) != Sizes[s]) { // Size of file too short to match -- do next size continue; } // Setup block pointers uint8_t *beginnings = pbuffer0; uint8_t *endings = pbuffer1; int offset = 0; // Calculate the first checksum, ready for rolling RollingChecksum rolling(beginnings, Sizes[s]); // Then roll, until the file is exhausted int64_t fileBlockNumber = 0; int64_t fileOffset = 0; int rollOverInitialBytes = 0; while(true) { if(maximumDiffingTime.HasExpired()) { ASSERT(pDiffTimer != NULL); BOX_INFO("MaximumDiffingTime reached - " "suspending file diff"); abortSearch = true; break; } if(pDiffTimer) { pDiffTimer->DoKeepAlive(); } // Load in another block of data, and record how big it is int bytesInEndings = rFile.Read(endings, Sizes[s]); int tmp; // Skip any bytes from a previous matched block if(rollOverInitialBytes > 0 && offset < bytesInEndings) { int spaceLeft = bytesInEndings - offset; int thisRoll = (rollOverInitialBytes > spaceLeft) ? spaceLeft : rollOverInitialBytes; rolling.RollForwardSeveral(beginnings+offset, endings+offset, Sizes[s], thisRoll); offset += thisRoll; fileOffset += thisRoll; rollOverInitialBytes -= thisRoll; if(rollOverInitialBytes) { goto refresh; } } if(goodnessOfFit.count(fileOffset)) { tmp = goodnessOfFit[fileOffset]; } else { tmp = 0; } if(tmp >= Sizes[s]) { // Skip over bigger ready-matched blocks completely rollOverInitialBytes = tmp; int spaceLeft = bytesInEndings - offset; int thisRoll = (rollOverInitialBytes > spaceLeft) ? spaceLeft : rollOverInitialBytes; rolling.RollForwardSeveral(beginnings+offset, endings+offset, Sizes[s], thisRoll); offset += thisRoll; fileOffset += thisRoll; rollOverInitialBytes -= thisRoll; if(rollOverInitialBytes) { goto refresh; } } while(offset < bytesInEndings) { // Is current checksum in hash list? uint16_t hash = rolling.GetComponentForHashing(); if(phashTable[hash] != 0 && (goodnessOfFit.count(fileOffset) == 0 || goodnessOfFit[fileOffset] < Sizes[s])) { if(SecondStageMatch(phashTable[hash], rolling, beginnings, endings, offset, Sizes[s], fileBlockNumber, pIndex, rFoundBlocks)) { BOX_TRACE("Found block match for " << hash << " of " << Sizes[s] << " bytes at offset " << fileOffset); goodnessOfFit[fileOffset] = Sizes[s]; // Block matched, roll the checksum forward to the next block without doing // any more comparisons, because these are pointless (as any more matches will be ignored when // the recipe is generated) and just take up valuable processor time. Edge cases are // especially nasty, using huge amounts of time and memory. int skip = Sizes[s]; if(offset < bytesInEndings && skip > 0) { int spaceLeft = bytesInEndings - offset; int thisRoll = (skip > spaceLeft) ? spaceLeft : skip; rolling.RollForwardSeveral(beginnings+offset, endings+offset, Sizes[s], thisRoll); offset += thisRoll; fileOffset += thisRoll; skip -= thisRoll; } // Not all the bytes necessary will have been skipped, so get them // skipped after the next block is loaded. rollOverInitialBytes = skip; // End this loop, so the final byte isn't used again break; } else { BOX_TRACE("False alarm match for " << hash << " of " << Sizes[s] << " bytes at offset " << fileOffset); } int64_t NumBlocksFound = static_cast( rFoundBlocks.size()); int64_t MaxBlocksFound = NumBlocks * BACKUP_FILE_DIFF_MAX_BLOCK_FIND_MULTIPLE; if(NumBlocksFound > MaxBlocksFound) { abortSearch = true; break; } } // Roll checksum forward rolling.RollForward(beginnings[offset], endings[offset], Sizes[s]); // Increment offsets ++offset; ++fileOffset; } if(abortSearch) break; refresh: // Finished? if(bytesInEndings != Sizes[s]) { // No more data in file -- check the final block // (Do a copy and paste of 5 lines of code instead of introducing a comparison for // each byte of the file) uint16_t hash = rolling.GetComponentForHashing(); if(phashTable[hash] != 0 && (goodnessOfFit.count(fileOffset) == 0 || goodnessOfFit[fileOffset] < Sizes[s])) { if(SecondStageMatch(phashTable[hash], rolling, beginnings, endings, offset, Sizes[s], fileBlockNumber, pIndex, rFoundBlocks)) { goodnessOfFit[fileOffset] = Sizes[s]; } } // finish break; } // Switch buffers, reset offset beginnings = endings; endings = (beginnings == pbuffer0)?(pbuffer1):(pbuffer0); // ie the other buffer offset = 0; // And count the blocks which have been done ++fileBlockNumber; } if(abortSearch) break; } // Free buffers and hash table ::free(pbuffer1); pbuffer1 = 0; ::free(pbuffer0); pbuffer0 = 0; ::free(phashTable); phashTable = 0; } catch(...) { // Cleanup and throw if(pbuffer1 != 0) ::free(pbuffer1); if(pbuffer0 != 0) ::free(pbuffer0); if(phashTable != 0) ::free(phashTable); throw; } #ifndef BOX_RELEASE_BUILD if(BackupStoreFile::TraceDetailsOfDiffProcess) { // Trace out the found blocks in debug mode BOX_TRACE("Diff: list of found blocks"); BOX_TRACE("======== ======== ======== ========"); BOX_TRACE(" Offset BlkIdx Size Movement"); for(std::map::const_iterator i(rFoundBlocks.begin()); i != rFoundBlocks.end(); ++i) { int64_t orgLoc = 0; for(int64_t b = 0; b < i->second; ++b) { orgLoc += pIndex[b].mSize; } BOX_TRACE(std::setw(8) << i->first << " " << std::setw(8) << i->second << " " << std::setw(8) << pIndex[i->second].mSize << " " << std::setw(8) << (i->first - orgLoc)); } BOX_TRACE("======== ======== ======== ========"); } #endif } // -------------------------------------------------------------------------- // // Function // Name: static SetupHashTable(BlocksAvailableEntry *, int64_t, in32_t, BlocksAvailableEntry **) // Purpose: Set up the hash table ready for a scan // Created: 14/1/04 // // -------------------------------------------------------------------------- static void SetupHashTable(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t BlockSize, BlocksAvailableEntry **pHashTable) { // Set all entries in the hash table to zero ::memset(pHashTable, 0, (sizeof(BlocksAvailableEntry *) * (64*1024))); // Scan through the blocks, building the hash table for(int64_t b = 0; b < NumBlocks; ++b) { // Only look at the required block size if(pIndex[b].mSize == BlockSize) { // Get the value under which to hash this entry uint16_t hash = RollingChecksum::ExtractHashingComponent(pIndex[b].mWeakChecksum); // Already present in table? if(pHashTable[hash] != 0) { //BOX_TRACE("Another hash entry for " << hash << " found"); // Yes -- need to set the pointer in this entry to the current entry to build the linked list pIndex[b].mpNextInHashList = pHashTable[hash]; } // Put a pointer to this entry in the hash table pHashTable[hash] = pIndex + b; } } } // -------------------------------------------------------------------------- // // Function // Name: static bool SecondStageMatch(xxx) // Purpose: When a match in the hash table is found, scan for second stage match using strong checksum. // Created: 14/1/04 // // -------------------------------------------------------------------------- static bool SecondStageMatch(BlocksAvailableEntry *pFirstInHashList, RollingChecksum &fastSum, uint8_t *pBeginnings, uint8_t *pEndings, int Offset, int32_t BlockSize, int64_t FileBlockNumber, BlocksAvailableEntry *pIndex, std::map &rFoundBlocks) { // Check parameters ASSERT(pBeginnings != 0); ASSERT(pEndings != 0); ASSERT(Offset >= 0); ASSERT(BlockSize > 0); ASSERT(pFirstInHashList != 0); ASSERT(pIndex != 0); #ifndef BOX_RELEASE_BUILD uint16_t DEBUG_Hash = fastSum.GetComponentForHashing(); #endif uint32_t Checksum = fastSum.GetChecksum(); // Before we go to the expense of the MD5, make sure it's a darn good match on the checksum we already know. BlocksAvailableEntry *scan = pFirstInHashList; bool found=false; while(scan != 0) { if(scan->mWeakChecksum == Checksum) { found = true; break; } scan = scan->mpNextInHashList; } if(!found) { return false; } // Calculate the strong MD5 digest for this block MD5Digest strong; // Add the data from the beginnings strong.Add(pBeginnings + Offset, BlockSize - Offset); // Add any data from the endings if(Offset > 0) { strong.Add(pEndings, Offset); } strong.Finish(); // Then go through the entries in the hash list, comparing with the strong digest calculated scan = pFirstInHashList; //BOX_TRACE("second stage match"); while(scan != 0) { //BOX_TRACE("scan size " << scan->mSize << // ", block size " << BlockSize << // ", hash " << Hash); ASSERT(scan->mSize == BlockSize); ASSERT(RollingChecksum::ExtractHashingComponent(scan->mWeakChecksum) == DEBUG_Hash); // Compare? if(strong.DigestMatches(scan->mStrongChecksum)) { //BOX_TRACE("Match!\n"); // Found! Add to list of found blocks... int64_t fileOffset = (FileBlockNumber * BlockSize) + Offset; int64_t blockIndex = (scan - pIndex); // pointer arthmitic is frowned upon. But most efficient way of doing it here -- alternative is to use more memory // We do NOT search for smallest blocks first, as this code originally assumed. // To prevent this from potentially overwriting a better match, the caller must determine // the relative "goodness" of any existing match and this one, and avoid the call if it // could be detrimental. rFoundBlocks[fileOffset] = blockIndex; // No point in searching further, report success return true; } // Next scan = scan->mpNextInHashList; } // Not matched return false; } // -------------------------------------------------------------------------- // // Function // Name: static GenerateRecipe(BackupStoreFileEncodeStream::Recipe &, BlocksAvailableEntry *, int64_t, std::map &) // Purpose: Fills in the recipe from the found block list // Created: 15/1/04 // // -------------------------------------------------------------------------- static void GenerateRecipe(BackupStoreFileEncodeStream::Recipe &rRecipe, BlocksAvailableEntry *pIndex, int64_t NumBlocks, std::map &rFoundBlocks, int64_t SizeOfInputFile) { // NOTE: This function could be a lot more sophisiticated. For example, if // a small block overlaps a big block like this // **** // ******************************* // then the small block will be used, not the big one. But it'd be better to // just ignore the small block and keep the big one. However, some stats should // be gathered about real world files before writing complex code which might // go wrong. // Initialise a blank instruction BackupStoreFileEncodeStream::RecipeInstruction instruction; #define RESET_INSTRUCTION \ instruction.mSpaceBefore = 0; \ instruction.mBlocks = 0; \ instruction.mpStartBlock = 0; RESET_INSTRUCTION // First, a special case for when there are no found blocks if(rFoundBlocks.size() == 0) { // No blocks, just a load of space instruction.mSpaceBefore = SizeOfInputFile; rRecipe.push_back(instruction); #ifndef BOX_RELEASE_BUILD if(BackupStoreFile::TraceDetailsOfDiffProcess) { BOX_TRACE("Diff: Default recipe generated, " << SizeOfInputFile << " bytes of file"); } #endif // Don't do anything return; } // Current location int64_t loc = 0; // Then iterate through the list, generating the recipe std::map::const_iterator i(rFoundBlocks.begin()); ASSERT(i != rFoundBlocks.end()); // check logic // Counting for debug tracing #ifndef BOX_RELEASE_BUILD int64_t debug_NewBytesFound = 0; int64_t debug_OldBlocksUsed = 0; #endif for(; i != rFoundBlocks.end(); ++i) { // Remember... map is (position in file) -> (index of block in pIndex) if(i->first < loc) { // This block overlaps the last one continue; } else if(i->first > loc) { // There's a gap between the end of the last thing and this block. // If there's an instruction waiting, push it onto the list if(instruction.mSpaceBefore != 0 || instruction.mpStartBlock != 0) { rRecipe.push_back(instruction); } // Start a new instruction, with the gap ready RESET_INSTRUCTION instruction.mSpaceBefore = i->first - loc; // Move location forward to match loc += instruction.mSpaceBefore; #ifndef BOX_RELEASE_BUILD debug_NewBytesFound += instruction.mSpaceBefore; #endif } // First, does the current instruction need pushing back, because this block is not // sequential to the last one? if(instruction.mpStartBlock != 0 && (pIndex + i->second) != (instruction.mpStartBlock + instruction.mBlocks)) { rRecipe.push_back(instruction); RESET_INSTRUCTION } // Add in this block if(instruction.mpStartBlock == 0) { // This block starts a new instruction instruction.mpStartBlock = pIndex + i->second; instruction.mBlocks = 1; } else { // It continues the previous section of blocks instruction.mBlocks += 1; } #ifndef BOX_RELEASE_BUILD debug_OldBlocksUsed++; #endif // Move location forward loc += pIndex[i->second].mSize; } // Push the last instruction generated rRecipe.push_back(instruction); // Is there any space left at the end which needs sending? if(loc != SizeOfInputFile) { RESET_INSTRUCTION instruction.mSpaceBefore = SizeOfInputFile - loc; #ifndef BOX_RELEASE_BUILD debug_NewBytesFound += instruction.mSpaceBefore; #endif rRecipe.push_back(instruction); } // dump out the recipe #ifndef BOX_RELEASE_BUILD BOX_TRACE("Diff: " << debug_NewBytesFound << " new bytes found, " << debug_OldBlocksUsed << " old blocks used"); if(BackupStoreFile::TraceDetailsOfDiffProcess) { BOX_TRACE("Diff: Recipe generated (size " << rRecipe.size()); BOX_TRACE("======== ========= ========"); BOX_TRACE("Space b4 FirstBlk NumBlks"); { for(unsigned int e = 0; e < rRecipe.size(); ++e) { char b[64]; #ifdef WIN32 sprintf(b, "%8I64d", (int64_t)(rRecipe[e].mpStartBlock - pIndex)); #else sprintf(b, "%8lld", (int64_t)(rRecipe[e].mpStartBlock - pIndex)); #endif BOX_TRACE(std::setw(8) << rRecipe[e].mSpaceBefore << " " << ((rRecipe[e].mpStartBlock == 0)?" -":b) << " " << std::setw(8) << rRecipe[e].mBlocks); } } BOX_TRACE("======== ========= ========"); } #endif } boxbackup/lib/backupclient/BackupClientRestore.cpp0000664000175000017500000005354111445744272023162 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientRestore.cpp // Purpose: // Created: 23/11/03 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include "BackupClientRestore.h" #include "autogen_BackupProtocolClient.h" #include "CommonException.h" #include "BackupClientFileAttributes.h" #include "IOStream.h" #include "BackupStoreDirectory.h" #include "BackupStoreFile.h" #include "CollectInBufferStream.h" #include "FileStream.h" #include "Utils.h" #include "MemLeakFindOn.h" #define MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES (128*1024) class RestoreResumeInfo { public: // constructor RestoreResumeInfo() : mNextLevelID(0), mpNextLevel(0) { } // destructor ~RestoreResumeInfo() { delete mpNextLevel; mpNextLevel = 0; } // Get a next level object RestoreResumeInfo &AddLevel(int64_t ID, const std::string &rLocalName) { ASSERT(mpNextLevel == 0 && mNextLevelID == 0); mpNextLevel = new RestoreResumeInfo; mNextLevelID = ID; mNextLevelLocalName = rLocalName; return *mpNextLevel; } // Remove the next level info void RemoveLevel() { ASSERT(mpNextLevel != 0 && mNextLevelID != 0); delete mpNextLevel; mpNextLevel = 0; mNextLevelID = 0; mNextLevelLocalName.erase(); } void Save(const std::string &rFilename) const { // TODO: use proper buffered streams when they're done // Build info in memory buffer CollectInBufferStream write; // Save this level SaveLevel(write); // Store in file write.SetForReading(); FileStream file(rFilename.c_str(), O_WRONLY | O_CREAT); write.CopyStreamTo(file, IOStream::TimeOutInfinite, 8*1024 /* large buffer */); } void SaveLevel(IOStream &rWrite) const { // Write the restored objects int64_t numObjects = mRestoredObjects.size(); rWrite.Write(&numObjects, sizeof(numObjects)); for(std::set::const_iterator i(mRestoredObjects.begin()); i != mRestoredObjects.end(); ++i) { int64_t id = *i; rWrite.Write(&id, sizeof(id)); } // Next level? if(mpNextLevel != 0) { // ID rWrite.Write(&mNextLevelID, sizeof(mNextLevelID)); // Name string int32_t nsize = mNextLevelLocalName.size(); rWrite.Write(&nsize, sizeof(nsize)); rWrite.Write(mNextLevelLocalName.c_str(), nsize); // And then the level itself mpNextLevel->SaveLevel(rWrite); } else { // Just write a zero int64_t zero = 0; rWrite.Write(&zero, sizeof(zero)); } } // Not written to be efficient -- shouldn't be called very often. bool Load(const std::string &rFilename) { // Delete and reset if necessary if(mpNextLevel != 0) { RemoveLevel(); } // Open file FileStream file(rFilename.c_str()); // Load this level return LoadLevel(file); } #define CHECKED_READ(x, s) if(!rRead.ReadFullBuffer(x, s, 0)) {return false;} bool LoadLevel(IOStream &rRead) { // Load the restored objects list mRestoredObjects.clear(); int64_t numObjects = 0; CHECKED_READ(&numObjects, sizeof(numObjects)); for(int64_t o = 0; o < numObjects; ++o) { int64_t id; CHECKED_READ(&id, sizeof(id)); mRestoredObjects.insert(id); } // ID of next level? int64_t nextID = 0; CHECKED_READ(&nextID, sizeof(nextID)); if(nextID != 0) { // Load the next level! std::string name; int32_t nsize = 0; CHECKED_READ(&nsize, sizeof(nsize)); char n[PATH_MAX]; if(nsize > PATH_MAX) return false; CHECKED_READ(n, nsize); name.assign(n, nsize); // Create a new level mpNextLevel = new RestoreResumeInfo; mNextLevelID = nextID; mNextLevelLocalName = name; // And ask it to read itself in if(!mpNextLevel->LoadLevel(rRead)) { return false; } } return true; } // List of objects at this level which have been done already std::set mRestoredObjects; // Next level ID int64_t mNextLevelID; // Pointer to next level RestoreResumeInfo *mpNextLevel; // Local filename of next level std::string mNextLevelLocalName; }; // parameters structure typedef struct { bool PrintDots; bool RestoreDeleted; bool ContinueAfterErrors; bool ContinuedAfterError; std::string mRestoreResumeInfoFilename; RestoreResumeInfo mResumeInfo; } RestoreParams; // -------------------------------------------------------------------------- // // Function // Name: BackupClientRestoreDir(BackupProtocolClient &, // int64_t, const char *, bool) // Purpose: Restore a directory // Created: 23/11/03 // // -------------------------------------------------------------------------- static int BackupClientRestoreDir(BackupProtocolClient &rConnection, int64_t DirectoryID, const std::string &rRemoteDirectoryName, const std::string &rLocalDirectoryName, RestoreParams &Params, RestoreResumeInfo &rLevel) { // If we're resuming... check that we haven't got a next level to // look at if(rLevel.mpNextLevel != 0) { // Recurse immediately std::string localDirname(rLocalDirectoryName + DIRECTORY_SEPARATOR_ASCHAR + rLevel.mNextLevelLocalName); BackupClientRestoreDir(rConnection, rLevel.mNextLevelID, rRemoteDirectoryName + '/' + rLevel.mNextLevelLocalName, localDirname, Params, *rLevel.mpNextLevel); // Add it to the list of done itmes rLevel.mRestoredObjects.insert(rLevel.mNextLevelID); // Remove the level for the recursed directory rLevel.RemoveLevel(); } // Create the local directory, if not already done. // Path and owner set later, just use restrictive owner mode. int exists; try { exists = ObjectExists(rLocalDirectoryName.c_str()); } catch (BoxException &e) { BOX_ERROR("Failed to check existence for " << rLocalDirectoryName << ": " << e.what()); return Restore_UnknownError; } catch(std::exception &e) { BOX_ERROR("Failed to check existence for " << rLocalDirectoryName << ": " << e.what()); return Restore_UnknownError; } catch(...) { BOX_ERROR("Failed to check existence for " << rLocalDirectoryName << ": unknown error"); return Restore_UnknownError; } switch(exists) { case ObjectExists_Dir: // Do nothing break; case ObjectExists_File: { // File exists with this name, which is fun. // Get rid of it. BOX_WARNING("File present with name '" << rLocalDirectoryName << "', removing " "out of the way of restored directory. " "Use specific restore with ID to " "restore this object."); if(::unlink(rLocalDirectoryName.c_str()) != 0) { BOX_LOG_SYS_ERROR("Failed to delete " "file '" << rLocalDirectoryName << "'"); return Restore_UnknownError; } BOX_TRACE("In restore, directory name " "collision with file '" << rLocalDirectoryName << "'"); } break; case ObjectExists_NoObject: // we'll create it in a second, after checking // whether the parent directory exists break; default: ASSERT(false); break; } std::string parentDirectoryName(rLocalDirectoryName); if(parentDirectoryName[parentDirectoryName.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR) { parentDirectoryName.resize(parentDirectoryName.size() - 1); } size_t lastSlash = parentDirectoryName.rfind(DIRECTORY_SEPARATOR_ASCHAR); if(lastSlash == std::string::npos) { // might be a forward slash separator, // especially in the unit tests! lastSlash = parentDirectoryName.rfind('/'); } if(lastSlash != std::string::npos) { // the target directory is a deep path, remove the last // directory name and check that the resulting parent // exists, otherwise the restore should fail. parentDirectoryName.resize(lastSlash); #ifdef WIN32 // if the path is a drive letter, then we need to // add a a backslash to query the root directory. if (lastSlash == 2 && parentDirectoryName[1] == ':') { parentDirectoryName += '\\'; } else if (lastSlash == 0) { parentDirectoryName += '\\'; } #endif int parentExists; try { parentExists = ObjectExists(parentDirectoryName.c_str()); } catch (BoxException &e) { BOX_ERROR("Failed to check existence for " << parentDirectoryName << ": " << e.what()); return Restore_UnknownError; } catch(std::exception &e) { BOX_ERROR("Failed to check existence for " << parentDirectoryName << ": " << e.what()); return Restore_UnknownError; } catch(...) { BOX_ERROR("Failed to check existence for " << parentDirectoryName << ": unknown error"); return Restore_UnknownError; } switch(parentExists) { case ObjectExists_Dir: // this is fine, do nothing break; case ObjectExists_File: BOX_ERROR("Failed to restore: '" << parentDirectoryName << "' " "is a file, but should be a " "directory."); return Restore_TargetPathNotFound; case ObjectExists_NoObject: BOX_ERROR("Failed to restore: parent '" << parentDirectoryName << "' of target " "directory does not exist."); return Restore_TargetPathNotFound; default: BOX_ERROR("Failed to restore: unknown " "result from ObjectExists('" << parentDirectoryName << "')"); return Restore_UnknownError; } } if((exists == ObjectExists_NoObject || exists == ObjectExists_File) && ::mkdir(rLocalDirectoryName.c_str(), S_IRWXU) != 0) { BOX_LOG_SYS_ERROR("Failed to create directory '" << rLocalDirectoryName << "'"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } // Save the restore info, in case it's needed later try { Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); } catch(std::exception &e) { BOX_ERROR("Failed to save resume info file '" << Params.mRestoreResumeInfoFilename << "': " << e.what()); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } catch(...) { BOX_ERROR("Failed to save resume info file '" << Params.mRestoreResumeInfoFilename << "': unknown error"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } // Fetch the directory listing from the server -- getting a // list of files which is appropriate to the restore type rConnection.QueryListDirectory( DirectoryID, Params.RestoreDeleted?(BackupProtocolClientListDirectory::Flags_Deleted):(BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING), BackupProtocolClientListDirectory::Flags_OldVersion | (Params.RestoreDeleted?(0):(BackupProtocolClientListDirectory::Flags_Deleted)), true /* want attributes */); // Retrieve the directory from the stream following BackupStoreDirectory dir; std::auto_ptr dirstream(rConnection.ReceiveStream()); dir.ReadFromStream(*dirstream, rConnection.GetTimeout()); // Apply attributes to the directory const StreamableMemBlock &dirAttrBlock(dir.GetAttributes()); BackupClientFileAttributes dirAttr(dirAttrBlock); try { dirAttr.WriteAttributes(rLocalDirectoryName.c_str(), true); } catch(std::exception &e) { BOX_ERROR("Failed to restore attributes for '" << rLocalDirectoryName << "': " << e.what()); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } catch(...) { BOX_ERROR("Failed to restore attributes for '" << rLocalDirectoryName << "': unknown error"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } int64_t bytesWrittenSinceLastRestoreInfoSave = 0; // Process files { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0) { // Check ID hasn't already been done if(rLevel.mRestoredObjects.find(en->GetObjectID()) == rLevel.mRestoredObjects.end()) { // Local name BackupStoreFilenameClear nm(en->GetName()); std::string localFilename(rLocalDirectoryName + DIRECTORY_SEPARATOR_ASCHAR + nm.GetClearFilename()); // Unlink anything which already exists: // For resuming restores, we can't overwrite // files already there. if(ObjectExists(localFilename) != ObjectExists_NoObject && ::unlink(localFilename.c_str()) != 0) { BOX_LOG_SYS_ERROR("Failed to delete " "file '" << localFilename << "'"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } BOX_TRACE("Restoring file: " << rRemoteDirectoryName + '/' + nm.GetClearFilename() << " (" << en->GetSizeInBlocks() << " blocks)"); // Request it from the store rConnection.QueryGetFile(DirectoryID, en->GetObjectID()); // Stream containing encoded file std::auto_ptr objectStream( rConnection.ReceiveStream()); // Decode the file -- need to do different // things depending on whether the directory // entry has additional attributes try { if(en->HasAttributes()) { // Use these attributes const StreamableMemBlock &storeAttr(en->GetAttributes()); BackupClientFileAttributes attr(storeAttr); BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout(), &attr); } else { // Use attributes stored in file BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout()); } } catch(std::exception &e) { BOX_ERROR("Failed to restore file '" << localFilename << "': " << e.what()); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } catch(...) { BOX_ERROR("Failed to restore file '" << localFilename << "': unknown error"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } // Progress display? if(Params.PrintDots) { printf("."); fflush(stdout); } // Add it to the list of done itmes rLevel.mRestoredObjects.insert(en->GetObjectID()); // Save restore info? int64_t fileSize; bool exists = false; try { exists = FileExists( localFilename.c_str(), &fileSize, true /* treat links as not existing */); } catch(std::exception &e) { BOX_ERROR("Failed to determine " "whether file exists: '" << localFilename << "': " << e.what()); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } catch(...) { BOX_ERROR("Failed to determine " "whether file exists: '" << localFilename << "': " "unknown error"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } if(exists) { // File exists... bytesWrittenSinceLastRestoreInfoSave += fileSize; if(bytesWrittenSinceLastRestoreInfoSave > MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES) { // Save the restore info, in // case it's needed later try { Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); } catch(std::exception &e) { BOX_ERROR("Failed to save resume info file '" << Params.mRestoreResumeInfoFilename << "': " << e.what()); return Restore_UnknownError; } catch(...) { BOX_ERROR("Failed to save resume info file '" << Params.mRestoreResumeInfoFilename << "': unknown error"); return Restore_UnknownError; } bytesWrittenSinceLastRestoreInfoSave = 0; } } } } } // Make sure the restore info has been saved if(bytesWrittenSinceLastRestoreInfoSave != 0) { // Save the restore info, in case it's needed later try { Params.mResumeInfo.Save( Params.mRestoreResumeInfoFilename); } catch(std::exception &e) { BOX_ERROR("Failed to save resume info file '" << Params.mRestoreResumeInfoFilename << "': " << e.what()); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } catch(...) { BOX_ERROR("Failed to save resume info file '" << Params.mRestoreResumeInfoFilename << "': unknown error"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } bytesWrittenSinceLastRestoreInfoSave = 0; } // Recurse to directories { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir)) != 0) { // Check ID hasn't already been done if(rLevel.mRestoredObjects.find(en->GetObjectID()) == rLevel.mRestoredObjects.end()) { // Local name BackupStoreFilenameClear nm(en->GetName()); std::string localDirname(rLocalDirectoryName + DIRECTORY_SEPARATOR_ASCHAR + nm.GetClearFilename()); // Add the level for the next entry RestoreResumeInfo &rnextLevel( rLevel.AddLevel(en->GetObjectID(), nm.GetClearFilename())); // Recurse BOX_TRACE("Entering directory: " << rRemoteDirectoryName + '/' + nm.GetClearFilename()); int result = BackupClientRestoreDir( rConnection, en->GetObjectID(), rRemoteDirectoryName + '/' + nm.GetClearFilename(), localDirname, Params, rnextLevel); if (result != Restore_Complete) { return result; } // Remove the level for the above call rLevel.RemoveLevel(); // Add it to the list of done itmes rLevel.mRestoredObjects.insert(en->GetObjectID()); } } } // now remove the user writable flag, if we added it earlier try { dirAttr.WriteAttributes(rLocalDirectoryName.c_str(), false); } catch(std::exception &e) { BOX_ERROR("Failed to restore attributes for '" << rLocalDirectoryName << "': " << e.what()); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } catch(...) { BOX_ERROR("Failed to restore attributes for '" << rLocalDirectoryName << "': unknown error"); if (Params.ContinueAfterErrors) { Params.ContinuedAfterError = true; } else { return Restore_UnknownError; } } return Restore_Complete; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientRestore(BackupProtocolClient &, int64_t, // const char *, bool, bool, bool, bool, bool) // Purpose: Restore a directory on the server to a local // directory on the disc. The local directory must not // already exist. // // If a restore is aborted for any reason, then it may // be resumed if Resume == true. If Resume == false // and resumption is possible, then // Restore_ResumePossible is returned. // // Set RestoreDeleted to restore a deleted directory. // This may not give the directory structure when it // was deleted, because files may have been deleted // within it before it was deleted. // // Returns Restore_TargetExists if the target // directory exists, but there is no restore possible. // (Won't attempt to overwrite things.) // // Returns Restore_Complete on success. (Exceptions // on error, unless ContinueAfterError is true and // the error is recoverable, in which case it returns // Restore_CompleteWithErrors) // Created: 23/11/03 // // -------------------------------------------------------------------------- int BackupClientRestore(BackupProtocolClient &rConnection, int64_t DirectoryID, const char *RemoteDirectoryName, const char *LocalDirectoryName, bool PrintDots, bool RestoreDeleted, bool UndeleteAfterRestoreDeleted, bool Resume, bool ContinueAfterErrors) { // Parameter block RestoreParams params; params.PrintDots = PrintDots; params.RestoreDeleted = RestoreDeleted; params.ContinueAfterErrors = ContinueAfterErrors; params.ContinuedAfterError = false; params.mRestoreResumeInfoFilename = LocalDirectoryName; params.mRestoreResumeInfoFilename += ".boxbackupresume"; // Target exists? int targetExistance = ObjectExists(LocalDirectoryName); // Does any resumption information exist? bool doingResume = false; if(FileExists(params.mRestoreResumeInfoFilename.c_str()) && targetExistance == ObjectExists_Dir) { if(!Resume) { // Caller didn't specify that resume should be done, // so refuse to do it but say why. return Restore_ResumePossible; } // Attempt to load the resume info file if(!params.mResumeInfo.Load(params.mRestoreResumeInfoFilename)) { // failed -- bad file, so things have gone a bit wrong return Restore_TargetExists; } // Flag as doing resume so next check isn't actually performed doingResume = true; } // Does the directory already exist? if(targetExistance != ObjectExists_NoObject && !doingResume) { // Don't do anything in this case! return Restore_TargetExists; } // Restore the directory int result = BackupClientRestoreDir(rConnection, DirectoryID, RemoteDirectoryName, LocalDirectoryName, params, params.mResumeInfo); if (result != Restore_Complete) { return result; } // Undelete the directory on the server? if(RestoreDeleted && UndeleteAfterRestoreDeleted) { // Send the command rConnection.QueryUndeleteDirectory(DirectoryID); } // Finish progress display? if(PrintDots) { printf("\n"); fflush(stdout); } // Delete the resume information file ::unlink(params.mRestoreResumeInfoFilename.c_str()); return params.ContinuedAfterError ? Restore_CompleteWithErrors : Restore_Complete; } boxbackup/lib/backupclient/BackupStoreFilename.h0000664000175000017500000000636611163676334022605 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFilename.h // Purpose: Filename for the backup store // Created: 2003/08/26 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREFILENAME__H #define BACKUPSTOREFILENAME__H #include class Protocol; class IOStream; // #define BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE // don't define this -- the problem of memory usage still appears without this. // It's just that this class really showed up the problem. Instead, malloc allocation // is globally defined in BoxPlatform.h, for troublesome libraries. #ifdef BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE // Use a malloc_allocated string, because the STL default allocators really screw up with // memory allocation, particularly with this class. // Makes a few things a bit messy and inefficient with conversions. // Given up using this, and use global malloc allocation instead, but thought it // worth leaving this code in just in case it's useful for the future. typedef std::basic_string, std::malloc_alloc> BackupStoreFilename_base; // If this is changed, change GetClearFilename() back to returning a reference. #else typedef std::string BackupStoreFilename_base; #endif // -------------------------------------------------------------------------- // // Class // Name: BackupStoreFilename // Purpose: Filename for the backup store // Created: 2003/08/26 // // -------------------------------------------------------------------------- class BackupStoreFilename /* : public BackupStoreFilename_base */ { private: std::string mEncryptedName; public: BackupStoreFilename(); BackupStoreFilename(const BackupStoreFilename &rToCopy); virtual ~BackupStoreFilename(); bool CheckValid(bool ExceptionIfInvalid = true) const; void ReadFromProtocol(Protocol &rProtocol); void WriteToProtocol(Protocol &rProtocol) const; void ReadFromStream(IOStream &rStream, int Timeout); void WriteToStream(IOStream &rStream) const; void SetAsClearFilename(const char *Clear); // Check that it's encrypted bool IsEncrypted() const; // These enumerated types belong in the base class so // the CheckValid() function can make sure that the encoding // is a valid encoding enum { Encoding_Min = 1, Encoding_Clear = 1, Encoding_Blowfish = 2, Encoding_Max = 2 }; const std::string& GetEncodedFilename() const { return mEncryptedName; } bool operator==(const BackupStoreFilename& rOther) const { return mEncryptedName == rOther.mEncryptedName; } bool operator!=(const BackupStoreFilename& rOther) const { return mEncryptedName != rOther.mEncryptedName; } protected: virtual void EncodedFilenameChanged(); void SetEncodedFilename(const std::string &rEncoded) { mEncryptedName = rEncoded; } }; // On the wire utilities for class and derived class #define BACKUPSTOREFILENAME_GET_SIZE(hdr) (( ((uint8_t)((hdr)[0])) | ( ((uint8_t)((hdr)[1])) << 8)) >> 2) #define BACKUPSTOREFILENAME_GET_ENCODING(hdr) (((hdr)[0]) & 0x3) #define BACKUPSTOREFILENAME_MAKE_HDR(hdr, size, encoding) {uint16_t h = (((uint16_t)size) << 2) | (encoding); ((hdr)[0]) = h & 0xff; ((hdr)[1]) = h >> 8;} #endif // BACKUPSTOREFILENAME__H boxbackup/lib/backupclient/BackupStoreFileRevDiff.cpp0000664000175000017500000001573210347400657023536 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileRevDiff.cpp // Purpose: Reverse a patch, to build a new patch from new to old files // Created: 12/7/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "BackupStoreFile.h" #include "BackupStoreFileWire.h" #include "BackupStoreObjectMagic.h" #include "BackupStoreException.h" #include "BackupStoreConstants.h" #include "BackupStoreFilename.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::ReverseDiffFile(IOStream &, IOStream &, IOStream &, IOStream &, int64_t) // Purpose: Reverse a patch, to build a new patch from new to old files. Takes // two independent copies to the From file, for efficiency. // Created: 12/7/04 // // -------------------------------------------------------------------------- void BackupStoreFile::ReverseDiffFile(IOStream &rDiff, IOStream &rFrom, IOStream &rFrom2, IOStream &rOut, int64_t ObjectIDOfFrom, bool *pIsCompletelyDifferent) { // Read and copy the header from the from file to the out file -- beginnings of the patch file_StreamFormat hdr; if(!rFrom.ReadFullBuffer(&hdr, sizeof(hdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Copy rOut.Write(&hdr, sizeof(hdr)); // Copy over filename and attributes // BLOCK { BackupStoreFilename filename; filename.ReadFromStream(rFrom, IOStream::TimeOutInfinite); filename.WriteToStream(rOut); StreamableMemBlock attr; attr.ReadFromStream(rFrom, IOStream::TimeOutInfinite); attr.WriteToStream(rOut); } // Build an index of common blocks. // For each block in the from file, we want to know it's index in the // diff file. Allocate memory for this information. int64_t fromNumBlocks = box_ntoh64(hdr.mNumBlocks); int64_t *pfromIndexInfo = (int64_t*)::malloc(fromNumBlocks * sizeof(int64_t)); if(pfromIndexInfo == 0) { throw std::bad_alloc(); } // Buffer data void *buffer = 0; int bufferSize = 0; // flag bool isCompletelyDifferent = true; try { // Initialise the index to be all 0, ie not filled in yet for(int64_t i = 0; i < fromNumBlocks; ++i) { pfromIndexInfo[i] = 0; } // Within the from file, skip to the index MoveStreamPositionToBlockIndex(rDiff); // Read in header of index file_BlockIndexHeader diffIdxHdr; if(!rDiff.ReadFullBuffer(&diffIdxHdr, sizeof(diffIdxHdr), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } if(ntohl(diffIdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // And then read in each entry int64_t diffNumBlocks = box_ntoh64(diffIdxHdr.mNumBlocks); for(int64_t b = 0; b < diffNumBlocks; ++b) { file_BlockIndexEntry e; if(!rDiff.ReadFullBuffer(&e, sizeof(e), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Where's the block? int64_t blockEn = box_ntoh64(e.mEncodedSize); if(blockEn > 0) { // Block is in the delta file, is ignored for now -- not relevant to rebuilding the from file } else { // Block is in the original file, store which block it is in this file int64_t fromIndex = 0 - blockEn; if(fromIndex < 0 || fromIndex >= fromNumBlocks) { THROW_EXCEPTION(BackupStoreException, IncompatibleFromAndDiffFiles) } // Store information about where it is in the new file // NOTE: This is slight different to how it'll be stored in the final index. pfromIndexInfo[fromIndex] = -1 - b; } } // Open the index for the second copy of the from file MoveStreamPositionToBlockIndex(rFrom2); // Read in header of index file_BlockIndexHeader fromIdxHdr; if(!rFrom2.ReadFullBuffer(&fromIdxHdr, sizeof(fromIdxHdr), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } if(ntohl(fromIdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 || box_ntoh64(fromIdxHdr.mOtherFileID) != 0) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // So, we can now start building the data in the file int64_t filePosition = rFrom.GetPosition(); for(int64_t b = 0; b < fromNumBlocks; ++b) { // Read entry from from index file_BlockIndexEntry e; if(!rFrom2.ReadFullBuffer(&e, sizeof(e), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Get size int64_t blockSize = box_hton64(e.mEncodedSize); if(blockSize < 0) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Copy this block? if(pfromIndexInfo[b] == 0) { // Copy it, first move to file location rFrom.Seek(filePosition, IOStream::SeekType_Absolute); // Make sure there's memory available to copy this if(bufferSize < blockSize || buffer == 0) { // Free old block if(buffer != 0) { ::free(buffer); buffer = 0; bufferSize = 0; } // Allocate new block buffer = ::malloc(blockSize); if(buffer == 0) { throw std::bad_alloc(); } bufferSize = blockSize; } ASSERT(bufferSize >= blockSize); // Copy the block if(!rFrom.ReadFullBuffer(buffer, blockSize, 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } rOut.Write(buffer, blockSize); // Store the size pfromIndexInfo[b] = blockSize; } else { // Block isn't needed, so it's not completely different isCompletelyDifferent = false; } filePosition += blockSize; } // Then write the index, modified header first fromIdxHdr.mOtherFileID = isCompletelyDifferent?0:(box_hton64(ObjectIDOfFrom)); rOut.Write(&fromIdxHdr, sizeof(fromIdxHdr)); // Move to start of index entries rFrom.Seek(filePosition + sizeof(file_BlockIndexHeader), IOStream::SeekType_Absolute); // Then copy modified entries for(int64_t b = 0; b < fromNumBlocks; ++b) { // Read entry from from index file_BlockIndexEntry e; if(!rFrom.ReadFullBuffer(&e, sizeof(e), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Modify... int64_t s = pfromIndexInfo[b]; // Adjust to reflect real block index (remember 0 has a different meaning here) if(s < 0) ++s; // Insert e.mEncodedSize = box_hton64(s); // Write rOut.Write(&e, sizeof(e)); } } catch(...) { ::free(pfromIndexInfo); if(buffer != 0) { ::free(buffer); } throw; } // Free memory used (oh for finally {} blocks) ::free(pfromIndexInfo); if(buffer != 0) { ::free(buffer); } // return completely different flag if(pIsCompletelyDifferent != 0) { *pIsCompletelyDifferent = isCompletelyDifferent; } } boxbackup/lib/backupclient/BackupClientMakeExcludeList.h0000664000175000017500000000342710347400657024221 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientMakeExcludeList.h // Purpose: Makes exclude lists from bbbackupd config location entries // Created: 28/1/04 // // -------------------------------------------------------------------------- #ifndef BACKUPCLIENTMAKEEXCLUDELIST__H #define BACKUPCLIENTMAKEEXCLUDELIST__H class ExcludeList; class Configuration; ExcludeList *BackupClientMakeExcludeList(const Configuration &rConfig, const char *DefiniteName, const char *RegexName, const char *AlwaysIncludeDefiniteName = 0, const char *AlwaysIncludeRegexName = 0); // -------------------------------------------------------------------------- // // Function // Name: BackupClientMakeExcludeList_Files(const Configuration &) // Purpose: Create a exclude list from config file entries for files. May return 0. // Created: 28/1/04 // // -------------------------------------------------------------------------- inline ExcludeList *BackupClientMakeExcludeList_Files(const Configuration &rConfig) { return BackupClientMakeExcludeList(rConfig, "ExcludeFile", "ExcludeFilesRegex", "AlwaysIncludeFile", "AlwaysIncludeFilesRegex"); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientMakeExcludeList_Dirs(const Configuration &) // Purpose: Create a exclude list from config file entries for directories. May return 0. // Created: 28/1/04 // // -------------------------------------------------------------------------- inline ExcludeList *BackupClientMakeExcludeList_Dirs(const Configuration &rConfig) { return BackupClientMakeExcludeList(rConfig, "ExcludeDir", "ExcludeDirsRegex", "AlwaysIncludeDir", "AlwaysIncludeDirsRegex"); } #endif // BACKUPCLIENTMAKEEXCLUDELIST__H boxbackup/lib/backupclient/BackupStoreFilenameClear.cpp0000664000175000017500000002460711163676334024105 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFilenameClear.cpp // Purpose: BackupStoreFilenames in the clear // Created: 2003/08/26 // // -------------------------------------------------------------------------- #include "Box.h" #include "BackupStoreFilenameClear.h" #include "BackupStoreException.h" #include "CipherContext.h" #include "CipherBlowfish.h" #include "Guards.h" #include "Logging.h" #include "MemLeakFindOn.h" // Hide private variables from the rest of the world namespace { int sEncodeMethod = BackupStoreFilename::Encoding_Clear; CipherContext sBlowfishEncrypt; CipherContext sBlowfishDecrypt; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::BackupStoreFilenameClear() // Purpose: Default constructor, creates an invalid filename // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilenameClear::BackupStoreFilenameClear() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::BackupStoreFilenameClear(const std::string &) // Purpose: Creates a filename, encoding from the given string // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilenameClear::BackupStoreFilenameClear(const std::string &rToEncode) { SetClearFilename(rToEncode); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilenameClear &) // Purpose: Copy constructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilenameClear &rToCopy) : BackupStoreFilename(rToCopy), mClearFilename(rToCopy.mClearFilename) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilename &rToCopy) // Purpose: Copy from base class // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilename &rToCopy) : BackupStoreFilename(rToCopy) { // Will get a clear filename when it's required } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::~BackupStoreFilenameClear() // Purpose: Destructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilenameClear::~BackupStoreFilenameClear() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::GetClearFilename() // Purpose: Get the unencoded filename // Created: 2003/08/26 // // -------------------------------------------------------------------------- #ifdef BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE const std::string BackupStoreFilenameClear::GetClearFilename() const { MakeClearAvailable(); // When modifying, remember to change back to reference return if at all possible // -- returns an object rather than a reference to allow easy use with other code. return std::string(mClearFilename.c_str(), mClearFilename.size()); } #else const std::string &BackupStoreFilenameClear::GetClearFilename() const { MakeClearAvailable(); return mClearFilename; } #endif // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::SetClearFilename(const std::string &) // Purpose: Encode and make available the clear filename // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilenameClear::SetClearFilename(const std::string &rToEncode) { // Only allow Blowfish encodings if(sEncodeMethod != Encoding_Blowfish) { THROW_EXCEPTION(BackupStoreException, FilenameEncryptionNotSetup) } // Make an encoded string with blowfish encryption EncryptClear(rToEncode, sBlowfishEncrypt, Encoding_Blowfish); // Store the clear filename mClearFilename.assign(rToEncode.c_str(), rToEncode.size()); // Make sure we did the right thing if(!CheckValid(false)) { THROW_EXCEPTION(BackupStoreException, Internal) } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::MakeClearAvailable() // Purpose: Private. Make sure the clear filename is available // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilenameClear::MakeClearAvailable() const { if(!mClearFilename.empty()) return; // nothing to do // Check valid CheckValid(); // Decode the header int size = BACKUPSTOREFILENAME_GET_SIZE(GetEncodedFilename()); int encoding = BACKUPSTOREFILENAME_GET_ENCODING(GetEncodedFilename()); // Decode based on encoding given in the header switch(encoding) { case Encoding_Clear: BOX_TRACE("**** BackupStoreFilename encoded with " "Clear encoding ****"); mClearFilename.assign(GetEncodedFilename().c_str() + 2, size - 2); break; case Encoding_Blowfish: DecryptEncoded(sBlowfishDecrypt); break; default: THROW_EXCEPTION(BackupStoreException, UnknownFilenameEncoding) break; } } // Buffer for encoding and decoding -- do this all in one single buffer to // avoid lots of string allocation, which stuffs up memory usage. // These static memory vars are, of course, not thread safe, but we don't use threads. static int sEncDecBufferSize = 0; static MemoryBlockGuard *spEncDecBuffer = 0; static void EnsureEncDecBufferSize(int BufSize) { if(spEncDecBuffer == 0) { #ifndef WIN32 BOX_TRACE("Allocating filename encoding/decoding buffer " "with size " << BufSize); #endif spEncDecBuffer = new MemoryBlockGuard(BufSize); MEMLEAKFINDER_NOT_A_LEAK(spEncDecBuffer); MEMLEAKFINDER_NOT_A_LEAK(*spEncDecBuffer); sEncDecBufferSize = BufSize; } else { if(sEncDecBufferSize < BufSize) { BOX_TRACE("Reallocating filename encoding/decoding " "buffer from " << sEncDecBufferSize << " to " << BufSize); spEncDecBuffer->Resize(BufSize); sEncDecBufferSize = BufSize; MEMLEAKFINDER_NOT_A_LEAK(*spEncDecBuffer); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::EncryptClear(const std::string &, CipherContext &, int) // Purpose: Private. Assigns the encoded filename string, encrypting. // Created: 1/12/03 // // -------------------------------------------------------------------------- void BackupStoreFilenameClear::EncryptClear(const std::string &rToEncode, CipherContext &rCipherContext, int StoreAsEncoding) { // Work out max size int maxOutSize = rCipherContext.MaxOutSizeForInBufferSize(rToEncode.size()) + 4; // Make sure encode/decode buffer has enough space EnsureEncDecBufferSize(maxOutSize); // Pointer to buffer uint8_t *buffer = *spEncDecBuffer; // Encode -- do entire block in one go int encSize = rCipherContext.TransformBlock(buffer + 2, sEncDecBufferSize - 2, rToEncode.c_str(), rToEncode.size()); // and add in header size encSize += 2; // Adjust header BACKUPSTOREFILENAME_MAKE_HDR(buffer, encSize, StoreAsEncoding); // Store the encoded string SetEncodedFilename(std::string((char*)buffer, encSize)); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::DecryptEncoded(CipherContext &) // Purpose: Decrypt the encoded filename using the cipher context // Created: 1/12/03 // // -------------------------------------------------------------------------- void BackupStoreFilenameClear::DecryptEncoded(CipherContext &rCipherContext) const { const std::string& rEncoded = GetEncodedFilename(); // Work out max size int maxOutSize = rCipherContext.MaxOutSizeForInBufferSize(rEncoded.size()) + 4; // Make sure encode/decode buffer has enough space EnsureEncDecBufferSize(maxOutSize); // Pointer to buffer uint8_t *buffer = *spEncDecBuffer; // Decrypt const char *str = rEncoded.c_str() + 2; int sizeOut = rCipherContext.TransformBlock(buffer, sEncDecBufferSize, str, rEncoded.size() - 2); // Assign to this mClearFilename.assign((char*)buffer, sizeOut); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::EncodedFilenameChanged() // Purpose: The encoded filename stored has changed // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilenameClear::EncodedFilenameChanged() { BackupStoreFilename::EncodedFilenameChanged(); // Delete stored filename in clear mClearFilename.erase(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::SetBlowfishKey(const void *, int) // Purpose: Set the key used for Blowfish encryption of filenames // Created: 1/12/03 // // -------------------------------------------------------------------------- void BackupStoreFilenameClear::SetBlowfishKey(const void *pKey, int KeyLength, const void *pIV, int IVLength) { // Initialisation vector not used. Can't use a different vector for each filename as // that would stop comparisions on the server working. sBlowfishEncrypt.Reset(); sBlowfishEncrypt.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength)); ASSERT(sBlowfishEncrypt.GetIVLength() == IVLength); sBlowfishEncrypt.SetIV(pIV); sBlowfishDecrypt.Reset(); sBlowfishDecrypt.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength)); ASSERT(sBlowfishDecrypt.GetIVLength() == IVLength); sBlowfishDecrypt.SetIV(pIV); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilenameClear::SetEncodingMethod(int) // Purpose: Set the encoding method used for filenames // Created: 1/12/03 // // -------------------------------------------------------------------------- void BackupStoreFilenameClear::SetEncodingMethod(int Method) { sEncodeMethod = Method; } boxbackup/lib/backupclient/BackupStoreFile.cpp0000664000175000017500000013261011445744272022267 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFile.cpp // Purpose: Utils for manipulating files // Created: 2003/08/28 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE #include #endif #include "BackupStoreFile.h" #include "BackupStoreFileWire.h" #include "BackupStoreFileCryptVar.h" #include "BackupStoreFilename.h" #include "BackupStoreException.h" #include "IOStream.h" #include "Guards.h" #include "FileModificationTime.h" #include "FileStream.h" #include "BackupClientFileAttributes.h" #include "BackupStoreObjectMagic.h" #include "Compress.h" #include "CipherContext.h" #include "CipherBlowfish.h" #include "CipherAES.h" #include "BackupStoreConstants.h" #include "CollectInBufferStream.h" #include "RollingChecksum.h" #include "MD5Digest.h" #include "ReadGatherStream.h" #include "Random.h" #include "BackupStoreFileEncodeStream.h" #include "Logging.h" #include "MemLeakFindOn.h" using namespace BackupStoreFileCryptVar; // How big a buffer to use for copying files #define COPY_BUFFER_SIZE (8*1024) // Statistics BackupStoreFileStats BackupStoreFile::msStats = {0,0,0}; #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE bool sWarnedAboutBackwardsCompatiblity = false; #endif // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::EncodeFile(IOStream &, IOStream &) // Purpose: Encode a file into something for storing on file server. // Requires a real filename so full info can be stored. // // Returns a stream. Most of the work is done by the stream // when data is actually requested -- the file will be held // open until the stream is deleted or the file finished. // Created: 2003/08/28 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreFile::EncodeFile(const char *Filename, int64_t ContainerID, const BackupStoreFilename &rStoreFilename, int64_t *pModificationTime, ReadLoggingStream::Logger* pLogger, RunStatusProvider* pRunStatusProvider) { // Create the stream std::auto_ptr stream(new BackupStoreFileEncodeStream); // Do the initial setup ((BackupStoreFileEncodeStream*)stream.get())->Setup(Filename, 0 /* no recipe, just encode */, ContainerID, rStoreFilename, pModificationTime, pLogger, pRunStatusProvider); // Return the stream for the caller return stream; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::VerifyEncodedFileFormat(IOStream &) // Purpose: Verify that an encoded file meets the format requirements. // Doesn't verify that the data is intact and can be decoded. // Optionally returns the ID of the file which it is diffed from, // and the (original) container ID. // Created: 2003/08/28 // // -------------------------------------------------------------------------- bool BackupStoreFile::VerifyEncodedFileFormat(IOStream &rFile, int64_t *pDiffFromObjectIDOut, int64_t *pContainerIDOut) { // Get the size of the file int64_t fileSize = rFile.BytesLeftToRead(); if(fileSize == IOStream::SizeOfStreamUnknown) { THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures) } // Get the header... file_StreamFormat hdr; if(!rFile.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */)) { // Couldn't read header return false; } // Check magic number if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1 #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0 #endif ) { return false; } // Get a filename, see if it loads OK try { BackupStoreFilename fn; fn.ReadFromStream(rFile, IOStream::TimeOutInfinite); } catch(...) { // an error occured while reading it, so that's not good return false; } // Skip the attributes -- because they're encrypted, the server can't tell whether they're OK or not try { int32_t size_s; if(!rFile.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) } int size = ntohl(size_s); // Skip forward the size rFile.Seek(size, IOStream::SeekType_Relative); } catch(...) { // an error occured while reading it, so that's not good return false; } // Get current position in file -- the end of the header int64_t headerEnd = rFile.GetPosition(); // Get number of blocks int64_t numBlocks = box_ntoh64(hdr.mNumBlocks); // Calculate where the block index will be, check it's reasonable int64_t blockIndexLoc = fileSize - ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)); if(blockIndexLoc < headerEnd) { // Not enough space left for the block index, let alone the blocks themselves return false; } // Load the block index header rFile.Seek(blockIndexLoc, IOStream::SeekType_Absolute); file_BlockIndexHeader blkhdr; if(!rFile.ReadFullBuffer(&blkhdr, sizeof(blkhdr), 0 /* not interested in bytes read if this fails */)) { // Couldn't read block index header -- assume bad file return false; } // Check header if((ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0 #endif ) || (int64_t)box_ntoh64(blkhdr.mNumBlocks) != numBlocks) { // Bad header -- either magic value or number of blocks is wrong return false; } // Flag for recording whether a block is referenced from another file bool blockFromOtherFileReferenced = false; // Read the index, checking that the length values all make sense int64_t currentBlockStart = headerEnd; for(int64_t b = 0; b < numBlocks; ++b) { // Read block entry file_BlockIndexEntry blk; if(!rFile.ReadFullBuffer(&blk, sizeof(blk), 0 /* not interested in bytes read if this fails */)) { // Couldn't read block index entry -- assume bad file return false; } // Check size and location int64_t blkSize = box_ntoh64(blk.mEncodedSize); if(blkSize <= 0) { // Mark that this file references another file blockFromOtherFileReferenced = true; } else { // This block is actually in this file if((currentBlockStart + blkSize) > blockIndexLoc) { // Encoded size makes the block run over the index return false; } // Move the current block start ot the end of this block currentBlockStart += blkSize; } } // Check that there's no empty space if(currentBlockStart != blockIndexLoc) { return false; } // Check that if another block is references, then the ID is there, and if one isn't there is no ID. int64_t otherID = box_ntoh64(blkhdr.mOtherFileID); if((otherID != 0 && blockFromOtherFileReferenced == false) || (otherID == 0 && blockFromOtherFileReferenced == true)) { // Doesn't look good! return false; } // Does the caller want the other ID? if(pDiffFromObjectIDOut) { *pDiffFromObjectIDOut = otherID; } // Does the caller want the container ID? if(pContainerIDOut) { *pContainerIDOut = box_ntoh64(hdr.mContainerID); } // Passes all tests return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodeFile(IOStream &, const char *) // Purpose: Decode a file. Will set file attributes. File must not exist. // Created: 2003/08/28 // // -------------------------------------------------------------------------- void BackupStoreFile::DecodeFile(IOStream &rEncodedFile, const char *DecodedFilename, int Timeout, const BackupClientFileAttributes *pAlterativeAttr) { // Does file exist? EMU_STRUCT_STAT st; if(EMU_STAT(DecodedFilename, &st) == 0) { THROW_EXCEPTION(BackupStoreException, OutputFileAlreadyExists) } // Try, delete output file if error try { // Make a stream for outputting this file FileStream out(DecodedFilename, O_WRONLY | O_CREAT | O_EXCL); // Get the decoding stream std::auto_ptr stream(DecodeFileStream(rEncodedFile, Timeout, pAlterativeAttr)); // Is it a symlink? if(!stream->IsSymLink()) { // Copy it out to the file stream->CopyStreamTo(out); } out.Close(); // The stream might have uncertain size, in which case // we need to drain it to get the // Protocol::ProtocolStreamHeader_EndOfStream byte // out of our connection stream. char buffer[1]; int drained = rEncodedFile.Read(buffer, 1); // The Read will return 0 if we are actually at the end // of the stream, but some tests decode files directly, // in which case we are actually positioned at the start // of the block index. I hope that reading an extra byte // doesn't hurt! // ASSERT(drained == 0); // Write the attributes try { stream->GetAttributes().WriteAttributes(DecodedFilename); } catch (std::exception& e) { BOX_WARNING("Failed to restore attributes on " << DecodedFilename << ": " << e.what()); } } catch(...) { ::unlink(DecodedFilename); throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodeFileStream(IOStream &, int, const BackupClientFileAttributes *) // Purpose: Return a stream which will decode the encrypted file data on the fly. // Accepts streams in block index first, or main header first, order. In the latter case, // the stream must be Seek()able. // // Before you use the returned stream, call IsSymLink() -- symlink streams won't allow // you to read any data to enforce correct logic. See BackupStoreFile::DecodeFile() implementation. // Created: 9/12/03 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreFile::DecodeFileStream(IOStream &rEncodedFile, int Timeout, const BackupClientFileAttributes *pAlterativeAttr) { // Create stream std::auto_ptr stream(new DecodedStream(rEncodedFile, Timeout)); // Get it ready stream->Setup(pAlterativeAttr); // Return to caller return stream; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::DecodedStream(IOStream &, int) // Purpose: Constructor // Created: 9/12/03 // // -------------------------------------------------------------------------- BackupStoreFile::DecodedStream::DecodedStream(IOStream &rEncodedFile, int Timeout) : mrEncodedFile(rEncodedFile), mTimeout(Timeout), mNumBlocks(0), mpBlockIndex(0), mpEncodedData(0), mpClearData(0), mClearDataSize(0), mCurrentBlock(-1), mCurrentBlockClearSize(0), mPositionInCurrentBlock(0), mEntryIVBase(42) // different to default value in the encoded stream! #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE , mIsOldVersion(false) #endif { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::~DecodedStream() // Purpose: Desctructor // Created: 9/12/03 // // -------------------------------------------------------------------------- BackupStoreFile::DecodedStream::~DecodedStream() { // Free any allocated memory if(mpBlockIndex) { ::free(mpBlockIndex); } if(mpEncodedData) { BackupStoreFile::CodingChunkFree(mpEncodedData); } if(mpClearData) { ::free(mpClearData); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::Setup(const BackupClientFileAttributes *) // Purpose: Get the stream ready to decode -- reads in headers // Created: 9/12/03 // // -------------------------------------------------------------------------- void BackupStoreFile::DecodedStream::Setup(const BackupClientFileAttributes *pAlterativeAttr) { // Get the size of the file int64_t fileSize = mrEncodedFile.BytesLeftToRead(); // Get the magic number to work out which order the stream is in int32_t magic; if(!mrEncodedFile.ReadFullBuffer(&magic, sizeof(magic), 0 /* not interested in bytes read if this fails */, mTimeout)) { // Couldn't read magic value THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } bool inFileOrder = true; switch(ntohl(magic)) { #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE case OBJECTMAGIC_FILE_MAGIC_VALUE_V0: mIsOldVersion = true; // control flows on #endif case OBJECTMAGIC_FILE_MAGIC_VALUE_V1: inFileOrder = true; break; #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE case OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0: mIsOldVersion = true; // control flows on #endif case OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1: inFileOrder = false; break; default: THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // If not in file order, then the index list must be read now if(!inFileOrder) { ReadBlockIndex(true /* have already read and verified the magic number */); } // Get header file_StreamFormat hdr; if(inFileOrder) { // Read the header, without the magic number if(!mrEncodedFile.ReadFullBuffer(((uint8_t*)&hdr) + sizeof(magic), sizeof(hdr) - sizeof(magic), 0 /* not interested in bytes read if this fails */, mTimeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } // Put in magic number hdr.mMagicValue = magic; } else { // Not in file order, so need to read the full header if(!mrEncodedFile.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, mTimeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } } // Check magic number if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1 #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0 #endif ) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Get the filename mFilename.ReadFromStream(mrEncodedFile, mTimeout); // Get the attributes (either from stream, or supplied attributes) if(pAlterativeAttr != 0) { // Read dummy attributes BackupClientFileAttributes attr; attr.ReadFromStream(mrEncodedFile, mTimeout); // Set to supplied attributes mAttributes = *pAlterativeAttr; } else { // Read the attributes from the stream mAttributes.ReadFromStream(mrEncodedFile, mTimeout); } // If it is in file order, go and read the file attributes // Requires that the stream can seek if(inFileOrder) { // Make sure the file size is known if(fileSize == IOStream::SizeOfStreamUnknown) { THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures) } // Store current location (beginning of encoded blocks) int64_t endOfHeaderPos = mrEncodedFile.GetPosition(); // Work out where the index is int64_t numBlocks = box_ntoh64(hdr.mNumBlocks); int64_t blockHeaderPos = fileSize - ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)); // Seek to that position mrEncodedFile.Seek(blockHeaderPos, IOStream::SeekType_Absolute); // Read the block index ReadBlockIndex(false /* magic number still to be read */); // Seek back to the end of header position, ready for reading the chunks mrEncodedFile.Seek(endOfHeaderPos, IOStream::SeekType_Absolute); } // Check view of blocks from block header and file header match if(mNumBlocks != (int64_t)box_ntoh64(hdr.mNumBlocks)) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Need to allocate some memory for the two blocks for reading encoded data, and clear data if(mNumBlocks > 0) { // Find the maximum encoded data size int32_t maxEncodedDataSize = 0; const file_BlockIndexEntry *entry = (file_BlockIndexEntry *)mpBlockIndex; ASSERT(entry != 0); for(int64_t e = 0; e < mNumBlocks; e++) { // Get the clear and encoded size int32_t encodedSize = box_ntoh64(entry[e].mEncodedSize); ASSERT(encodedSize > 0); // Larger? if(encodedSize > maxEncodedDataSize) maxEncodedDataSize = encodedSize; } // Allocate those blocks! mpEncodedData = (uint8_t*)BackupStoreFile::CodingChunkAlloc(maxEncodedDataSize + 32); // Allocate the block for the clear data, using the hint from the header. // If this is wrong, things will exception neatly later on, so it can't be used // to do anything more than cause an error on downloading. mClearDataSize = OutputBufferSizeForKnownOutputSize(ntohl(hdr.mMaxBlockClearSize)) + 32; mpClearData = (uint8_t*)::malloc(mClearDataSize); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::ReadBlockIndex(bool) // Purpose: Read the block index from the stream, and store in internal buffer (minus header) // Created: 9/12/03 // // -------------------------------------------------------------------------- void BackupStoreFile::DecodedStream::ReadBlockIndex(bool MagicAlreadyRead) { // Header file_BlockIndexHeader blkhdr; // Read it in -- way depends on how whether the magic number has already been read if(MagicAlreadyRead) { // Read the header, without the magic number if(!mrEncodedFile.ReadFullBuffer(((uint8_t*)&blkhdr) + sizeof(blkhdr.mMagicValue), sizeof(blkhdr) - sizeof(blkhdr.mMagicValue), 0 /* not interested in bytes read if this fails */, mTimeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } } else { // Magic not already read, so need to read the full header if(!mrEncodedFile.ReadFullBuffer(&blkhdr, sizeof(blkhdr), 0 /* not interested in bytes read if this fails */, mTimeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } // Check magic value if(ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0 #endif ) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } } // Get the number of blocks out of the header mNumBlocks = box_ntoh64(blkhdr.mNumBlocks); // Read the IV base mEntryIVBase = box_ntoh64(blkhdr.mEntryIVBase); // Load the block entries in? if(mNumBlocks > 0) { // How big is the index? int64_t indexSize = sizeof(file_BlockIndexEntry) * mNumBlocks; // Allocate some memory mpBlockIndex = ::malloc(indexSize); if(mpBlockIndex == 0) { throw std::bad_alloc(); } // Read it in if(!mrEncodedFile.ReadFullBuffer(mpBlockIndex, indexSize, 0 /* not interested in bytes read if this fails */, mTimeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::Read(void *, int, int) // Purpose: As interface. Reads decrpyted data. // Created: 9/12/03 // // -------------------------------------------------------------------------- int BackupStoreFile::DecodedStream::Read(void *pBuffer, int NBytes, int Timeout) { // Symlinks don't have data. So can't read it. Not even zero bytes. if(IsSymLink()) { // Don't allow reading in this case THROW_EXCEPTION(BackupStoreException, ThereIsNoDataInASymLink); } // Already finished? if(mCurrentBlock >= mNumBlocks) { // At end of stream, nothing to do return 0; } int bytesToRead = NBytes; uint8_t *output = (uint8_t*)pBuffer; while(bytesToRead > 0 && mCurrentBlock < mNumBlocks) { // Anything left in the current block? if(mPositionInCurrentBlock < mCurrentBlockClearSize) { // Copy data out of this buffer int s = mCurrentBlockClearSize - mPositionInCurrentBlock; if(s > bytesToRead) s = bytesToRead; // limit to requested data // Copy ::memcpy(output, mpClearData + mPositionInCurrentBlock, s); // Update positions output += s; mPositionInCurrentBlock += s; bytesToRead -= s; } // Need to get some more data? if(bytesToRead > 0 && mPositionInCurrentBlock >= mCurrentBlockClearSize) { // Number of next block ++mCurrentBlock; if(mCurrentBlock >= mNumBlocks) { // Stop now! break; } // Get the size from the block index const file_BlockIndexEntry *entry = (file_BlockIndexEntry *)mpBlockIndex; int32_t encodedSize = box_ntoh64(entry[mCurrentBlock].mEncodedSize); if(encodedSize <= 0) { // The caller is attempting to decode a file which is the direct result of a diff // operation, and so does not contain all the data. // It needs to be combined with the previous version first. THROW_EXCEPTION(BackupStoreException, CannotDecodeDiffedFilesWithoutCombining) } // Load in next block if(!mrEncodedFile.ReadFullBuffer(mpEncodedData, encodedSize, 0 /* not interested in bytes read if this fails */, mTimeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) } // Decode the data mCurrentBlockClearSize = BackupStoreFile::DecodeChunk(mpEncodedData, encodedSize, mpClearData, mClearDataSize); // Calculate IV for this entry uint64_t iv = mEntryIVBase; iv += mCurrentBlock; // Convert to network byte order before encrypting with it, so that restores work on // platforms with different endiannesses. iv = box_hton64(iv); sBlowfishDecryptBlockEntry.SetIV(&iv); // Decrypt the encrypted section file_BlockIndexEntryEnc entryEnc; int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc), entry[mCurrentBlock].mEnEnc, sizeof(entry[mCurrentBlock].mEnEnc)); if(sectionSize != sizeof(entryEnc)) { THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength) } // Make sure this is the right size if(mCurrentBlockClearSize != (int32_t)ntohl(entryEnc.mSize)) { #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE if(!mIsOldVersion) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Versions 0.05 and previous of Box Backup didn't properly handle endianess of the // IV for the encrypted section. Try again, with the thing the other way round iv = box_swap64(iv); sBlowfishDecryptBlockEntry.SetIV(&iv); int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc), entry[mCurrentBlock].mEnEnc, sizeof(entry[mCurrentBlock].mEnEnc)); if(sectionSize != sizeof(entryEnc)) { THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength) } if(mCurrentBlockClearSize != (int32_t)ntohl(entryEnc.mSize)) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } else { // Warn and log this issue if(!sWarnedAboutBackwardsCompatiblity) { BOX_WARNING("WARNING: Decoded one or more files using backwards compatibility mode for block index."); sWarnedAboutBackwardsCompatiblity = true; } } #else THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) #endif } // Check the digest MD5Digest md5; md5.Add(mpClearData, mCurrentBlockClearSize); md5.Finish(); if(!md5.DigestMatches((uint8_t*)entryEnc.mStrongChecksum)) { THROW_EXCEPTION(BackupStoreException, BackupStoreFileFailedIntegrityCheck) } // Set vars to say what's happening mPositionInCurrentBlock = 0; } } ASSERT(bytesToRead >= 0); ASSERT(bytesToRead <= NBytes); return NBytes - bytesToRead; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::IsSymLink() // Purpose: Is the unencoded file actually a symlink? // Created: 10/12/03 // // -------------------------------------------------------------------------- bool BackupStoreFile::DecodedStream::IsSymLink() { // First, check in with the attributes if(!mAttributes.IsSymLink()) { return false; } // So the attributes think it is a symlink. // Consistency check... if(mNumBlocks != 0) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::Write(const void *, int) // Purpose: As interface. Throws exception, as you can't write to this stream. // Created: 9/12/03 // // -------------------------------------------------------------------------- void BackupStoreFile::DecodedStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(BackupStoreException, CantWriteToDecodedFileStream) } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::StreamDataLeft() // Purpose: As interface. Any data left? // Created: 9/12/03 // // -------------------------------------------------------------------------- bool BackupStoreFile::DecodedStream::StreamDataLeft() { return mCurrentBlock < mNumBlocks; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodedStream::StreamClosed() // Purpose: As interface. Always returns true, no writing allowed. // Created: 9/12/03 // // -------------------------------------------------------------------------- bool BackupStoreFile::DecodedStream::StreamClosed() { // Can't write to this stream! return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::SetBlowfishKey(const void *, int) // Purpose: Static. Sets the key to use for encryption and decryption. // Created: 7/12/03 // // -------------------------------------------------------------------------- void BackupStoreFile::SetBlowfishKeys(const void *pKey, int KeyLength, const void *pBlockEntryKey, int BlockEntryKeyLength) { // IVs set later sBlowfishEncrypt.Reset(); sBlowfishEncrypt.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength)); sBlowfishDecrypt.Reset(); sBlowfishDecrypt.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength)); sBlowfishEncryptBlockEntry.Reset(); sBlowfishEncryptBlockEntry.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pBlockEntryKey, BlockEntryKeyLength)); sBlowfishEncryptBlockEntry.UsePadding(false); sBlowfishDecryptBlockEntry.Reset(); sBlowfishDecryptBlockEntry.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pBlockEntryKey, BlockEntryKeyLength)); sBlowfishDecryptBlockEntry.UsePadding(false); } #ifndef HAVE_OLD_SSL // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::SetAESKey(const void *, int) // Purpose: Sets the AES key to use for file data encryption. Will select AES as // the cipher to use when encrypting. // Created: 27/4/04 // // -------------------------------------------------------------------------- void BackupStoreFile::SetAESKey(const void *pKey, int KeyLength) { // Setup context sAESEncrypt.Reset(); sAESEncrypt.Init(CipherContext::Encrypt, CipherAES(CipherDescription::Mode_CBC, pKey, KeyLength)); sAESDecrypt.Reset(); sAESDecrypt.Init(CipherContext::Decrypt, CipherAES(CipherDescription::Mode_CBC, pKey, KeyLength)); // Set encryption to use this key, instead of the "default" blowfish key spEncrypt = &sAESEncrypt; sEncryptCipherType = HEADER_AES_ENCODING; } #endif // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::MaxBlockSizeForChunkSize(int) // Purpose: The maximum output size of a block, given the chunk size // Created: 7/12/03 // // -------------------------------------------------------------------------- int BackupStoreFile::MaxBlockSizeForChunkSize(int ChunkSize) { // Calculate... the maximum size of output by first the largest it could be after compression, // which is encrypted, and has a 1 bytes header and the IV added, plus 1 byte for luck // And then on top, add 128 bytes just to make sure. (Belts and braces approach to fixing // an problem where a rather non-compressable file didn't fit in a block buffer.) return sBlowfishEncrypt.MaxOutSizeForInBufferSize(Compress_MaxSizeForCompressedData(ChunkSize)) + 1 + 1 + sBlowfishEncrypt.GetIVLength() + 128; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::EncodeChunk(const void *, int, BackupStoreFile::EncodingBuffer &) // Purpose: Encodes a chunk (encryption, possible compressed beforehand) // Created: 8/12/03 // // -------------------------------------------------------------------------- int BackupStoreFile::EncodeChunk(const void *Chunk, int ChunkSize, BackupStoreFile::EncodingBuffer &rOutput) { ASSERT(spEncrypt != 0); // Check there's some space in the output block if(rOutput.mBufferSize < 256) { rOutput.Reallocate(256); } // Check alignment of the block ASSERT((((uint32_t)(long)rOutput.mpBuffer) % BACKUPSTOREFILE_CODING_BLOCKSIZE) == BACKUPSTOREFILE_CODING_OFFSET); // Want to compress it? bool compressChunk = (ChunkSize >= BACKUP_FILE_MIN_COMPRESSED_CHUNK_SIZE); // Build header uint8_t header = sEncryptCipherType << HEADER_ENCODING_SHIFT; if(compressChunk) header |= HEADER_CHUNK_IS_COMPRESSED; // Store header rOutput.mpBuffer[0] = header; int outOffset = 1; // Setup cipher, and store the IV int ivLen = 0; const void *iv = spEncrypt->SetRandomIV(ivLen); ::memcpy(rOutput.mpBuffer + outOffset, iv, ivLen); outOffset += ivLen; // Start encryption process spEncrypt->Begin(); #define ENCODECHUNK_CHECK_SPACE(ToEncryptSize) \ { \ if((rOutput.mBufferSize - outOffset) < ((ToEncryptSize) + 128)) \ { \ rOutput.Reallocate(rOutput.mBufferSize + (ToEncryptSize) + 128); \ } \ } // Encode the chunk if(compressChunk) { // buffer to compress into uint8_t buffer[2048]; // Set compressor with all the chunk as an input Compress compress; compress.Input(Chunk, ChunkSize); compress.FinishInput(); // Get and encrypt output while(!compress.OutputHasFinished()) { int s = compress.Output(buffer, sizeof(buffer)); if(s > 0) { ENCODECHUNK_CHECK_SPACE(s) outOffset += spEncrypt->Transform(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset, buffer, s); } else { // Should never happen, as we put all the input in in one go. // So if this happens, it means there's a logical problem somewhere THROW_EXCEPTION(BackupStoreException, Internal) } } ENCODECHUNK_CHECK_SPACE(16) outOffset += spEncrypt->Final(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset); } else { // Straight encryption ENCODECHUNK_CHECK_SPACE(ChunkSize) outOffset += spEncrypt->Transform(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset, Chunk, ChunkSize); ENCODECHUNK_CHECK_SPACE(16) outOffset += spEncrypt->Final(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset); } ASSERT(outOffset < rOutput.mBufferSize); // first check should have sorted this -- merely logic check return outOffset; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::DecodeChunk(const void *, int, void *, int) // Purpose: Decode an encoded chunk -- use OutputBufferSizeForKnownOutputSize() to find // the extra output buffer size needed before calling. // See notes in EncodeChunk() for notes re alignment of the // encoded data. // Created: 8/12/03 // // -------------------------------------------------------------------------- int BackupStoreFile::DecodeChunk(const void *Encoded, int EncodedSize, void *Output, int OutputSize) { // Check alignment of the encoded block ASSERT((((uint32_t)(long)Encoded) % BACKUPSTOREFILE_CODING_BLOCKSIZE) == BACKUPSTOREFILE_CODING_OFFSET); // First check if(EncodedSize < 1) { THROW_EXCEPTION(BackupStoreException, BadEncodedChunk) } const uint8_t *input = (uint8_t*)Encoded; // Get header, make checks, etc uint8_t header = input[0]; bool chunkCompressed = (header & HEADER_CHUNK_IS_COMPRESSED) == HEADER_CHUNK_IS_COMPRESSED; uint8_t encodingType = (header >> HEADER_ENCODING_SHIFT); if(encodingType != HEADER_BLOWFISH_ENCODING && encodingType != HEADER_AES_ENCODING) { THROW_EXCEPTION(BackupStoreException, ChunkHasUnknownEncoding) } #ifndef HAVE_OLD_SSL // Choose cipher CipherContext &cipher((encodingType == HEADER_AES_ENCODING)?sAESDecrypt:sBlowfishDecrypt); #else // AES not supported with this version of OpenSSL if(encodingType == HEADER_AES_ENCODING) { THROW_EXCEPTION(BackupStoreException, AEScipherNotSupportedByInstalledOpenSSL) } CipherContext &cipher(sBlowfishDecrypt); #endif // Check enough space for header, an IV and one byte of input int ivLen = cipher.GetIVLength(); if(EncodedSize < (1 + ivLen + 1)) { THROW_EXCEPTION(BackupStoreException, BadEncodedChunk) } // Set IV in decrypt context, and start cipher.SetIV(input + 1); cipher.Begin(); // Setup vars for code int inOffset = 1 + ivLen; uint8_t *output = (uint8_t*)Output; int outOffset = 0; // Do action if(chunkCompressed) { // Do things in chunks uint8_t buffer[2048]; int inputBlockLen = cipher.InSizeForOutBufferSize(sizeof(buffer)); // Decompressor Compress decompress; while(inOffset < EncodedSize) { // Decrypt a block int bl = inputBlockLen; if(bl > (EncodedSize - inOffset)) bl = EncodedSize - inOffset; // not too long int s = cipher.Transform(buffer, sizeof(buffer), input + inOffset, bl); inOffset += bl; // Decompress the decrypted data if(s > 0) { decompress.Input(buffer, s); int os = 0; do { os = decompress.Output(output + outOffset, OutputSize - outOffset); outOffset += os; } while(os > 0); // Check that there's space left in the output buffer -- there always should be if(outOffset >= OutputSize) { THROW_EXCEPTION(BackupStoreException, NotEnoughSpaceToDecodeChunk) } } } // Get any compressed data remaining in the cipher context and compression int s = cipher.Final(buffer, sizeof(buffer)); decompress.Input(buffer, s); decompress.FinishInput(); while(!decompress.OutputHasFinished()) { int os = decompress.Output(output + outOffset, OutputSize - outOffset); outOffset += os; // Check that there's space left in the output buffer -- there always should be if(outOffset >= OutputSize) { THROW_EXCEPTION(BackupStoreException, NotEnoughSpaceToDecodeChunk) } } } else { // Easy decryption outOffset += cipher.Transform(output + outOffset, OutputSize - outOffset, input + inOffset, EncodedSize - inOffset); outOffset += cipher.Final(output + outOffset, OutputSize - outOffset); } return outOffset; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::ReorderFileToStreamOrder(IOStream *, bool) // Purpose: Returns a stream which gives a Stream order version of the encoded file. // If TakeOwnership == true, then the input stream will be deleted when the // returned stream is deleted. // The input stream must be seekable. // Created: 10/12/03 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreFile::ReorderFileToStreamOrder(IOStream *pStream, bool TakeOwnership) { ASSERT(pStream != 0); // Get the size of the file int64_t fileSize = pStream->BytesLeftToRead(); if(fileSize == IOStream::SizeOfStreamUnknown) { THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures) } // Read the header int bytesRead = 0; file_StreamFormat hdr; bool readBlock = pStream->ReadFullBuffer(&hdr, sizeof(hdr), &bytesRead); // Seek backwards to put the file pointer back where it was before we started this pStream->Seek(0 - bytesRead, IOStream::SeekType_Relative); // Check we got a block if(!readBlock) { // Couldn't read header -- assume file bad THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Check magic number if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1 #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0 #endif ) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Get number of blocks int64_t numBlocks = box_ntoh64(hdr.mNumBlocks); // Calculate where the block index will be, check it's reasonable int64_t blockIndexSize = ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)); int64_t blockIndexLoc = fileSize - blockIndexSize; if(blockIndexLoc < 0) { // Doesn't look good! THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Build a reordered stream std::auto_ptr reordered(new ReadGatherStream(TakeOwnership)); // Set it up... ReadGatherStream &rreordered(*((ReadGatherStream*)reordered.get())); int component = rreordered.AddComponent(pStream); // Send out the block index rreordered.AddBlock(component, blockIndexSize, true, blockIndexLoc); // And then the rest of the file rreordered.AddBlock(component, blockIndexLoc, true, 0); return reordered; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::ResetStats() // Purpose: Reset the gathered statistics // Created: 20/1/04 // // -------------------------------------------------------------------------- void BackupStoreFile::ResetStats() { msStats.mBytesInEncodedFiles = 0; msStats.mBytesAlreadyOnServer = 0; msStats.mTotalFileStreamSize = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::CompareFileContentsAgainstBlockIndex(const char *, IOStream &) // Purpose: Compares the contents of a file against the checksums contained in the // block index. Returns true if the checksums match, meaning the file is // extremely likely to match the original. Will always consume the entire index. // Created: 21/1/04 // // -------------------------------------------------------------------------- bool BackupStoreFile::CompareFileContentsAgainstBlockIndex(const char *Filename, IOStream &rBlockIndex, int Timeout) { // is it a symlink? bool sourceIsSymlink = false; { EMU_STRUCT_STAT st; if(EMU_LSTAT(Filename, &st) == -1) { THROW_EXCEPTION(CommonException, OSFileError) } if((st.st_mode & S_IFMT) == S_IFLNK) { sourceIsSymlink = true; } } // Open file, if it's not a symlink std::auto_ptr in; if(!sourceIsSymlink) { in.reset(new FileStream(Filename)); } // Read header file_BlockIndexHeader hdr; if(!rBlockIndex.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, Timeout)) { // Couldn't read header THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Check magic if(hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE && hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0) #endif ) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE bool isOldVersion = hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0); #endif // Get basic information int64_t numBlocks = box_ntoh64(hdr.mNumBlocks); uint64_t entryIVBase = box_ntoh64(hdr.mEntryIVBase); //TODO: Verify that these sizes look reasonable // setup void *data = 0; int32_t dataSize = -1; bool matches = true; int64_t totalSizeInBlockIndex = 0; try { for(int64_t b = 0; b < numBlocks; ++b) { // Read an entry from the stream file_BlockIndexEntry entry; if(!rBlockIndex.ReadFullBuffer(&entry, sizeof(entry), 0 /* not interested in bytes read if this fails */, Timeout)) { // Couldn't read entry THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Calculate IV for this entry uint64_t iv = entryIVBase; iv += b; iv = box_hton64(iv); #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE if(isOldVersion) { // Reverse the IV for compatibility iv = box_swap64(iv); } #endif sBlowfishDecryptBlockEntry.SetIV(&iv); // Decrypt the encrypted section file_BlockIndexEntryEnc entryEnc; int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc), entry.mEnEnc, sizeof(entry.mEnEnc)); if(sectionSize != sizeof(entryEnc)) { THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength) } // Size of block int32_t blockClearSize = ntohl(entryEnc.mSize); if(blockClearSize < 0 || blockClearSize > (BACKUP_FILE_MAX_BLOCK_SIZE + 1024)) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } totalSizeInBlockIndex += blockClearSize; // Make sure there's enough memory allocated to load the block in if(dataSize < blockClearSize) { // Too small, free the block if it's already allocated if(data != 0) { ::free(data); data = 0; } // Allocate a block data = ::malloc(blockClearSize + 128); if(data == 0) { throw std::bad_alloc(); } dataSize = blockClearSize + 128; } // Load in the block from the file, if it's not a symlink if(!sourceIsSymlink) { if(in->Read(data, blockClearSize) != blockClearSize) { // Not enough data left in the file, can't possibly match matches = false; } else { // Check the checksum MD5Digest md5; md5.Add(data, blockClearSize); md5.Finish(); if(!md5.DigestMatches(entryEnc.mStrongChecksum)) { // Checksum didn't match matches = false; } } } // Keep on going regardless, to make sure the entire block index stream is read // -- must always be consistent about what happens with the stream. } } catch(...) { // clean up in case of errors if(data != 0) { ::free(data); data = 0; } throw; } // free block if(data != 0) { ::free(data); data = 0; } // Check for data left over if it's not a symlink if(!sourceIsSymlink) { // Anything left to read in the file? if(in->BytesLeftToRead() != 0) { // File has extra data at the end matches = false; } } // Symlinks must have zero size on server if(sourceIsSymlink) { matches = (totalSizeInBlockIndex == 0); } return matches; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::EncodingBuffer::EncodingBuffer() // Purpose: Constructor // Created: 25/11/04 // // -------------------------------------------------------------------------- BackupStoreFile::EncodingBuffer::EncodingBuffer() : mpBuffer(0), mBufferSize(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::EncodingBuffer::~EncodingBuffer() // Purpose: Destructor // Created: 25/11/04 // // -------------------------------------------------------------------------- BackupStoreFile::EncodingBuffer::~EncodingBuffer() { if(mpBuffer != 0) { BackupStoreFile::CodingChunkFree(mpBuffer); mpBuffer = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::EncodingBuffer::Allocate(int) // Purpose: Do initial allocation of block // Created: 25/11/04 // // -------------------------------------------------------------------------- void BackupStoreFile::EncodingBuffer::Allocate(int Size) { ASSERT(mpBuffer == 0); uint8_t *buffer = (uint8_t*)BackupStoreFile::CodingChunkAlloc(Size); if(buffer == 0) { throw std::bad_alloc(); } mpBuffer = buffer; mBufferSize = Size; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::EncodingBuffer::Reallocate(int) // Purpose: Reallocate the block. Try not to call this, it has to copy // the entire contents as the block can't be reallocated straight. // Created: 25/11/04 // // -------------------------------------------------------------------------- void BackupStoreFile::EncodingBuffer::Reallocate(int NewSize) { BOX_TRACE("Reallocating EncodingBuffer from " << mBufferSize << " to " << NewSize); ASSERT(mpBuffer != 0); uint8_t *buffer = (uint8_t*)BackupStoreFile::CodingChunkAlloc(NewSize); if(buffer == 0) { throw std::bad_alloc(); } // Copy data ::memcpy(buffer, mpBuffer, (NewSize > mBufferSize)?mBufferSize:NewSize); // Free old BackupStoreFile::CodingChunkFree(mpBuffer); // Store new buffer mpBuffer = buffer; mBufferSize = NewSize; } // -------------------------------------------------------------------------- // // Function // Name: DiffTimer::DiffTimer(); // Purpose: Constructor // Created: 2005/02/01 // // -------------------------------------------------------------------------- DiffTimer::DiffTimer() { } // -------------------------------------------------------------------------- // // Function // Name: DiffTimer::DiffTimer(); // Purpose: Destructor // Created: 2005/02/01 // // -------------------------------------------------------------------------- DiffTimer::~DiffTimer() { } boxbackup/lib/backupclient/BackupStoreDirectory.cpp0000664000175000017500000003643610515010774023353 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreDirectory.h // Purpose: Representation of a backup directory // Created: 2003/08/26 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupStoreDirectory.h" #include "IOStream.h" #include "BackupStoreException.h" #include "BackupStoreObjectMagic.h" #include "MemLeakFindOn.h" // set packing to one byte #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "BeginStructPackForWire.h" #else BEGIN_STRUCTURE_PACKING_FOR_WIRE #endif typedef struct { int32_t mMagicValue; // also the version number int32_t mNumEntries; int64_t mObjectID; // this object ID int64_t mContainerID; // ID of container uint64_t mAttributesModTime; int32_t mOptionsPresent; // bit mask of optional sections / features present // Then a StreamableMemBlock for attributes } dir_StreamFormat; typedef enum { Option_DependencyInfoPresent = 1 } dir_StreamFormatOptions; typedef struct { uint64_t mModificationTime; int64_t mObjectID; int64_t mSizeInBlocks; uint64_t mAttributesHash; int16_t mFlags; // order smaller items after bigger ones (for alignment) // Then a BackupStoreFilename // Then a StreamableMemBlock for attributes } en_StreamFormat; typedef struct { int64_t mDependsNewer; int64_t mDependsOlder; } en_StreamFormatDepends; // Use default packing #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "EndStructPackForWire.h" #else END_STRUCTURE_PACKING_FOR_WIRE #endif // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::BackupStoreDirectory() // Purpose: Constructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreDirectory::BackupStoreDirectory() : mRevisionID(0), mObjectID(0), mContainerID(0), mAttributesModTime(0), mUserInfo1(0) { ASSERT(sizeof(u_int64_t) == sizeof(box_time_t)); } // -------------------------------------------------------------------------- // // File // Name: BackupStoreDirectory::BackupStoreDirectory(int64_t, int64_t) // Purpose: Constructor giving object and container IDs // Created: 2003/08/28 // // -------------------------------------------------------------------------- BackupStoreDirectory::BackupStoreDirectory(int64_t ObjectID, int64_t ContainerID) : mRevisionID(0), mObjectID(ObjectID), mContainerID(ContainerID), mAttributesModTime(0), mUserInfo1(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::~BackupStoreDirectory() // Purpose: Destructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreDirectory::~BackupStoreDirectory() { for(std::vector::iterator i(mEntries.begin()); i != mEntries.end(); ++i) { delete (*i); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::ReadFromStream(IOStream &, int) // Purpose: Reads the directory contents from a stream. Exceptions will yeild incomplete reads. // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreDirectory::ReadFromStream(IOStream &rStream, int Timeout) { // Get the header dir_StreamFormat hdr; if(!rStream.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, Timeout)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Check magic value... if(OBJECTMAGIC_DIR_MAGIC_VALUE != ntohl(hdr.mMagicValue)) { THROW_EXCEPTION(BackupStoreException, BadDirectoryFormat) } // Get data mObjectID = box_ntoh64(hdr.mObjectID); mContainerID = box_ntoh64(hdr.mContainerID); mAttributesModTime = box_ntoh64(hdr.mAttributesModTime); // Options int32_t options = ntohl(hdr.mOptionsPresent); // Get attributes mAttributes.ReadFromStream(rStream, Timeout); // Decode count int count = ntohl(hdr.mNumEntries); // Clear existing list for(std::vector::iterator i = mEntries.begin(); i != mEntries.end(); i++) { delete (*i); } mEntries.clear(); // Read them in! for(int c = 0; c < count; ++c) { Entry *pen = new Entry; try { // Read from stream pen->ReadFromStream(rStream, Timeout); // Add to list mEntries.push_back(pen); } catch(...) { delete pen; throw; } } // Read in dependency info? if(options & Option_DependencyInfoPresent) { // Read in extra dependency data for(int c = 0; c < count; ++c) { mEntries[c]->ReadFromStreamDependencyInfo(rStream, Timeout); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::WriteToStream(IOStream &, int16_t, int16_t, bool, bool) // Purpose: Writes a selection of entries to a stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreDirectory::WriteToStream(IOStream &rStream, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet, bool StreamAttributes, bool StreamDependencyInfo) const { // Get count of entries int32_t count = mEntries.size(); if(FlagsMustBeSet != Entry::Flags_INCLUDE_EVERYTHING || FlagsNotToBeSet != Entry::Flags_EXCLUDE_NOTHING) { // Need to count the entries count = 0; Iterator i(*this); while(i.Next(FlagsMustBeSet, FlagsNotToBeSet) != 0) { count++; } } // Check that sensible IDs have been set ASSERT(mObjectID != 0); ASSERT(mContainerID != 0); // Need dependency info? bool dependencyInfoRequired = false; if(StreamDependencyInfo) { Iterator i(*this); Entry *pen = 0; while((pen = i.Next(FlagsMustBeSet, FlagsNotToBeSet)) != 0) { if(pen->HasDependencies()) { dependencyInfoRequired = true; } } } // Options int32_t options = 0; if(dependencyInfoRequired) options |= Option_DependencyInfoPresent; // Build header dir_StreamFormat hdr; hdr.mMagicValue = htonl(OBJECTMAGIC_DIR_MAGIC_VALUE); hdr.mNumEntries = htonl(count); hdr.mObjectID = box_hton64(mObjectID); hdr.mContainerID = box_hton64(mContainerID); hdr.mAttributesModTime = box_hton64(mAttributesModTime); hdr.mOptionsPresent = htonl(options); // Write header rStream.Write(&hdr, sizeof(hdr)); // Write the attributes? if(StreamAttributes) { mAttributes.WriteToStream(rStream); } else { // Write a blank header instead StreamableMemBlock::WriteEmptyBlockToStream(rStream); } // Then write all the entries Iterator i(*this); Entry *pen = 0; while((pen = i.Next(FlagsMustBeSet, FlagsNotToBeSet)) != 0) { pen->WriteToStream(rStream); } // Write dependency info? if(dependencyInfoRequired) { Iterator i(*this); Entry *pen = 0; while((pen = i.Next(FlagsMustBeSet, FlagsNotToBeSet)) != 0) { pen->WriteToStreamDependencyInfo(rStream); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::AddEntry(const Entry &) // Purpose: Adds entry to directory (no checking) // Created: 2003/08/27 // // -------------------------------------------------------------------------- BackupStoreDirectory::Entry *BackupStoreDirectory::AddEntry(const Entry &rEntryToCopy) { Entry *pnew = new Entry(rEntryToCopy); try { mEntries.push_back(pnew); } catch(...) { delete pnew; throw; } return pnew; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::AddEntry(const BackupStoreFilename &, int64_t, int64_t, int16_t) // Purpose: Adds entry to directory (no checking) // Created: 2003/08/27 // // -------------------------------------------------------------------------- BackupStoreDirectory::Entry *BackupStoreDirectory::AddEntry(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags, box_time_t AttributesModTime) { Entry *pnew = new Entry(rName, ModificationTime, ObjectID, SizeInBlocks, Flags, AttributesModTime); try { mEntries.push_back(pnew); } catch(...) { delete pnew; throw; } return pnew; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::DeleteEntry(int64_t) // Purpose: Deletes entry with given object ID (uses linear search, maybe a little inefficient) // Created: 2003/08/27 // // -------------------------------------------------------------------------- void BackupStoreDirectory::DeleteEntry(int64_t ObjectID) { for(std::vector::iterator i(mEntries.begin()); i != mEntries.end(); ++i) { if((*i)->mObjectID == ObjectID) { // Delete delete (*i); // Remove from list mEntries.erase(i); // Done return; } } // Not found THROW_EXCEPTION(BackupStoreException, CouldNotFindEntryInDirectory) } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::FindEntryByID(int64_t) // Purpose: Finds a specific entry. Returns 0 if the entry doesn't exist. // Created: 12/11/03 // // -------------------------------------------------------------------------- BackupStoreDirectory::Entry *BackupStoreDirectory::FindEntryByID(int64_t ObjectID) const { for(std::vector::const_iterator i(mEntries.begin()); i != mEntries.end(); ++i) { if((*i)->mObjectID == ObjectID) { // Found return (*i); } } // Not found return 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::Entry() // Purpose: Constructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreDirectory::Entry::Entry() : mModificationTime(0), mObjectID(0), mSizeInBlocks(0), mFlags(0), mAttributesHash(0), mMinMarkNumber(0), mMarkNumber(0), mDependsNewer(0), mDependsOlder(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::~Entry() // Purpose: Destructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreDirectory::Entry::~Entry() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::Entry(const Entry &) // Purpose: Copy constructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreDirectory::Entry::Entry(const Entry &rToCopy) : mName(rToCopy.mName), mModificationTime(rToCopy.mModificationTime), mObjectID(rToCopy.mObjectID), mSizeInBlocks(rToCopy.mSizeInBlocks), mFlags(rToCopy.mFlags), mAttributesHash(rToCopy.mAttributesHash), mAttributes(rToCopy.mAttributes), mMinMarkNumber(rToCopy.mMinMarkNumber), mMarkNumber(rToCopy.mMarkNumber), mDependsNewer(rToCopy.mDependsNewer), mDependsOlder(rToCopy.mDependsOlder) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::Entry(const BackupStoreFilename &, int64_t, int64_t, int16_t) // Purpose: Constructor from values // Created: 2003/08/27 // // -------------------------------------------------------------------------- BackupStoreDirectory::Entry::Entry(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags, uint64_t AttributesHash) : mName(rName), mModificationTime(ModificationTime), mObjectID(ObjectID), mSizeInBlocks(SizeInBlocks), mFlags(Flags), mAttributesHash(AttributesHash), mMinMarkNumber(0), mMarkNumber(0), mDependsNewer(0), mDependsOlder(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::TryReading(IOStream &, int) // Purpose: Read an entry from a stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreDirectory::Entry::ReadFromStream(IOStream &rStream, int Timeout) { // Grab the raw bytes from the stream which compose the header en_StreamFormat entry; if(!rStream.ReadFullBuffer(&entry, sizeof(entry), 0 /* not interested in bytes read if this fails */, Timeout)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Do reading first before modifying the variables, to be more exception safe // Get the filename BackupStoreFilename name; name.ReadFromStream(rStream, Timeout); // Get the attributes mAttributes.ReadFromStream(rStream, Timeout); // Store the rest of the bits mModificationTime = box_ntoh64(entry.mModificationTime); mObjectID = box_ntoh64(entry.mObjectID); mSizeInBlocks = box_ntoh64(entry.mSizeInBlocks); mAttributesHash = box_ntoh64(entry.mAttributesHash); mFlags = ntohs(entry.mFlags); mName = name; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::WriteToStream(IOStream &) // Purpose: Writes the entry to a stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreDirectory::Entry::WriteToStream(IOStream &rStream) const { // Build a structure en_StreamFormat entry; entry.mModificationTime = box_hton64(mModificationTime); entry.mObjectID = box_hton64(mObjectID); entry.mSizeInBlocks = box_hton64(mSizeInBlocks); entry.mAttributesHash = box_hton64(mAttributesHash); entry.mFlags = htons(mFlags); // Write it rStream.Write(&entry, sizeof(entry)); // Write the filename mName.WriteToStream(rStream); // Write any attributes mAttributes.WriteToStream(rStream); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::ReadFromStreamDependencyInfo(IOStream &, int) // Purpose: Read the optional dependency info from a stream // Created: 13/7/04 // // -------------------------------------------------------------------------- void BackupStoreDirectory::Entry::ReadFromStreamDependencyInfo(IOStream &rStream, int Timeout) { // Grab the raw bytes from the stream which compose the header en_StreamFormatDepends depends; if(!rStream.ReadFullBuffer(&depends, sizeof(depends), 0 /* not interested in bytes read if this fails */, Timeout)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Store the data mDependsNewer = box_ntoh64(depends.mDependsNewer); mDependsOlder = box_ntoh64(depends.mDependsOlder); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDirectory::Entry::WriteToStreamDependencyInfo(IOStream &) // Purpose: Write the optional dependency info to a stream // Created: 13/7/04 // // -------------------------------------------------------------------------- void BackupStoreDirectory::Entry::WriteToStreamDependencyInfo(IOStream &rStream) const { // Build structure en_StreamFormatDepends depends; depends.mDependsNewer = box_hton64(mDependsNewer); depends.mDependsOlder = box_hton64(mDependsOlder); // Write rStream.Write(&depends, sizeof(depends)); } boxbackup/lib/backupclient/BackupStoreFile.h0000664000175000017500000001665711165365160021742 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFile.h // Purpose: Utils for manipulating files // Created: 2003/08/28 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREFILE__H #define BACKUPSTOREFILE__H #include #include #include #include "BackupClientFileAttributes.h" #include "BackupStoreFilename.h" #include "IOStream.h" #include "ReadLoggingStream.h" #include "RunStatusProvider.h" typedef struct { int64_t mBytesInEncodedFiles; int64_t mBytesAlreadyOnServer; int64_t mTotalFileStreamSize; } BackupStoreFileStats; // Uncomment to disable backwards compatibility //#define BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE // Output buffer to EncodeChunk and input data to DecodeChunk must // have specific alignment, see function comments. #define BACKUPSTOREFILE_CODING_BLOCKSIZE 16 #define BACKUPSTOREFILE_CODING_OFFSET 15 // Have some memory allocation commands, note closing "Off" at end of file. #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Class // Name: DiffTimer // Purpose: Interface for classes that can keep track of diffing time, // and send SSL keepalive messages // Created: 2006/01/19 // // -------------------------------------------------------------------------- class DiffTimer { public: DiffTimer(); virtual ~DiffTimer(); public: virtual void DoKeepAlive() = 0; virtual int GetMaximumDiffingTime() = 0; virtual bool IsManaged() = 0; }; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreFile // Purpose: Class to hold together utils for manipulating files. // Created: 2003/08/28 // // -------------------------------------------------------------------------- class BackupStoreFile { public: class DecodedStream : public IOStream { friend class BackupStoreFile; private: DecodedStream(IOStream &rEncodedFile, int Timeout); DecodedStream(const DecodedStream &); // not allowed DecodedStream &operator=(const DecodedStream &); // not allowed public: ~DecodedStream(); // Stream functions virtual int Read(void *pBuffer, int NBytes, int Timeout); virtual void Write(const void *pBuffer, int NBytes); virtual bool StreamDataLeft(); virtual bool StreamClosed(); // Accessor functions const BackupClientFileAttributes &GetAttributes() {return mAttributes;} const BackupStoreFilename &GetFilename() {return mFilename;} int64_t GetNumBlocks() {return mNumBlocks;} // primarily for tests bool IsSymLink(); private: void Setup(const BackupClientFileAttributes *pAlterativeAttr); void ReadBlockIndex(bool MagicAlreadyRead); private: IOStream &mrEncodedFile; int mTimeout; BackupClientFileAttributes mAttributes; BackupStoreFilename mFilename; int64_t mNumBlocks; void *mpBlockIndex; uint8_t *mpEncodedData; uint8_t *mpClearData; int mClearDataSize; int mCurrentBlock; int mCurrentBlockClearSize; int mPositionInCurrentBlock; uint64_t mEntryIVBase; #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE bool mIsOldVersion; #endif }; // Main interface static std::auto_ptr EncodeFile(const char *Filename, int64_t ContainerID, const BackupStoreFilename &rStoreFilename, int64_t *pModificationTime = 0, ReadLoggingStream::Logger* pLogger = NULL, RunStatusProvider* pRunStatusProvider = NULL); static std::auto_ptr EncodeFileDiff ( const char *Filename, int64_t ContainerID, const BackupStoreFilename &rStoreFilename, int64_t DiffFromObjectID, IOStream &rDiffFromBlockIndex, int Timeout, DiffTimer *pDiffTimer, int64_t *pModificationTime = 0, bool *pIsCompletelyDifferent = 0 ); static bool VerifyEncodedFileFormat(IOStream &rFile, int64_t *pDiffFromObjectIDOut = 0, int64_t *pContainerIDOut = 0); static void CombineFile(IOStream &rDiff, IOStream &rDiff2, IOStream &rFrom, IOStream &rOut); static void CombineDiffs(IOStream &rDiff1, IOStream &rDiff2, IOStream &rDiff2b, IOStream &rOut); static void ReverseDiffFile(IOStream &rDiff, IOStream &rFrom, IOStream &rFrom2, IOStream &rOut, int64_t ObjectIDOfFrom, bool *pIsCompletelyDifferent = 0); static void DecodeFile(IOStream &rEncodedFile, const char *DecodedFilename, int Timeout, const BackupClientFileAttributes *pAlterativeAttr = 0); static std::auto_ptr DecodeFileStream(IOStream &rEncodedFile, int Timeout, const BackupClientFileAttributes *pAlterativeAttr = 0); static bool CompareFileContentsAgainstBlockIndex(const char *Filename, IOStream &rBlockIndex, int Timeout); static std::auto_ptr CombineFileIndices(IOStream &rDiff, IOStream &rFrom, bool DiffIsIndexOnly = false, bool FromIsIndexOnly = false); // Stream manipulation static std::auto_ptr ReorderFileToStreamOrder(IOStream *pStream, bool TakeOwnership); static void MoveStreamPositionToBlockIndex(IOStream &rStream); // Crypto setup static void SetBlowfishKeys(const void *pKey, int KeyLength, const void *pBlockEntryKey, int BlockEntryKeyLength); #ifndef HAVE_OLD_SSL static void SetAESKey(const void *pKey, int KeyLength); #endif // Allocation of properly aligning chunks for decoding and encoding chunks inline static void *CodingChunkAlloc(int Size) { uint8_t *a = (uint8_t*)malloc((Size) + (BACKUPSTOREFILE_CODING_BLOCKSIZE * 3)); if(a == 0) return 0; // Align to main block size ASSERT(sizeof(unsigned long) >= sizeof(void*)); // make sure casting the right pointer size uint8_t adjustment = BACKUPSTOREFILE_CODING_BLOCKSIZE - (uint8_t)(((unsigned long)a) % BACKUPSTOREFILE_CODING_BLOCKSIZE); uint8_t *b = (a + adjustment); // Store adjustment *b = adjustment; // Return offset return b + BACKUPSTOREFILE_CODING_OFFSET; } inline static void CodingChunkFree(void *Block) { // Check alignment is as expected ASSERT(sizeof(unsigned long) >= sizeof(void*)); // make sure casting the right pointer size ASSERT((uint8_t)(((unsigned long)Block) % BACKUPSTOREFILE_CODING_BLOCKSIZE) == BACKUPSTOREFILE_CODING_OFFSET); uint8_t *a = (uint8_t*)Block; a -= BACKUPSTOREFILE_CODING_OFFSET; // Adjust downwards... a -= *a; free(a); } static void DiffTimerExpired(); // Building blocks class EncodingBuffer { public: EncodingBuffer(); ~EncodingBuffer(); private: // No copying EncodingBuffer(const EncodingBuffer &); EncodingBuffer &operator=(const EncodingBuffer &); public: void Allocate(int Size); void Reallocate(int NewSize); uint8_t *mpBuffer; int mBufferSize; }; static int MaxBlockSizeForChunkSize(int ChunkSize); static int EncodeChunk(const void *Chunk, int ChunkSize, BackupStoreFile::EncodingBuffer &rOutput); // Caller should know how big the output size is, but also allocate a bit more memory to cover various // overheads allowed for in checks static inline int OutputBufferSizeForKnownOutputSize(int KnownChunkSize) { // Plenty big enough return KnownChunkSize + 256; } static int DecodeChunk(const void *Encoded, int EncodedSize, void *Output, int OutputSize); // Statisitics, not designed to be completely reliable static void ResetStats(); static BackupStoreFileStats msStats; // For debug #ifndef BOX_RELEASE_BUILD static bool TraceDetailsOfDiffProcess; #endif // For decoding encoded files static void DumpFile(void *clibFileHandle, bool ToTrace, IOStream &rFile); }; #include "MemLeakFindOff.h" #endif // BACKUPSTOREFILE__H boxbackup/lib/backupclient/BackupStoreException.txt0000664000175000017500000000574611221401721023371 0ustar siretartsiretartEXCEPTION BackupStore 4 Internal 0 BadAccountDatabaseFile 1 AccountDatabaseNoSuchEntry 2 InvalidBackupStoreFilename 3 UnknownFilenameEncoding 4 CouldntReadEntireStructureFromStream 5 BadDirectoryFormat 6 CouldNotFindEntryInDirectory 7 OutputFileAlreadyExists 8 OSFileError 9 StreamDoesntHaveRequiredFeatures 10 BadBackupStoreFile 11 CouldNotLoadStoreInfo 12 BadStoreInfoOnLoad 13 StoreInfoIsReadOnly 14 StoreInfoDirNotInList 15 StoreInfoBlockDeltaMakesValueNegative 16 DirectoryHasBeenDeleted 17 StoreInfoNotInitialised 18 StoreInfoAlreadyLoaded 19 StoreInfoNotLoaded 20 ReadFileFromStreamTimedOut 21 FileWrongSizeAfterBeingStored 22 AddedFileDoesNotVerify 23 StoreInfoForWrongAccount 24 ContextIsReadOnly 25 AttributesNotLoaded 26 AttributesNotUnderstood 27 WrongServerVersion 28 # client side ClientMarkerNotAsExpected 29 Another process logged into the store and modified it while this process was running. Check you're not running two or more clients on the same account. NameAlreadyExistsInDirectory 30 BerkelyDBFailure 31 # client side InodeMapIsReadOnly 32 # client side InodeMapNotOpen 33 # client side FilenameEncryptionKeyNotKnown 34 FilenameEncryptionNoKeyForSpecifiedMethod 35 FilenameEncryptionNotSetup 36 CouldntLoadClientKeyMaterial 37 BadEncryptedAttributes 38 EncryptedAttributesHaveUnknownEncoding 39 OutputSizeTooSmallForChunk 40 BadEncodedChunk 41 NotEnoughSpaceToDecodeChunk 42 ChunkHasUnknownEncoding 43 ChunkContainsBadCompressedData 44 CantWriteToEncodedFileStream 45 Temp_FileEncodeStreamDidntReadBuffer 46 CantWriteToDecodedFileStream 47 WhenDecodingExpectedToReadButCouldnt 48 BackupStoreFileFailedIntegrityCheck 49 ThereIsNoDataInASymLink 50 IVLengthForEncodedBlockSizeDoesntMeetLengthRequirements 51 BlockEntryEncodingDidntGiveExpectedLength 52 CouldNotFindUnusedIDDuringAllocation 53 AddedFileExceedsStorageLimit 54 CannotDiffAnIncompleteStoreFile 55 CannotDecodeDiffedFilesWithoutCombining 56 FailedToReadBlockOnCombine 57 OnCombineFromFileIsIncomplete 58 BadNotifySysadminEventCode 59 InternalAlgorithmErrorCheckIDNotMonotonicallyIncreasing 60 CouldNotLockStoreAccount 61 Another process is accessing this account -- is a client connected to the server? AttributeHashSecretNotSet 62 AEScipherNotSupportedByInstalledOpenSSL 63 The system needs to be compiled with support for OpenSSL 0.9.7 or later to be able to decode files encrypted with AES SignalReceived 64 A signal was received by the process, restart or terminate needed. Exception thrown to abort connection. IncompatibleFromAndDiffFiles 65 Attempt to use a diff and a from file together, when they're not related DiffFromIDNotFoundInDirectory 66 When uploading via a diff, the diff from file must be in the same directory PatchChainInfoBadInDirectory 67 A directory contains inconsistent information. Run bbstoreaccounts check to fix it. UnknownObjectRefCountRequested 68 A reference count was requested for an object whose reference count is not known. boxbackup/lib/backupclient/BackupStoreFileCryptVar.cpp0000664000175000017500000000173610347400657023762 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileCryptVar.cpp // Purpose: Cryptographic keys for backup store files // Created: 12/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #include "BackupStoreFileCryptVar.h" #include "BackupStoreFileWire.h" #include "MemLeakFindOn.h" CipherContext BackupStoreFileCryptVar::sBlowfishEncrypt; CipherContext BackupStoreFileCryptVar::sBlowfishDecrypt; #ifndef HAVE_OLD_SSL CipherContext BackupStoreFileCryptVar::sAESEncrypt; CipherContext BackupStoreFileCryptVar::sAESDecrypt; #endif // Default to blowfish CipherContext *BackupStoreFileCryptVar::spEncrypt = &BackupStoreFileCryptVar::sBlowfishEncrypt; uint8_t BackupStoreFileCryptVar::sEncryptCipherType = HEADER_BLOWFISH_ENCODING; CipherContext BackupStoreFileCryptVar::sBlowfishEncryptBlockEntry; CipherContext BackupStoreFileCryptVar::sBlowfishDecryptBlockEntry; boxbackup/lib/backupclient/BackupStoreFileEncodeStream.cpp0000664000175000017500000005147211165365160024561 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileEncodeStream.cpp // Purpose: Implement stream-based file encoding for the backup store // Created: 12/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupClientFileAttributes.h" #include "BackupStoreConstants.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" #include "BackupStoreFileCryptVar.h" #include "BackupStoreFileEncodeStream.h" #include "BackupStoreFileWire.h" #include "BackupStoreObjectMagic.h" #include "BoxTime.h" #include "FileStream.h" #include "Random.h" #include "RollingChecksum.h" #include "MemLeakFindOn.h" #include using namespace BackupStoreFileCryptVar; // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::BackupStoreFileEncodeStream // Purpose: Constructor (opens file) // Created: 8/12/03 // // -------------------------------------------------------------------------- BackupStoreFileEncodeStream::BackupStoreFileEncodeStream() : mpRecipe(0), mpFile(0), mpLogging(0), mpRunStatusProvider(NULL), mStatus(Status_Header), mSendData(true), mTotalBlocks(0), mAbsoluteBlockNumber(-1), mInstructionNumber(-1), mNumBlocks(0), mCurrentBlock(-1), mCurrentBlockEncodedSize(0), mPositionInCurrentBlock(0), mBlockSize(BACKUP_FILE_MIN_BLOCK_SIZE), mLastBlockSize(0), mpRawBuffer(0), mAllocatedBufferSize(0), mEntryIVBase(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::~BackupStoreFileEncodeStream() // Purpose: Destructor // Created: 8/12/03 // // -------------------------------------------------------------------------- BackupStoreFileEncodeStream::~BackupStoreFileEncodeStream() { // Free buffers if(mpRawBuffer) { ::free(mpRawBuffer); mpRawBuffer = 0; } // Close the file, which we might have open if(mpFile) { delete mpFile; mpFile = 0; } // Clear up logging stream if(mpLogging) { delete mpLogging; mpLogging = 0; } // Free the recipe if(mpRecipe != 0) { delete mpRecipe; mpRecipe = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::Setup(const char *, Recipe *, int64_t, const BackupStoreFilename &, int64_t *) // Purpose: Reads file information, and builds file header reading for sending. // Takes ownership of the Recipe. // Created: 8/12/03 // // -------------------------------------------------------------------------- void BackupStoreFileEncodeStream::Setup(const char *Filename, BackupStoreFileEncodeStream::Recipe *pRecipe, int64_t ContainerID, const BackupStoreFilename &rStoreFilename, int64_t *pModificationTime, ReadLoggingStream::Logger* pLogger, RunStatusProvider* pRunStatusProvider) { // Pointer to a blank recipe which we might create BackupStoreFileEncodeStream::Recipe *pblankRecipe = 0; try { // Get file attributes box_time_t modTime = 0; int64_t fileSize = 0; BackupClientFileAttributes attr; attr.ReadAttributes(Filename, false /* no zeroing of modification times */, &modTime, 0 /* not interested in attr mod time */, &fileSize); // Might need to create a blank recipe... if(pRecipe == 0) { pblankRecipe = new BackupStoreFileEncodeStream::Recipe(0, 0); BackupStoreFileEncodeStream::RecipeInstruction instruction; instruction.mSpaceBefore = fileSize; // whole file instruction.mBlocks = 0; // no blocks instruction.mpStartBlock = 0; // no block pblankRecipe->push_back(instruction); pRecipe = pblankRecipe; } // Tell caller? if(pModificationTime != 0) { *pModificationTime = modTime; } // Go through each instruction in the recipe and work out how many blocks // it will add, and the max clear size of these blocks int maxBlockClearSize = 0; for(uint64_t inst = 0; inst < pRecipe->size(); ++inst) { if((*pRecipe)[inst].mSpaceBefore > 0) { // Calculate the number of blocks the space before requires int64_t numBlocks; int32_t blockSize, lastBlockSize; CalculateBlockSizes((*pRecipe)[inst].mSpaceBefore, numBlocks, blockSize, lastBlockSize); // Add to accumlated total mTotalBlocks += numBlocks; // Update maximum clear size if(blockSize > maxBlockClearSize) maxBlockClearSize = blockSize; if(lastBlockSize > maxBlockClearSize) maxBlockClearSize = lastBlockSize; } // Add number of blocks copied from the previous file mTotalBlocks += (*pRecipe)[inst].mBlocks; // Check for bad things if((*pRecipe)[inst].mBlocks < 0 || ((*pRecipe)[inst].mBlocks != 0 && (*pRecipe)[inst].mpStartBlock == 0)) { THROW_EXCEPTION(BackupStoreException, Internal) } // Run through blocks to get the max clear size for(int32_t b = 0; b < (*pRecipe)[inst].mBlocks; ++b) { if((*pRecipe)[inst].mpStartBlock[b].mSize > maxBlockClearSize) maxBlockClearSize = (*pRecipe)[inst].mpStartBlock[b].mSize; } } // Send data? (symlinks don't have any data in them) mSendData = !attr.IsSymLink(); // If not data is being sent, then the max clear block size is zero if(!mSendData) { maxBlockClearSize = 0; } // Header file_StreamFormat hdr; hdr.mMagicValue = htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V1); hdr.mNumBlocks = (mSendData)?(box_hton64(mTotalBlocks)):(0); hdr.mContainerID = box_hton64(ContainerID); hdr.mModificationTime = box_hton64(modTime); // add a bit to make it harder to tell what's going on -- try not to give away too much info about file size hdr.mMaxBlockClearSize = htonl(maxBlockClearSize + 128); hdr.mOptions = 0; // no options defined yet // Write header to stream mData.Write(&hdr, sizeof(hdr)); // Write filename to stream rStoreFilename.WriteToStream(mData); // Write attributes to stream attr.WriteToStream(mData); // Allocate some buffers for writing data if(mSendData) { // Open the file mpFile = new FileStream(Filename); if (pLogger) { // Create logging stream mpLogging = new ReadLoggingStream(*mpFile, *pLogger); } else { // re-use FileStream instead mpLogging = mpFile; mpFile = NULL; } // Work out the largest possible block required for the encoded data mAllocatedBufferSize = BackupStoreFile::MaxBlockSizeForChunkSize(maxBlockClearSize); // Then allocate two blocks of this size mpRawBuffer = (uint8_t*)::malloc(mAllocatedBufferSize); if(mpRawBuffer == 0) { throw std::bad_alloc(); } #ifndef BOX_RELEASE_BUILD // In debug builds, make sure that the reallocation code is exercised. mEncodedBuffer.Allocate(mAllocatedBufferSize / 4); #else mEncodedBuffer.Allocate(mAllocatedBufferSize); #endif } else { // Write an empty block index for the symlink file_BlockIndexHeader blkhdr; blkhdr.mMagicValue = htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1); blkhdr.mOtherFileID = box_hton64(0); // not other file ID blkhdr.mEntryIVBase = box_hton64(0); blkhdr.mNumBlocks = box_hton64(0); mData.Write(&blkhdr, sizeof(blkhdr)); } // Ready for reading mData.SetForReading(); // Update stats BackupStoreFile::msStats.mBytesInEncodedFiles += fileSize; // Finally, store the pointer to the recipe, when we know exceptions won't occur mpRecipe = pRecipe; } catch(...) { // Clean up any blank recipe if(pblankRecipe != 0) { delete pblankRecipe; pblankRecipe = 0; } throw; } mpRunStatusProvider = pRunStatusProvider; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::CalculateBlockSizes(int64_t &, int32_t &, int32_t &) // Purpose: Calculates the sizes of blocks in a section of the file // Created: 16/1/04 // // -------------------------------------------------------------------------- void BackupStoreFileEncodeStream::CalculateBlockSizes(int64_t DataSize, int64_t &rNumBlocksOut, int32_t &rBlockSizeOut, int32_t &rLastBlockSizeOut) { // How many blocks, and how big? rBlockSizeOut = BACKUP_FILE_MIN_BLOCK_SIZE / 2; do { rBlockSizeOut *= 2; rNumBlocksOut = (DataSize + rBlockSizeOut - 1) / rBlockSizeOut; } while(rBlockSizeOut < BACKUP_FILE_MAX_BLOCK_SIZE && rNumBlocksOut > BACKUP_FILE_INCREASE_BLOCK_SIZE_AFTER); // Last block size rLastBlockSizeOut = DataSize - ((rNumBlocksOut - 1) * rBlockSizeOut); // Avoid small blocks? if(rLastBlockSizeOut < BACKUP_FILE_AVOID_BLOCKS_LESS_THAN && rNumBlocksOut > 1) { // Add the small bit of data to the last block --rNumBlocksOut; rLastBlockSizeOut += rBlockSizeOut; } // checks! ASSERT((((rNumBlocksOut-1) * rBlockSizeOut) + rLastBlockSizeOut) == DataSize); //TRACE4("CalcBlockSize, sz %lld, num %lld, blocksize %d, last %d\n", DataSize, rNumBlocksOut, (int32_t)rBlockSizeOut, (int32_t)rLastBlockSizeOut); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::Read(void *, int, int) // Purpose: As interface -- generates encoded file data on the fly from the raw file // Created: 8/12/03 // // -------------------------------------------------------------------------- int BackupStoreFileEncodeStream::Read(void *pBuffer, int NBytes, int Timeout) { // Check there's something to do. if(mStatus == Status_Finished) { return 0; } if(mpRunStatusProvider && mpRunStatusProvider->StopRun()) { THROW_EXCEPTION(BackupStoreException, SignalReceived); } int bytesToRead = NBytes; uint8_t *buffer = (uint8_t*)pBuffer; while(bytesToRead > 0 && mStatus != Status_Finished) { if(mStatus == Status_Header || mStatus == Status_BlockListing) { // Header or block listing phase -- send from the buffered stream // Send bytes from the data buffer int b = mData.Read(buffer, bytesToRead, Timeout); bytesToRead -= b; buffer += b; // Check to see if all the data has been used from this stream if(!mData.StreamDataLeft()) { // Yes, move on to next phase (or finish, if there's no file data) if(!mSendData) { mStatus = Status_Finished; } else { // Reset the buffer so it can be used for the next phase mData.Reset(); // Get buffer ready for index? if(mStatus == Status_Header) { // Just finished doing the stream header, create the block index header file_BlockIndexHeader blkhdr; blkhdr.mMagicValue = htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1); ASSERT(mpRecipe != 0); blkhdr.mOtherFileID = box_hton64(mpRecipe->GetOtherFileID()); blkhdr.mNumBlocks = box_hton64(mTotalBlocks); // Generate the IV base Random::Generate(&mEntryIVBase, sizeof(mEntryIVBase)); blkhdr.mEntryIVBase = box_hton64(mEntryIVBase); mData.Write(&blkhdr, sizeof(blkhdr)); } ++mStatus; } } } else if(mStatus == Status_Blocks) { // Block sending phase if(mPositionInCurrentBlock >= mCurrentBlockEncodedSize) { // Next block! ++mCurrentBlock; ++mAbsoluteBlockNumber; if(mCurrentBlock >= mNumBlocks) { // Output extra blocks for this instruction and move forward in file if(mInstructionNumber >= 0) { SkipPreviousBlocksInInstruction(); } // Is there another instruction to go? ++mInstructionNumber; // Skip instructions which don't contain any data while(mInstructionNumber < static_cast(mpRecipe->size()) && (*mpRecipe)[mInstructionNumber].mSpaceBefore == 0) { SkipPreviousBlocksInInstruction(); ++mInstructionNumber; } if(mInstructionNumber >= static_cast(mpRecipe->size())) { // End of blocks, go to next phase ++mStatus; // Set the data to reading so the index can be written mData.SetForReading(); } else { // Get ready for this instruction SetForInstruction(); } } // Can't use 'else' here as SetForInstruction() will change this if(mCurrentBlock < mNumBlocks) { EncodeCurrentBlock(); } } // Send data from the current block (if there's data to send) if(mPositionInCurrentBlock < mCurrentBlockEncodedSize) { // How much data to put in the buffer? int s = mCurrentBlockEncodedSize - mPositionInCurrentBlock; if(s > bytesToRead) s = bytesToRead; // Copy it in ::memcpy(buffer, mEncodedBuffer.mpBuffer + mPositionInCurrentBlock, s); // Update variables bytesToRead -= s; buffer += s; mPositionInCurrentBlock += s; } } else { // Should never get here, as it'd be an invalid status ASSERT(false); } } // Add encoded size to stats BackupStoreFile::msStats.mTotalFileStreamSize += (NBytes - bytesToRead); // Return size of data to caller return NBytes - bytesToRead; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::StorePreviousBlocksInInstruction() // Purpose: Private. Stores the blocks of the old file referenced in the current // instruction into the index and skips over the data in the file // Created: 16/1/04 // // -------------------------------------------------------------------------- void BackupStoreFileEncodeStream::SkipPreviousBlocksInInstruction() { // Check something is necessary if((*mpRecipe)[mInstructionNumber].mpStartBlock == 0 || (*mpRecipe)[mInstructionNumber].mBlocks == 0) { return; } // Index of the first block in old file (being diffed from) int firstIndex = mpRecipe->BlockPtrToIndex((*mpRecipe)[mInstructionNumber].mpStartBlock); int64_t sizeToSkip = 0; for(int32_t b = 0; b < (*mpRecipe)[mInstructionNumber].mBlocks; ++b) { // Update stats BackupStoreFile::msStats.mBytesAlreadyOnServer += (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mSize; // Store the entry StoreBlockIndexEntry(0 - (firstIndex + b), (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mSize, (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mWeakChecksum, (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mStrongChecksum); // Increment the absolute block number -- kept encryption IV in sync ++mAbsoluteBlockNumber; // Add the size of this block to the size to skip sizeToSkip += (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mSize; } // Move forward in the stream mpLogging->Seek(sizeToSkip, IOStream::SeekType_Relative); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::SetForInstruction() // Purpose: Private. Sets the state of the internal variables for the current instruction in the recipe // Created: 16/1/04 // // -------------------------------------------------------------------------- void BackupStoreFileEncodeStream::SetForInstruction() { // Calculate block sizes CalculateBlockSizes((*mpRecipe)[mInstructionNumber].mSpaceBefore, mNumBlocks, mBlockSize, mLastBlockSize); // Set variables mCurrentBlock = 0; mCurrentBlockEncodedSize = 0; mPositionInCurrentBlock = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::EncodeCurrentBlock() // Purpose: Private. Encodes the current block, and writes the block data to the index // Created: 8/12/03 // // -------------------------------------------------------------------------- void BackupStoreFileEncodeStream::EncodeCurrentBlock() { // How big is the block, raw? int blockRawSize = mBlockSize; if(mCurrentBlock == (mNumBlocks - 1)) { blockRawSize = mLastBlockSize; } ASSERT(blockRawSize < mAllocatedBufferSize); // Check file open if(mpLogging == 0) { // File should be open, but isn't. So logical error. THROW_EXCEPTION(BackupStoreException, Internal) } // Read the data in if(!mpLogging->ReadFullBuffer(mpRawBuffer, blockRawSize, 0 /* not interested in size if failure */)) { // TODO: Do something more intelligent, and abort // this upload because the file has changed. THROW_EXCEPTION(BackupStoreException, Temp_FileEncodeStreamDidntReadBuffer) } // Encode it mCurrentBlockEncodedSize = BackupStoreFile::EncodeChunk(mpRawBuffer, blockRawSize, mEncodedBuffer); //TRACE2("Encode: Encoded size of block %d is %d\n", (int32_t)mCurrentBlock, (int32_t)mCurrentBlockEncodedSize); // Create block listing data -- generate checksums RollingChecksum weakChecksum(mpRawBuffer, blockRawSize); MD5Digest strongChecksum; strongChecksum.Add(mpRawBuffer, blockRawSize); strongChecksum.Finish(); // Add entry to the index StoreBlockIndexEntry(mCurrentBlockEncodedSize, blockRawSize, weakChecksum.GetChecksum(), strongChecksum.DigestAsData()); // Set vars to reading this block mPositionInCurrentBlock = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::StoreBlockIndexEntry(int64_t, int32_t, uint32_t, uint8_t *) // Purpose: Private. Adds an entry to the index currently being stored for sending at end of the stream. // Created: 16/1/04 // // -------------------------------------------------------------------------- void BackupStoreFileEncodeStream::StoreBlockIndexEntry(int64_t EncSizeOrBlkIndex, int32_t ClearSize, uint32_t WeakChecksum, uint8_t *pStrongChecksum) { // First, the encrypted section file_BlockIndexEntryEnc entryEnc; entryEnc.mSize = htonl(ClearSize); entryEnc.mWeakChecksum = htonl(WeakChecksum); ::memcpy(entryEnc.mStrongChecksum, pStrongChecksum, sizeof(entryEnc.mStrongChecksum)); // Then the clear section file_BlockIndexEntry entry; entry.mEncodedSize = box_hton64(((uint64_t)EncSizeOrBlkIndex)); // Then encrypt the encryted section // Generate the IV from the block number if(sBlowfishEncryptBlockEntry.GetIVLength() != sizeof(mEntryIVBase)) { THROW_EXCEPTION(BackupStoreException, IVLengthForEncodedBlockSizeDoesntMeetLengthRequirements) } uint64_t iv = mEntryIVBase; iv += mAbsoluteBlockNumber; // Convert to network byte order before encrypting with it, so that restores work on // platforms with different endiannesses. iv = box_hton64(iv); sBlowfishEncryptBlockEntry.SetIV(&iv); // Encode the data int encodedSize = sBlowfishEncryptBlockEntry.TransformBlock(entry.mEnEnc, sizeof(entry.mEnEnc), &entryEnc, sizeof(entryEnc)); if(encodedSize != sizeof(entry.mEnEnc)) { THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength) } // Save to data block for sending at the end of the stream mData.Write(&entry, sizeof(entry)); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::Write(const void *, int) // Purpose: As interface. Exceptions. // Created: 8/12/03 // // -------------------------------------------------------------------------- void BackupStoreFileEncodeStream::Write(const void *pBuffer, int NBytes) { THROW_EXCEPTION(BackupStoreException, CantWriteToEncodedFileStream) } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::StreamDataLeft() // Purpose: As interface -- end of stream reached? // Created: 8/12/03 // // -------------------------------------------------------------------------- bool BackupStoreFileEncodeStream::StreamDataLeft() { return (mStatus != Status_Finished); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::StreamClosed() // Purpose: As interface // Created: 8/12/03 // // -------------------------------------------------------------------------- bool BackupStoreFileEncodeStream::StreamClosed() { return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::Recipe::Recipe(BackupStoreFileCreation::BlocksAvailableEntry *, int64_t) // Purpose: Constructor. Takes ownership of the block index, and will delete it when it's deleted // Created: 15/1/04 // // -------------------------------------------------------------------------- BackupStoreFileEncodeStream::Recipe::Recipe(BackupStoreFileCreation::BlocksAvailableEntry *pBlockIndex, int64_t NumBlocksInIndex, int64_t OtherFileID) : mpBlockIndex(pBlockIndex), mNumBlocksInIndex(NumBlocksInIndex), mOtherFileID(OtherFileID) { ASSERT((mpBlockIndex == 0) || (NumBlocksInIndex != 0)) } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFileEncodeStream::Recipe::~Recipe() // Purpose: Destructor // Created: 15/1/04 // // -------------------------------------------------------------------------- BackupStoreFileEncodeStream::Recipe::~Recipe() { // Free the block index, if there is one if(mpBlockIndex != 0) { ::free(mpBlockIndex); } } boxbackup/lib/backupclient/BackupDaemonConfigVerify.h0000664000175000017500000000075310347400657023554 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupDaemonConfigVerify.h // Purpose: Configuration file definition for bbackupd // Created: 2003/10/10 // // -------------------------------------------------------------------------- #ifndef BACKUPDAEMONCONFIGVERIFY__H #define BACKUPDAEMONCONFIGVERIFY__H #include "Configuration.h" extern const ConfigurationVerify BackupDaemonConfigVerify; #endif // BACKUPDAEMONCONFIGVERIFY__H boxbackup/lib/backupclient/BackupClientFileAttributes.cpp0000664000175000017500000010571611445744272024467 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientFileAttributes.cpp // Purpose: Storage of file attributes // Created: 2003/10/07 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_XATTR_H #include #include #endif #include #include "BackupClientFileAttributes.h" #include "CommonException.h" #include "FileModificationTime.h" #include "BoxTimeToUnix.h" #include "BackupStoreException.h" #include "CipherContext.h" #include "CipherBlowfish.h" #include "MD5Digest.h" #include "MemLeakFindOn.h" // set packing to one byte #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "BeginStructPackForWire.h" #else BEGIN_STRUCTURE_PACKING_FOR_WIRE #endif #define ATTRIBUTETYPE_GENERIC_UNIX 1 #define ATTRIBUTE_ENCODING_BLOWFISH 2 typedef struct { int32_t AttributeType; u_int32_t UID; u_int32_t GID; u_int64_t ModificationTime; u_int64_t AttrModificationTime; u_int32_t UserDefinedFlags; u_int32_t FileGenerationNumber; u_int16_t Mode; // Symbolic link filename may follow // Extended attribute (xattr) information may follow, format is: // u_int32_t Size of extended attribute block (excluding this word) // For each of NumberOfAttributes (sorted by AttributeName): // u_int16_t AttributeNameLength // char AttributeName[AttributeNameLength] // u_int32_t AttributeValueLength // unsigned char AttributeValue[AttributeValueLength] // AttributeName is 0 terminated, AttributeValue is not (and may be binary data) } attr_StreamFormat; // This has wire packing so it's compatible across platforms // Use wider than necessary sizes, just to be careful. typedef struct { int32_t uid, gid, mode; #ifdef WIN32 int64_t fileCreationTime; #endif } attributeHashData; // Use default packing #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "EndStructPackForWire.h" #else END_STRUCTURE_PACKING_FOR_WIRE #endif #define MAX_ATTRIBUTE_HASH_SECRET_LENGTH 256 // Hide private static variables from the rest of the world // -- don't put them as static class variables to avoid openssl/evp.h being // included all over the project. namespace { CipherContext sBlowfishEncrypt; CipherContext sBlowfishDecrypt; uint8_t sAttributeHashSecret[MAX_ATTRIBUTE_HASH_SECRET_LENGTH]; int sAttributeHashSecretLength = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::BackupClientFileAttributes() // Purpose: Default constructor // Created: 2003/10/07 // // -------------------------------------------------------------------------- BackupClientFileAttributes::BackupClientFileAttributes() : mpClearAttributes(0) { ASSERT(sizeof(u_int64_t) == sizeof(box_time_t)); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::BackupClientFileAttributes(const BackupClientFileAttributes &) // Purpose: Copy constructor // Created: 2003/10/07 // // -------------------------------------------------------------------------- BackupClientFileAttributes::BackupClientFileAttributes(const BackupClientFileAttributes &rToCopy) : StreamableMemBlock(rToCopy), // base class does the hard work mpClearAttributes(0) { } BackupClientFileAttributes::BackupClientFileAttributes(const StreamableMemBlock &rToCopy) : StreamableMemBlock(rToCopy), // base class does the hard work mpClearAttributes(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::~BackupClientFileAttributes() // Purpose: Destructor // Created: 2003/10/07 // // -------------------------------------------------------------------------- BackupClientFileAttributes::~BackupClientFileAttributes() { if(mpClearAttributes) { delete mpClearAttributes; mpClearAttributes = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes &operator=(const BackupClientFileAttributes &) // Purpose: Assignment operator // Created: 2003/10/07 // // -------------------------------------------------------------------------- BackupClientFileAttributes &BackupClientFileAttributes::operator=(const BackupClientFileAttributes &rAttr) { StreamableMemBlock::Set(rAttr); RemoveClear(); // make sure no decrypted version held return *this; } // Assume users play nice BackupClientFileAttributes &BackupClientFileAttributes::operator=(const StreamableMemBlock &rAttr) { StreamableMemBlock::Set(rAttr); RemoveClear(); // make sure no decrypted version held return *this; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::operator==(const BackupClientFileAttributes &) // Purpose: Comparison operator // Created: 2003/10/09 // // -------------------------------------------------------------------------- bool BackupClientFileAttributes::operator==(const BackupClientFileAttributes &rAttr) const { EnsureClearAvailable(); rAttr.EnsureClearAvailable(); return mpClearAttributes->operator==(*rAttr.mpClearAttributes); } // Too dangerous to allow -- put the two names the wrong way round, and it compares encrypted data. /*bool BackupClientFileAttributes::operator==(const StreamableMemBlock &rAttr) const { StreamableMemBlock *pDecoded = 0; try { EnsureClearAvailable(); StreamableMemBlock *pDecoded = MakeClear(rAttr); // Compare using clear version bool compared = mpClearAttributes->operator==(rAttr); // Delete temporary delete pDecoded; return compared; } catch(...) { delete pDecoded; throw; } }*/ // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::Compare(const BackupClientFileAttributes &, bool) // Purpose: Compare, optionally ignoring the attribute // modification time and/or modification time, and some // data which is irrelevant in practise (eg file // generation number) // Created: 10/12/03 // // -------------------------------------------------------------------------- bool BackupClientFileAttributes::Compare(const BackupClientFileAttributes &rAttr, bool IgnoreAttrModTime, bool IgnoreModTime) const { EnsureClearAvailable(); rAttr.EnsureClearAvailable(); // Check sizes are the same, as a first check if(mpClearAttributes->GetSize() != rAttr.mpClearAttributes->GetSize()) { BOX_TRACE("Attribute Compare: Attributes objects are " "different sizes, cannot compare them: local " << mpClearAttributes->GetSize() << " bytes, remote " << rAttr.mpClearAttributes->GetSize() << " bytes"); return false; } // Then check the elements of the two things // Bytes are checked in network order, but this doesn't matter as we're only checking for equality. attr_StreamFormat *a1 = (attr_StreamFormat*)mpClearAttributes->GetBuffer(); attr_StreamFormat *a2 = (attr_StreamFormat*)rAttr.mpClearAttributes->GetBuffer(); #define COMPARE(attribute, message) \ if (a1->attribute != a2->attribute) \ { \ BOX_TRACE("Attribute Compare: " << message << " differ: " \ "local " << ntoh(a1->attribute) << ", " \ "remote " << ntoh(a2->attribute)); \ return false; \ } COMPARE(AttributeType, "Attribute types"); COMPARE(UID, "UIDs"); COMPARE(GID, "GIDs"); COMPARE(UserDefinedFlags, "User-defined flags"); COMPARE(Mode, "Modes"); if(!IgnoreModTime) { uint64_t t1 = box_ntoh64(a1->ModificationTime); uint64_t t2 = box_ntoh64(a2->ModificationTime); time_t s1 = BoxTimeToSeconds(t1); time_t s2 = BoxTimeToSeconds(t2); if(s1 != s2) { BOX_TRACE("Attribute Compare: File modification " "times differ: local " << FormatTime(t1, true) << " (" << s1 << "), " "remote " << FormatTime(t2, true) << " (" << s2 << ")"); return false; } } if(!IgnoreAttrModTime) { uint64_t t1 = box_ntoh64(a1->AttrModificationTime); uint64_t t2 = box_ntoh64(a2->AttrModificationTime); time_t s1 = BoxTimeToSeconds(t1); time_t s2 = BoxTimeToSeconds(t2); if(s1 != s2) { BOX_TRACE("Attribute Compare: Attribute modification " "times differ: local " << FormatTime(t1, true) << " (" << s1 << "), " "remote " << FormatTime(t2, true) << " (" << s2 << ")"); return false; } } // Check symlink string? unsigned int size = mpClearAttributes->GetSize(); if(size > sizeof(attr_StreamFormat)) { // Symlink strings don't match. This also compares xattrs int datalen = size - sizeof(attr_StreamFormat); if(::memcmp(a1 + 1, a2 + 1, datalen) != 0) { std::string s1((char *)(a1 + 1), datalen); std::string s2((char *)(a2 + 1), datalen); BOX_TRACE("Attribute Compare: Symbolic link target " "or extended attributes differ: " "local " << PrintEscapedBinaryData(s1) << ", " "remote " << PrintEscapedBinaryData(s2)); return false; } } // Passes all test, must be OK return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::ReadAttributes( // const char *Filename, bool ZeroModificationTimes, // box_time_t *pModTime, box_time_t *pAttrModTime, // int64_t *pFileSize, InodeRefType *pInodeNumber, // bool *pHasMultipleLinks) // Purpose: Read the attributes of the file, and store them // ready for streaming. Optionally retrieve the // modification time and attribute modification time. // Created: 2003/10/07 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::ReadAttributes(const char *Filename, bool ZeroModificationTimes, box_time_t *pModTime, box_time_t *pAttrModTime, int64_t *pFileSize, InodeRefType *pInodeNumber, bool *pHasMultipleLinks) { StreamableMemBlock *pnewAttr = 0; try { EMU_STRUCT_STAT st; if(EMU_LSTAT(Filename, &st) != 0) { BOX_LOG_SYS_ERROR("Failed to stat file: '" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileError) } // Modification times etc if(pModTime) {*pModTime = FileModificationTime(st);} if(pAttrModTime) {*pAttrModTime = FileAttrModificationTime(st);} if(pFileSize) {*pFileSize = st.st_size;} if(pInodeNumber) {*pInodeNumber = st.st_ino;} if(pHasMultipleLinks) {*pHasMultipleLinks = (st.st_nlink > 1);} pnewAttr = new StreamableMemBlock; FillAttributes(*pnewAttr, Filename, st, ZeroModificationTimes); #ifndef WIN32 // Is it a link? if((st.st_mode & S_IFMT) == S_IFLNK) { FillAttributesLink(*pnewAttr, Filename, st); } #endif FillExtendedAttr(*pnewAttr, Filename); #ifdef WIN32 //this is to catch those problems with invalid time stamps stored... //need to find out the reason why - but also a catch as well. attr_StreamFormat *pattr = (attr_StreamFormat*)pnewAttr->GetBuffer(); ASSERT(pattr != 0); // __time64_t winTime = BoxTimeToSeconds( // pnewAttr->ModificationTime); u_int64_t modTime = box_ntoh64(pattr->ModificationTime); box_time_t modSecs = BoxTimeToSeconds(modTime); __time64_t winTime = modSecs; // _MAX__TIME64_T doesn't seem to be defined, but the code below // will throw an assertion failure if we exceed it :-) // Microsoft says dates up to the year 3000 are valid, which // is a bit more than 15 * 2^32. Even that doesn't seem // to be true (still aborts), but it can at least hold 2^32. if (winTime >= 0x100000000LL || _gmtime64(&winTime) == 0) { BOX_ERROR("Invalid Modification Time caught for " "file: '" << Filename << "'"); pattr->ModificationTime = 0; } modTime = box_ntoh64(pattr->AttrModificationTime); modSecs = BoxTimeToSeconds(modTime); winTime = modSecs; if (winTime > 0x100000000LL || _gmtime64(&winTime) == 0) { BOX_ERROR("Invalid Attribute Modification Time " "caught for file: '" << Filename << "'"); pattr->AttrModificationTime = 0; } #endif // Attributes ready. Encrypt into this block EncryptAttr(*pnewAttr); // Store the new attributes RemoveClear(); mpClearAttributes = pnewAttr; pnewAttr = 0; } catch(...) { // clean up delete pnewAttr; pnewAttr = 0; throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::ReadAttributesLink() // Purpose: Private function, handles standard attributes for all objects // Created: 2003/10/07 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::FillAttributes(StreamableMemBlock &outputBlock, const char *Filename, EMU_STRUCT_STAT &st, bool ZeroModificationTimes) { outputBlock.ResizeBlock(sizeof(attr_StreamFormat)); attr_StreamFormat *pattr = (attr_StreamFormat*)outputBlock.GetBuffer(); ASSERT(pattr != 0); // Fill in the entries pattr->AttributeType = htonl(ATTRIBUTETYPE_GENERIC_UNIX); pattr->UID = htonl(st.st_uid); pattr->GID = htonl(st.st_gid); if(ZeroModificationTimes) { pattr->ModificationTime = 0; pattr->AttrModificationTime = 0; } else { pattr->ModificationTime = box_hton64(FileModificationTime(st)); pattr->AttrModificationTime = box_hton64(FileAttrModificationTime(st)); } pattr->Mode = htons(st.st_mode); #ifndef HAVE_STRUCT_STAT_ST_FLAGS pattr->UserDefinedFlags = 0; pattr->FileGenerationNumber = 0; #else pattr->UserDefinedFlags = htonl(st.st_flags); pattr->FileGenerationNumber = htonl(st.st_gen); #endif } #ifndef WIN32 // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::ReadAttributesLink() // Purpose: Private function, handles the case where a symbolic link is needed // Created: 2003/10/07 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::FillAttributesLink(StreamableMemBlock &outputBlock, const char *Filename, struct stat &st) { // Make sure we're only called for symbolic links ASSERT((st.st_mode & S_IFMT) == S_IFLNK); // Get the filename the link is linked to char linkedTo[PATH_MAX+4]; int linkedToSize = ::readlink(Filename, linkedTo, PATH_MAX); if(linkedToSize == -1) { BOX_LOG_SYS_ERROR("Failed to readlink '" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileError); } int oldSize = outputBlock.GetSize(); outputBlock.ResizeBlock(oldSize+linkedToSize+1); char* buffer = static_cast(outputBlock.GetBuffer()); // Add the path name for the symbolic link, and add 0 termination std::memcpy(buffer+oldSize, linkedTo, linkedToSize); buffer[oldSize+linkedToSize] = '\0'; } #endif // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::ReadExtendedAttr(const char *, unsigned char**) // Purpose: Private function, read the extended attributes of the file into the block // Created: 2005/06/12 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::FillExtendedAttr(StreamableMemBlock &outputBlock, const char *Filename) { #ifdef HAVE_SYS_XATTR_H int listBufferSize = 10000; char* list = new char[listBufferSize]; try { // This returns an unordered list of attribute names, each 0 terminated, // concatenated together int listSize = ::llistxattr(Filename, list, listBufferSize); if(listSize>listBufferSize) { delete[] list, list = NULL; list = new char[listSize]; listSize = ::llistxattr(Filename, list, listSize); } if(listSize>0) { // Extract list of attribute names so we can sort them std::vector attrKeys; for(int i = 0; i500 ? (xattrSize+listSize)*2 : 1000; outputBlock.ResizeBlock(xattrBufferSize); unsigned char* buffer = static_cast(outputBlock.GetBuffer()); // Leave space for attr block size later int xattrBlockSizeOffset = xattrSize; xattrSize += sizeof(u_int32_t); // Loop for each attribute for(std::vector::const_iterator attrKeyI = attrKeys.begin(); attrKeyI!=attrKeys.end(); ++attrKeyI) { std::string attrKey(*attrKeyI); if(xattrSize+sizeof(u_int16_t)+attrKey.size()+1+sizeof(u_int32_t)>static_cast(xattrBufferSize)) { xattrBufferSize = (xattrBufferSize+sizeof(u_int16_t)+attrKey.size()+1+sizeof(u_int32_t))*2; outputBlock.ResizeBlock(xattrBufferSize); buffer = static_cast(outputBlock.GetBuffer()); } // Store length and text for attibute name u_int16_t keyLength = htons(attrKey.size()+1); std::memcpy(buffer+xattrSize, &keyLength, sizeof(u_int16_t)); xattrSize += sizeof(u_int16_t); std::memcpy(buffer+xattrSize, attrKey.c_str(), attrKey.size()+1); xattrSize += attrKey.size()+1; // Leave space for value size int valueSizeOffset = xattrSize; xattrSize += sizeof(u_int32_t); // Find size of attribute (must call with buffer and length 0 on some platforms, // as -1 is returned if the data doesn't fit.) int valueSize = ::lgetxattr(Filename, attrKey.c_str(), 0, 0); if(valueSize<0) { BOX_LOG_SYS_ERROR("Failed to get " "extended attributes size " "for '" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileError); } // Resize block, if needed if(xattrSize+valueSize>xattrBufferSize) { xattrBufferSize = (xattrBufferSize+valueSize)*2; outputBlock.ResizeBlock(xattrBufferSize); buffer = static_cast(outputBlock.GetBuffer()); } // This gets the attribute value (may be text or binary), no termination valueSize = ::lgetxattr(Filename, attrKey.c_str(), buffer+xattrSize, xattrBufferSize-xattrSize); if(valueSize<0) { BOX_LOG_SYS_ERROR("Failed to get " "extended attributes for " "'" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileError); } xattrSize += valueSize; // Fill in value size u_int32_t valueLength = htonl(valueSize); std::memcpy(buffer+valueSizeOffset, &valueLength, sizeof(u_int32_t)); } // Fill in attribute block size u_int32_t xattrBlockLength = htonl(xattrSize-xattrBlockSizeOffset-sizeof(u_int32_t)); std::memcpy(buffer+xattrBlockSizeOffset, &xattrBlockLength, sizeof(u_int32_t)); outputBlock.ResizeBlock(xattrSize); } else if(listSize<0) { if(errno == EOPNOTSUPP || errno == EACCES) { // fail silently } else if(errno == ERANGE) { BOX_ERROR("Failed to list extended " "attributes of '" << Filename << "': " "buffer too small, not backed up"); } else { BOX_LOG_SYS_ERROR("Failed to list extended " "attributes of '" << Filename << "', " "not backed up"); THROW_EXCEPTION(CommonException, OSFileError); } } } catch(...) { delete[] list; throw; } delete[] list; #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::GetModificationTimes() // Purpose: Returns the modification time embedded in the // attributes. // Created: 2010/02/24 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::GetModificationTimes( box_time_t *pModificationTime, box_time_t *pAttrModificationTime) const { // Got something loaded if(GetSize() <= 0) { THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); } // Make sure there are clear attributes to use EnsureClearAvailable(); ASSERT(mpClearAttributes != 0); // Check if the decrypted attributes are small enough, and the type of attributes stored if(mpClearAttributes->GetSize() < (int)sizeof(int32_t)) { THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood); } int32_t *type = (int32_t*)mpClearAttributes->GetBuffer(); ASSERT(type != 0); if(ntohl(*type) != ATTRIBUTETYPE_GENERIC_UNIX) { // Don't know what to do with these THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood); } // Check there is enough space for an attributes block if(mpClearAttributes->GetSize() < (int)sizeof(attr_StreamFormat)) { // Too small THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); } // Get pointer to structure attr_StreamFormat *pattr = (attr_StreamFormat*)mpClearAttributes->GetBuffer(); if(pModificationTime) { *pModificationTime = box_ntoh64(pattr->ModificationTime); } if(pAttrModificationTime) { *pAttrModificationTime = box_ntoh64(pattr->AttrModificationTime); } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::WriteAttributes(const char *) // Purpose: Apply the stored attributes to the file // Created: 2003/10/07 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::WriteAttributes(const char *Filename, bool MakeUserWritable) const { // Got something loaded if(GetSize() <= 0) { THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); } // Make sure there are clear attributes to use EnsureClearAvailable(); ASSERT(mpClearAttributes != 0); // Check if the decrypted attributes are small enough, and the type of attributes stored if(mpClearAttributes->GetSize() < (int)sizeof(int32_t)) { THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood); } int32_t *type = (int32_t*)mpClearAttributes->GetBuffer(); ASSERT(type != 0); if(ntohl(*type) != ATTRIBUTETYPE_GENERIC_UNIX) { // Don't know what to do with these THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood); } // Check there is enough space for an attributes block if(mpClearAttributes->GetSize() < (int)sizeof(attr_StreamFormat)) { // Too small THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); } // Get pointer to structure attr_StreamFormat *pattr = (attr_StreamFormat*)mpClearAttributes->GetBuffer(); int xattrOffset = sizeof(attr_StreamFormat); // is it a symlink? int16_t mode = ntohs(pattr->Mode); if((mode & S_IFMT) == S_IFLNK) { // Check things are sensible if(mpClearAttributes->GetSize() < (int)sizeof(attr_StreamFormat) + 1) { // Too small THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); } #ifdef WIN32 BOX_WARNING("Cannot create symbolic links on Windows: '" << Filename << "'"); #else // Make a symlink, first deleting anything in the way ::unlink(Filename); if(::symlink((char*)(pattr + 1), Filename) != 0) { BOX_LOG_SYS_ERROR("Failed to symlink '" << Filename << "' to '" << (char*)(pattr + 1) << "'"); THROW_EXCEPTION(CommonException, OSFileError) } #endif xattrOffset += std::strlen(reinterpret_cast(pattr+1))+1; } // If working as root, set user IDs if(::geteuid() == 0) { #ifndef HAVE_LCHOWN // only if not a link, can't set their owner on this platform if((mode & S_IFMT) != S_IFLNK) { // Not a link, use normal chown if(::chown(Filename, ntohl(pattr->UID), ntohl(pattr->GID)) != 0) { BOX_LOG_SYS_ERROR("Failed to change " "owner of file " "'" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileError) } } #else // use the version which sets things on symlinks if(::lchown(Filename, ntohl(pattr->UID), ntohl(pattr->GID)) != 0) { BOX_LOG_SYS_ERROR("Failed to change owner of " "symbolic link '" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileError) } #endif } if(static_cast(xattrOffset+sizeof(u_int32_t))<=mpClearAttributes->GetSize()) { WriteExtendedAttr(Filename, xattrOffset); } // Stop now if symlink, because otherwise it'll just be applied to the target if((mode & S_IFMT) == S_IFLNK) { return; } // Set modification time? box_time_t modtime = box_ntoh64(pattr->ModificationTime); if(modtime != 0) { // Work out times as timevals struct timeval times[2]; #ifdef WIN32 BoxTimeToTimeval(box_ntoh64(pattr->ModificationTime), times[1]); BoxTimeToTimeval(box_ntoh64(pattr->AttrModificationTime), times[0]); // Because stat() returns the creation time in the ctime // field under Windows, and this gets saved in the // AttrModificationTime field of the serialised attributes, // we subvert the first parameter of emu_utimes() to allow // it to be reset to the right value on the restored file. #else BoxTimeToTimeval(modtime, times[1]); // Copy access time as well, why not, got to set it to something times[0] = times[1]; // Attr modification time will be changed anyway, // nothing that can be done about it #endif // Try to apply if(::utimes(Filename, times) != 0) { BOX_LOG_SYS_WARNING("Failed to change times of " "file '" << Filename << "' to ctime=" << BOX_FORMAT_TIMESPEC(times[0]) << ", mtime=" << BOX_FORMAT_TIMESPEC(times[1])); } } if (MakeUserWritable) { mode |= S_IRWXU; } // Apply everything else... (allowable mode flags only) // Mode must be done last (think setuid) if(::chmod(Filename, mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX)) != 0) { BOX_LOG_SYS_ERROR("Failed to change permissions of file " "'" << Filename << "'"); THROW_EXCEPTION(CommonException, OSFileError) } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::IsSymLink() // Purpose: Do these attributes represent a symbolic link? // Created: 2003/10/07 // // -------------------------------------------------------------------------- bool BackupClientFileAttributes::IsSymLink() const { EnsureClearAvailable(); // Got the right kind of thing? if(mpClearAttributes->GetSize() < (int)sizeof(int32_t)) { THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); } // Get the type of attributes stored int32_t *type = (int32_t*)mpClearAttributes->GetBuffer(); ASSERT(type != 0); if(ntohl(*type) == ATTRIBUTETYPE_GENERIC_UNIX && mpClearAttributes->GetSize() > (int)sizeof(attr_StreamFormat)) { // Check link attr_StreamFormat *pattr = (attr_StreamFormat*)mpClearAttributes->GetBuffer(); return ((ntohs(pattr->Mode)) & S_IFMT) == S_IFLNK; } return false; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::RemoveClear() // Purpose: Private. Deletes any clear version of the attributes that may be held // Created: 3/12/03 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::RemoveClear() const { if(mpClearAttributes) { delete mpClearAttributes; } mpClearAttributes = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::EnsureClearAvailable() // Purpose: Private. Makes sure the clear version is available // Created: 3/12/03 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::EnsureClearAvailable() const { if(mpClearAttributes == 0) { mpClearAttributes = MakeClear(*this); } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::WriteExtendedAttr(const char *Filename, int xattrOffset) // Purpose: Private function, apply the stored extended attributes to the file // Created: 2005/06/13 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::WriteExtendedAttr(const char *Filename, int xattrOffset) const { #ifdef HAVE_SYS_XATTR_H const char* buffer = static_cast(mpClearAttributes->GetBuffer()); u_int32_t xattrBlockLength = 0; std::memcpy(&xattrBlockLength, buffer+xattrOffset, sizeof(u_int32_t)); int xattrBlockSize = ntohl(xattrBlockLength); xattrOffset += sizeof(u_int32_t); int xattrEnd = xattrOffset+xattrBlockSize; if(xattrEnd>mpClearAttributes->GetSize()) { // Too small THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); } while(xattrOffsetGetBuffer(), maxDecryptedSize, encBlock + 1 + ivSize, rEncrypted.GetSize() - (ivSize + 1)); // Resize block to fit pdecrypted->ResizeBlock(decryptedSize); } catch(...) { delete pdecrypted; pdecrypted = 0; } return pdecrypted; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::SetBlowfishKey(const void *, int) // Purpose: Static. Sets the key to use for encryption and decryption. // Created: 3/12/03 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::SetBlowfishKey(const void *pKey, int KeyLength) { // IVs set later sBlowfishEncrypt.Reset(); sBlowfishEncrypt.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength)); sBlowfishDecrypt.Reset(); sBlowfishDecrypt.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength)); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::EncryptAttr(const StreamableMemBlock &) // Purpose: Private. Encrypt the given attributes into this block. // Created: 3/12/03 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::EncryptAttr(const StreamableMemBlock &rToEncrypt) { // Free any existing block FreeBlock(); // Work out the maximum amount of space we need int maxEncryptedSize = sBlowfishEncrypt.MaxOutSizeForInBufferSize(rToEncrypt.GetSize()); // And the size of the IV int ivSize = sBlowfishEncrypt.GetIVLength(); // Allocate this space AllocateBlock(maxEncryptedSize + ivSize + 1); // Store the encoding byte uint8_t *block = (uint8_t*)GetBuffer(); block[0] = ATTRIBUTE_ENCODING_BLOWFISH; // Generate and store an IV for this attribute block int ivSize2 = 0; const void *iv = sBlowfishEncrypt.SetRandomIV(ivSize2); ASSERT(ivSize == ivSize2); // Copy into the encrypted block ::memcpy(block + 1, iv, ivSize); // Do the transform int encrytedSize = sBlowfishEncrypt.TransformBlock(block + 1 + ivSize, maxEncryptedSize, rToEncrypt.GetBuffer(), rToEncrypt.GetSize()); // Resize this block ResizeBlock(encrytedSize + ivSize + 1); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::SetAttributeHashSecret(const void *, int) // Purpose: Set the secret for the filename attribute hash // Created: 25/4/04 // // -------------------------------------------------------------------------- void BackupClientFileAttributes::SetAttributeHashSecret(const void *pSecret, int SecretLength) { if(SecretLength > (int)sizeof(sAttributeHashSecret)) { SecretLength = sizeof(sAttributeHashSecret); } if(SecretLength < 0) { THROW_EXCEPTION(BackupStoreException, Internal) } // Copy ::memcpy(sAttributeHashSecret, pSecret, SecretLength); sAttributeHashSecretLength = SecretLength; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientFileAttributes::GenerateAttributeHash( // struct stat &, const std::string &, // const std::string &) // Purpose: Generate a 64 bit hash from the attributes, used to // detect changes. Include filename in the hash, so // that it changes from one file to another, so don't // reveal identical attributes. // Created: 25/4/04 // // -------------------------------------------------------------------------- uint64_t BackupClientFileAttributes::GenerateAttributeHash(EMU_STRUCT_STAT &st, const std::string &filename, const std::string &leafname) { if(sAttributeHashSecretLength == 0) { THROW_EXCEPTION(BackupStoreException, AttributeHashSecretNotSet) } // Assemble stuff we're interested in attributeHashData hashData; memset(&hashData, 0, sizeof(hashData)); // Use network byte order and large sizes to be cross platform hashData.uid = htonl(st.st_uid); hashData.gid = htonl(st.st_gid); hashData.mode = htonl(st.st_mode); #ifdef WIN32 // On Windows, the "file attribute modification time" is the // file creation time, and we want to back this up, restore // it and compare it. // // On other platforms, it's not very important and can't // reliably be set to anything other than the current time. hashData.fileCreationTime = box_hton64(st.st_ctime); #endif StreamableMemBlock xattr; FillExtendedAttr(xattr, filename.c_str()); // Create a MD5 hash of the data, filename, and secret MD5Digest digest; digest.Add(&hashData, sizeof(hashData)); digest.Add(xattr.GetBuffer(), xattr.GetSize()); digest.Add(leafname.c_str(), leafname.size()); digest.Add(sAttributeHashSecret, sAttributeHashSecretLength); digest.Finish(); // Return the first 64 bits of the hash uint64_t result = *((uint64_t *)(digest.DigestAsData())); return result; } boxbackup/lib/backupclient/BackupClientCryptoKeys.h0000664000175000017500000000425211053244601023275 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientCryptoKeys.h // Purpose: Format of crypto keys file, and function for setting everything up // Created: 1/12/03 // // -------------------------------------------------------------------------- #ifndef BACKUPCLIENTCRYTOKEYS__H #define BACKUPCLIENTCRYTOKEYS__H // All keys are the maximum size that Blowfish supports. Since only the // setup time is affected by key length (encryption same speed whatever) // there is no disadvantage to using long keys as they are never // transmitted and are static over long periods of time. // All sizes in bytes. Some gaps deliberately left in the used material. // How long the key material file is expected to be #define BACKUPCRYPTOKEYS_FILE_SIZE 1024 // key for encrypting filenames (448 bits) #define BACKUPCRYPTOKEYS_FILENAME_KEY_START 0 #define BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH 56 #define BACKUPCRYPTOKEYS_FILENAME_IV_START (0 + BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH) #define BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH 8 // key for encrypting attributes (448 bits) #define BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START (BACKUPCRYPTOKEYS_FILENAME_KEY_START+64) #define BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH 56 // Blowfish key for encrypting file data (448 bits (max blowfish key length)) #define BACKUPCRYPTOKEYS_FILE_KEY_START (BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START+64) #define BACKUPCRYPTOKEYS_FILE_KEY_LENGTH 56 // key for encrypting file block index entries #define BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START (BACKUPCRYPTOKEYS_FILE_KEY_START+64) #define BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_LENGTH 56 // Secret for hashing attributes #define BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START (BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START+64) #define BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_LENGTH 128 // AES key for encrypting file data (256 bits (max AES key length)) #define BACKUPCRYPTOKEYS_FILE_AES_KEY_START (BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START+128) #define BACKUPCRYPTOKEYS_FILE_AES_KEY_LENGTH 32 void BackupClientCryptoKeys_Setup(const std::string& rKeyMaterialFilename); #endif // BACKUPCLIENTCRYTOKEYS__H boxbackup/lib/backupclient/BackupStoreFilename.cpp0000664000175000017500000001752311163676334023135 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFilename.cpp // Purpose: Filename for the backup store // Created: 2003/08/26 // // -------------------------------------------------------------------------- #include "Box.h" #include "BackupStoreFilename.h" #include "Protocol.h" #include "BackupStoreException.h" #include "IOStream.h" #include "Guards.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::BackupStoreFilename() // Purpose: Default constructor -- creates an invalid filename // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilename::BackupStoreFilename() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::BackupStoreFilename(const BackupStoreFilename &) // Purpose: Copy constructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilename::BackupStoreFilename(const BackupStoreFilename &rToCopy) : mEncryptedName(rToCopy.mEncryptedName) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::~BackupStoreFilename() // Purpose: Destructor // Created: 2003/08/26 // // -------------------------------------------------------------------------- BackupStoreFilename::~BackupStoreFilename() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::CheckValid(bool) // Purpose: Checks the encoded filename for validity // Created: 2003/08/26 // // -------------------------------------------------------------------------- bool BackupStoreFilename::CheckValid(bool ExceptionIfInvalid) const { bool ok = true; if(mEncryptedName.size() < 2) { // Isn't long enough to have a header ok = false; } else { // Check size is consistent unsigned int dsize = BACKUPSTOREFILENAME_GET_SIZE(this->mEncryptedName); if(dsize != mEncryptedName.size()) { ok = false; } // And encoding is an accepted value unsigned int encoding = BACKUPSTOREFILENAME_GET_ENCODING(this->mEncryptedName); if(encoding < Encoding_Min || encoding > Encoding_Max) { ok = false; } } // Exception? if(!ok && ExceptionIfInvalid) { THROW_EXCEPTION(BackupStoreException, InvalidBackupStoreFilename) } return ok; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::ReadFromProtocol(Protocol &) // Purpose: Reads the filename from the protocol object // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilename::ReadFromProtocol(Protocol &rProtocol) { // Read the header char hdr[2]; rProtocol.Read(hdr, 2); // How big is it? int dsize = BACKUPSTOREFILENAME_GET_SIZE(hdr); // Fetch rest of data, relying on the Protocol to error on stupidly large sizes for us std::string data; rProtocol.Read(data, dsize - 2); // assign to this string, storing the header and the extra data mEncryptedName.assign(hdr, 2); mEncryptedName.append(data.c_str(), data.size()); // Check it CheckValid(); // Alert derived classes EncodedFilenameChanged(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::WriteToProtocol(Protocol &) // Purpose: Writes the filename to the protocol object // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilename::WriteToProtocol(Protocol &rProtocol) const { CheckValid(); rProtocol.Write(mEncryptedName.c_str(), mEncryptedName.size()); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::ReadFromStream(IOStream &) // Purpose: Reads the filename from a stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilename::ReadFromStream(IOStream &rStream, int Timeout) { // Read the header char hdr[2]; if(!rStream.ReadFullBuffer(hdr, 2, 0 /* not interested in bytes read if this fails */, Timeout)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // How big is it? unsigned int dsize = BACKUPSTOREFILENAME_GET_SIZE(hdr); // Assume most filenames are small char buf[256]; if(dsize < sizeof(buf)) { // Fetch rest of data, relying on the Protocol to error on stupidly large sizes for us if(!rStream.ReadFullBuffer(buf + 2, dsize - 2, 0 /* not interested in bytes read if this fails */, Timeout)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Copy in header buf[0] = hdr[0]; buf[1] = hdr[1]; // assign to this string, storing the header and the extra data mEncryptedName.assign(buf, dsize); } else { // Block of memory to hold it MemoryBlockGuard dataB(dsize+2); char *data = dataB; // Fetch rest of data, relying on the Protocol to error on stupidly large sizes for us if(!rStream.ReadFullBuffer(data + 2, dsize - 2, 0 /* not interested in bytes read if this fails */, Timeout)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Copy in header data[0] = hdr[0]; data[1] = hdr[1]; // assign to this string, storing the header and the extra data mEncryptedName.assign(data, dsize); } // Check it CheckValid(); // Alert derived classes EncodedFilenameChanged(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::WriteToStream(IOStream &) // Purpose: Writes the filename to a stream // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilename::WriteToStream(IOStream &rStream) const { CheckValid(); rStream.Write(mEncryptedName.c_str(), mEncryptedName.size()); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::EncodedFilenameChanged() // Purpose: The encoded filename stored has changed // Created: 2003/08/26 // // -------------------------------------------------------------------------- void BackupStoreFilename::EncodedFilenameChanged() { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::IsEncrypted() // Purpose: Returns true if the filename is stored using an encrypting encoding // Created: 1/12/03 // // -------------------------------------------------------------------------- bool BackupStoreFilename::IsEncrypted() const { return BACKUPSTOREFILENAME_GET_ENCODING(this->mEncryptedName) != Encoding_Clear; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFilename::SetAsClearFilename(const char *) // Purpose: Sets this object to be a valid filename, but with a // filename in the clear. Used on the server to create // filenames when there's no way of encrypting it. // Created: 22/4/04 // // -------------------------------------------------------------------------- void BackupStoreFilename::SetAsClearFilename(const char *Clear) { // Make std::string from the clear name std::string toEncode(Clear); // Make an encoded string char hdr[2]; BACKUPSTOREFILENAME_MAKE_HDR(hdr, toEncode.size()+2, Encoding_Clear); std::string encoded(hdr, 2); encoded += toEncode; ASSERT(encoded.size() == toEncode.size() + 2); // Store the encoded string mEncryptedName.assign(encoded); // Stuff which must be done EncodedFilenameChanged(); CheckValid(false); } boxbackup/lib/backupclient/Makefile.extra0000664000175000017500000000100711345266370021313 0ustar siretartsiretart MAKEPROTOCOL = ../../lib/server/makeprotocol.pl GEN_CMD_SRV = $(MAKEPROTOCOL) Client ../../bin/bbstored/backupprotocol.txt # AUTOGEN SEEDING autogen_BackupProtocolClient.cpp autogen_BackupProtocolClient.h: $(MAKEPROTOCOL) ../../bin/bbstored/backupprotocol.txt $(_PERL) $(GEN_CMD_SRV) MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_BackupStoreException.h autogen_BackupStoreException.cpp: $(MAKEEXCEPTION) BackupStoreException.txt $(_PERL) $(MAKEEXCEPTION) BackupStoreException.txt boxbackup/lib/backupclient/BackupClientCryptoKeys.cpp0000664000175000017500000000526611060460171023636 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientCryptoKeys.cpp // Purpose: function for setting up all the backup client keys // Created: 1/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupClientCryptoKeys.h" #include "FileStream.h" #include "BackupStoreFilenameClear.h" #include "BackupStoreException.h" #include "BackupClientFileAttributes.h" #include "BackupStoreFile.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupClientCryptoKeys_Setup(const char *) // Purpose: Read in the key material file, and set keys to all the backup elements required. // Created: 1/12/03 // // -------------------------------------------------------------------------- void BackupClientCryptoKeys_Setup(const std::string& rKeyMaterialFilename) { // Read in the key material unsigned char KeyMaterial[BACKUPCRYPTOKEYS_FILE_SIZE]; // Open the file FileStream file(rKeyMaterialFilename); // Read in data if(!file.ReadFullBuffer(KeyMaterial, BACKUPCRYPTOKEYS_FILE_SIZE, 0)) { THROW_EXCEPTION(BackupStoreException, CouldntLoadClientKeyMaterial) } // Setup keys and encoding method for filename encryption BackupStoreFilenameClear::SetBlowfishKey( KeyMaterial + BACKUPCRYPTOKEYS_FILENAME_KEY_START, BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH, KeyMaterial + BACKUPCRYPTOKEYS_FILENAME_IV_START, BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH); BackupStoreFilenameClear::SetEncodingMethod( BackupStoreFilename::Encoding_Blowfish); // Setup key for attributes encryption BackupClientFileAttributes::SetBlowfishKey( KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START, BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH); // Setup secret for attribute hashing BackupClientFileAttributes::SetAttributeHashSecret( KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START, BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_LENGTH); // Setup keys for file data encryption BackupStoreFile::SetBlowfishKeys( KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START, BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH, KeyMaterial + BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START, BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_LENGTH); #ifndef HAVE_OLD_SSL // Use AES where available BackupStoreFile::SetAESKey( KeyMaterial + BACKUPCRYPTOKEYS_FILE_AES_KEY_START, BACKUPCRYPTOKEYS_FILE_AES_KEY_LENGTH); #endif // Wipe the key material from memory #ifdef _MSC_VER // not defined on MinGW SecureZeroMemory(KeyMaterial, BACKUPCRYPTOKEYS_FILE_SIZE); #else ::memset(KeyMaterial, 0, BACKUPCRYPTOKEYS_FILE_SIZE); #endif } boxbackup/lib/backupclient/BackupStoreFileWire.h0000664000175000017500000000414210347400657022555 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileWire.h // Purpose: On the wire / disc formats for backup store files // Created: 12/1/04 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREFILEWIRE__H #define BACKUPSTOREFILEWIRE__H #include "MD5Digest.h" // set packing to one byte #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "BeginStructPackForWire.h" #else BEGIN_STRUCTURE_PACKING_FOR_WIRE #endif typedef struct { int32_t mMagicValue; // also the version number int64_t mNumBlocks; // number of blocks contained in the file int64_t mContainerID; int64_t mModificationTime; int32_t mMaxBlockClearSize; // Maximum clear size that can be expected for a block int32_t mOptions; // bitmask of options used // Then a BackupStoreFilename // Then a BackupClientFileAttributes } file_StreamFormat; typedef struct { int32_t mMagicValue; // different magic value int64_t mOtherFileID; // the file ID of the 'other' file which may be referenced by the index uint64_t mEntryIVBase; // base value for block IV int64_t mNumBlocks; // repeat of value in file header } file_BlockIndexHeader; typedef struct { int32_t mSize; // size in clear uint32_t mWeakChecksum; // weak, rolling checksum uint8_t mStrongChecksum[MD5Digest::DigestLength]; // strong digest based checksum } file_BlockIndexEntryEnc; typedef struct { union { int64_t mEncodedSize; // size encoded, if > 0 int64_t mOtherBlockIndex; // 0 - block number in other file, if <= 0 }; uint8_t mEnEnc[sizeof(file_BlockIndexEntryEnc)]; // Encoded section } file_BlockIndexEntry; // Use default packing #ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS #include "EndStructPackForWire.h" #else END_STRUCTURE_PACKING_FOR_WIRE #endif // header for blocks of compressed data in files #define HEADER_CHUNK_IS_COMPRESSED 1 // bit #define HEADER_ENCODING_SHIFT 1 // shift value #define HEADER_BLOWFISH_ENCODING 1 // value stored in bits 1 -- 7 #define HEADER_AES_ENCODING 2 // value stored in bits 1 -- 7 #endif // BACKUPSTOREFILEWIRE__H boxbackup/lib/backupclient/BackupStoreException.h0000664000175000017500000000063610347400657023011 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreException.h // Purpose: Exception // Created: 2003/07/08 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREEXCEPTION__H #define BACKUPSTOREEXCEPTION__H // Compatibility #include "autogen_BackupStoreException.h" #endif // BACKUPSTOREEXCEPTION__H boxbackup/lib/backupclient/BackupClientRestore.h0000664000175000017500000000156411445744272022625 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientRestore.h // Purpose: Functions to restore files from a backup store // Created: 23/11/03 // // -------------------------------------------------------------------------- #ifndef BACKUPSCLIENTRESTORE_H #define BACKUPSCLIENTRESTORE__H class BackupProtocolClient; enum { Restore_Complete = 0, Restore_ResumePossible, Restore_TargetExists, Restore_TargetPathNotFound, Restore_UnknownError, Restore_CompleteWithErrors, }; int BackupClientRestore(BackupProtocolClient &rConnection, int64_t DirectoryID, const char *RemoteDirectoryName, const char *LocalDirectoryName, bool PrintDots = false, bool RestoreDeleted = false, bool UndeleteAfterRestoreDeleted = false, bool Resume = false, bool ContinueAfterErrors = false); #endif // BACKUPSCLIENTRESTORE__H boxbackup/lib/backupclient/BackupStoreFileCmbDiff.cpp0000664000175000017500000002264610347400657023505 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreFileCmbDiff.cpp // Purpose: Combine two diffs together // Created: 12/7/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "BackupStoreFile.h" #include "BackupStoreFileWire.h" #include "BackupStoreObjectMagic.h" #include "BackupStoreException.h" #include "BackupStoreConstants.h" #include "BackupStoreFilename.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreFile::CombineDiffs(IOStream &, IOStream &, IOStream &rOut) // Purpose: Given two diffs, combine them into a single diff, to produce a diff // which, combined with the original file, creates the result of applying // rDiff, then rDiff2. Two opens of rDiff2 are required // Created: 12/7/04 // // -------------------------------------------------------------------------- void BackupStoreFile::CombineDiffs(IOStream &rDiff1, IOStream &rDiff2, IOStream &rDiff2b, IOStream &rOut) { // Skip header of first diff, record where the data starts, and skip to the index int64_t diff1DataStarts = 0; { // Read the header for the From file file_StreamFormat diff1Hdr; if(!rDiff1.ReadFullBuffer(&diff1Hdr, sizeof(diff1Hdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(diff1Hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Skip over the filename and attributes of the From file // BLOCK { BackupStoreFilename filename2; filename2.ReadFromStream(rDiff1, IOStream::TimeOutInfinite); int32_t size_s; if(!rDiff1.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */)) { THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) } int size = ntohl(size_s); // Skip forward the size rDiff1.Seek(size, IOStream::SeekType_Relative); } // Record position diff1DataStarts = rDiff1.GetPosition(); // Skip to index rDiff1.Seek(0 - (((box_ntoh64(diff1Hdr.mNumBlocks)) * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End); } // Read the index of the first diff // Header first file_BlockIndexHeader diff1IdxHdr; if(!rDiff1.ReadFullBuffer(&diff1IdxHdr, sizeof(diff1IdxHdr), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } if(ntohl(diff1IdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } int64_t diff1NumBlocks = box_ntoh64(diff1IdxHdr.mNumBlocks); // Allocate some memory int64_t *diff1BlockStartPositions = (int64_t*)::malloc((diff1NumBlocks + 1) * sizeof(int64_t)); if(diff1BlockStartPositions == 0) { throw std::bad_alloc(); } // Buffer data void *buffer = 0; int bufferSize = 0; try { // Then the entries: // For each entry, want to know if it's in the file, and if so, how big it is. // We'll store this as an array of file positions in the file, with an additioal // entry on the end so that we can work out the length of the last block. // If an entry isn't in the file, then store 0 - (position in other file). int64_t diff1Position = diff1DataStarts; for(int64_t b = 0; b < diff1NumBlocks; ++b) { file_BlockIndexEntry e; if(!rDiff1.ReadFullBuffer(&e, sizeof(e), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Where's the block? int64_t blockEn = box_ntoh64(e.mEncodedSize); if(blockEn <= 0) { // Just store the negated block number diff1BlockStartPositions[b] = blockEn; } else { // Block is present in this file diff1BlockStartPositions[b] = diff1Position; diff1Position += blockEn; } } // Finish off the list, so the last entry can have it's size calcuated. diff1BlockStartPositions[diff1NumBlocks] = diff1Position; // Now read the second diff's header, copying it to the out file file_StreamFormat diff2Hdr; if(!rDiff2.ReadFullBuffer(&diff2Hdr, sizeof(diff2Hdr), 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } if(ntohl(diff2Hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } // Copy rOut.Write(&diff2Hdr, sizeof(diff2Hdr)); // Copy over filename and attributes // BLOCK { BackupStoreFilename filename; filename.ReadFromStream(rDiff2, IOStream::TimeOutInfinite); filename.WriteToStream(rOut); StreamableMemBlock attr; attr.ReadFromStream(rDiff2, IOStream::TimeOutInfinite); attr.WriteToStream(rOut); } // Get to the index of rDiff2b, and read the header MoveStreamPositionToBlockIndex(rDiff2b); file_BlockIndexHeader diff2IdxHdr; if(!rDiff2b.ReadFullBuffer(&diff2IdxHdr, sizeof(diff2IdxHdr), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } if(ntohl(diff2IdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } int64_t diff2NumBlocks = box_ntoh64(diff2IdxHdr.mNumBlocks); int64_t diff2IndexEntriesStart = rDiff2b.GetPosition(); // Then read all the entries int64_t diff2FilePosition = rDiff2.GetPosition(); for(int64_t b = 0; b < diff2NumBlocks; ++b) { file_BlockIndexEntry e; if(!rDiff2b.ReadFullBuffer(&e, sizeof(e), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // What do to next about copying data bool copyBlock = false; int copySize = 0; int64_t copyFrom = 0; bool fromFileDiff1 = false; // Where's the block? int64_t blockEn = box_ntoh64(e.mEncodedSize); if(blockEn > 0) { // Block is present in this file -- copy to out copyBlock = true; copyFrom = diff2FilePosition; copySize = (int)blockEn; // Move pointer onwards diff2FilePosition += blockEn; } else { // Block isn't present here -- is it present in the old one? int64_t blockIndex = 0 - blockEn; if(blockIndex < 0 || blockIndex > diff1NumBlocks) { THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile) } if(diff1BlockStartPositions[blockIndex] > 0) { // Block is in the old diff file, copy it across copyBlock = true; copyFrom = diff1BlockStartPositions[blockIndex]; int nb = blockIndex + 1; while(diff1BlockStartPositions[nb] <= 0) { // This is safe, because the last entry will terminate it properly! ++nb; ASSERT(nb <= diff1NumBlocks); } copySize = diff1BlockStartPositions[nb] - copyFrom; fromFileDiff1 = true; } } //TRACE4("%d %d %lld %d\n", copyBlock, copySize, copyFrom, fromFileDiff1); // Copy data to the output file? if(copyBlock) { // Allocate enough space if(bufferSize < copySize || buffer == 0) { // Free old block if(buffer != 0) { ::free(buffer); buffer = 0; bufferSize = 0; } // Allocate new block buffer = ::malloc(copySize); if(buffer == 0) { throw std::bad_alloc(); } bufferSize = copySize; } ASSERT(bufferSize >= copySize); // Load in the data if(fromFileDiff1) { rDiff1.Seek(copyFrom, IOStream::SeekType_Absolute); if(!rDiff1.ReadFullBuffer(buffer, copySize, 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } } else { rDiff2.Seek(copyFrom, IOStream::SeekType_Absolute); if(!rDiff2.ReadFullBuffer(buffer, copySize, 0)) { THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine) } } // Write out data rOut.Write(buffer, copySize); } } // Write the modified header diff2IdxHdr.mOtherFileID = diff1IdxHdr.mOtherFileID; rOut.Write(&diff2IdxHdr, sizeof(diff2IdxHdr)); // Then we'll write out the index, reading the data again rDiff2b.Seek(diff2IndexEntriesStart, IOStream::SeekType_Absolute); for(int64_t b = 0; b < diff2NumBlocks; ++b) { file_BlockIndexEntry e; if(!rDiff2b.ReadFullBuffer(&e, sizeof(e), 0)) { THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream) } // Where's the block? int64_t blockEn = box_ntoh64(e.mEncodedSize); // If it's not in this file, it needs modification... if(blockEn <= 0) { int64_t blockIndex = 0 - blockEn; // In another file. Need to translate this against the other diff if(diff1BlockStartPositions[blockIndex] > 0) { // Block is in the first diff file, stick in size int nb = blockIndex + 1; while(diff1BlockStartPositions[nb] <= 0) { // This is safe, because the last entry will terminate it properly! ++nb; ASSERT(nb <= diff1NumBlocks); } int64_t size = diff1BlockStartPositions[nb] - diff1BlockStartPositions[blockIndex]; e.mEncodedSize = box_hton64(size); } else { // Block in the original file, use translated value e.mEncodedSize = box_hton64(diff1BlockStartPositions[blockIndex]); } } // Write entry rOut.Write(&e, sizeof(e)); } } catch(...) { // clean up ::free(diff1BlockStartPositions); if(buffer != 0) { ::free(buffer); } throw; } // Clean up allocated memory ::free(diff1BlockStartPositions); if(buffer != 0) { ::free(buffer); } } boxbackup/lib/backupclient/BackupClientFileAttributes.h0000664000175000017500000000555711345271627024134 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientFileAttributes.h // Purpose: Storage of file attributes // Created: 2003/10/07 // // -------------------------------------------------------------------------- #ifndef BACKUPCLIENTFILEATTRIBUTES__H #define BACKUPCLIENTFILEATTRIBUTES__H #include #include "StreamableMemBlock.h" #include "BoxTime.h" EMU_STRUCT_STAT; // declaration // -------------------------------------------------------------------------- // // Class // Name: BackupClientFileAttributes // Purpose: Storage, streaming and application of file attributes // Created: 2003/10/07 // // -------------------------------------------------------------------------- class BackupClientFileAttributes : public StreamableMemBlock { public: BackupClientFileAttributes(); BackupClientFileAttributes(const BackupClientFileAttributes &rToCopy); BackupClientFileAttributes(const StreamableMemBlock &rToCopy); ~BackupClientFileAttributes(); BackupClientFileAttributes &operator=(const BackupClientFileAttributes &rAttr); BackupClientFileAttributes &operator=(const StreamableMemBlock &rAttr); bool operator==(const BackupClientFileAttributes &rAttr) const; // bool operator==(const StreamableMemBlock &rAttr) const; // too dangerous? bool Compare(const BackupClientFileAttributes &rAttr, bool IgnoreAttrModTime = false, bool IgnoreModTime = false) const; // Prevent access to base class members accidently void Set(); void ReadAttributes(const char *Filename, bool ZeroModificationTimes = false, box_time_t *pModTime = 0, box_time_t *pAttrModTime = 0, int64_t *pFileSize = 0, InodeRefType *pInodeNumber = 0, bool *pHasMultipleLinks = 0); void WriteAttributes(const char *Filename, bool MakeUserWritable = false) const; void GetModificationTimes(box_time_t *pModificationTime, box_time_t *pAttrModificationTime) const; bool IsSymLink() const; static void SetBlowfishKey(const void *pKey, int KeyLength); static void SetAttributeHashSecret(const void *pSecret, int SecretLength); static uint64_t GenerateAttributeHash(EMU_STRUCT_STAT &st, const std::string &filename, const std::string &leafname); static void FillExtendedAttr(StreamableMemBlock &outputBlock, const char *Filename); private: static void FillAttributes(StreamableMemBlock &outputBlock, const char *Filename, EMU_STRUCT_STAT &st, bool ZeroModificationTimes); static void FillAttributesLink(StreamableMemBlock &outputBlock, const char *Filename, struct stat &st); void WriteExtendedAttr(const char *Filename, int xattrOffset) const; void RemoveClear() const; void EnsureClearAvailable() const; static StreamableMemBlock *MakeClear(const StreamableMemBlock &rEncrypted); void EncryptAttr(const StreamableMemBlock &rToEncrypt); private: mutable StreamableMemBlock *mpClearAttributes; }; #endif // BACKUPCLIENTFILEATTRIBUTES__H boxbackup/lib/backupclient/BackupClientMakeExcludeList.cpp0000664000175000017500000000407710347400657024556 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientMakeExcludeList.cpp // Purpose: Makes exclude lists from bbbackupd config location entries // Created: 28/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #include "BackupClientMakeExcludeList.h" #include "Configuration.h" #include "ExcludeList.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupClientMakeExcludeList(const Configuration &, const char *, const char *) // Purpose: Given a Configuration object corresponding to a bbackupd Location, and the // two names of the keys for definite and regex entries, return a ExcludeList. // Or 0 if it isn't required. // Created: 28/1/04 // // -------------------------------------------------------------------------- ExcludeList *BackupClientMakeExcludeList(const Configuration &rConfig, const char *DefiniteName, const char *RegexName, const char *AlwaysIncludeDefiniteName, const char *AlwaysIncludeRegexName) { // Check that at least one of the entries exists if(!rConfig.KeyExists(DefiniteName) && !rConfig.KeyExists(RegexName)) { // Neither exists -- return 0 as an Exclude list isn't required. return 0; } // Create the exclude list ExcludeList *pexclude = new ExcludeList; try { // Definite names to add? if(rConfig.KeyExists(DefiniteName)) { pexclude->AddDefiniteEntries(rConfig.GetKeyValue(DefiniteName)); } // Regular expressions to add? if(rConfig.KeyExists(RegexName)) { pexclude->AddRegexEntries(rConfig.GetKeyValue(RegexName)); } // Add a "always include" list? if(AlwaysIncludeDefiniteName != 0 && AlwaysIncludeRegexName != 0) { // This will accept NULL as a valid argument, so safe to do this. pexclude->SetAlwaysIncludeList( BackupClientMakeExcludeList(rConfig, AlwaysIncludeDefiniteName, AlwaysIncludeRegexName) ); } } catch(...) { // Clean up delete pexclude; throw; } return pexclude; } boxbackup/lib/backupclient/BackupStoreConstants.h0000664000175000017500000000274110702536017023021 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreContants.h // Purpose: constants for the backup system // Created: 2003/08/28 // // -------------------------------------------------------------------------- #ifndef BACKUPSTORECONSTANTS__H #define BACKUPSTORECONSTANTS__H #define BACKUPSTORE_ROOT_DIRECTORY_ID 1 #define BACKUP_STORE_SERVER_VERSION 1 // Minimum size for a chunk to be compressed #define BACKUP_FILE_MIN_COMPRESSED_CHUNK_SIZE 256 // min and max sizes for blocks #define BACKUP_FILE_MIN_BLOCK_SIZE 4096 #define BACKUP_FILE_MAX_BLOCK_SIZE (512*1024) // Increase the block size if there are more than this number of blocks #define BACKUP_FILE_INCREASE_BLOCK_SIZE_AFTER 4096 // Avoid creating blocks smaller than this #define BACKUP_FILE_AVOID_BLOCKS_LESS_THAN 128 // Maximum number of sizes to do an rsync-like scan for #define BACKUP_FILE_DIFF_MAX_BLOCK_SIZES 64 // When doing rsync scans, do not scan for blocks smaller than #define BACKUP_FILE_DIFF_MIN_BLOCK_SIZE 128 // A limit to stop diffing running out of control: If more than this // times the number of blocks in the original index are found, stop // looking. This stops really bad cases of diffing files containing // all the same byte using huge amounts of memory and processor time. // This is a multiple of the number of blocks in the diff from file. #define BACKUP_FILE_DIFF_MAX_BLOCK_FIND_MULTIPLE 4096 #endif // BACKUPSTORECONSTANTS__H boxbackup/distribution/0000775000175000017500000000000011652362372016040 5ustar siretartsiretartboxbackup/distribution/COMMON-MANIFEST.txt0000664000175000017500000000155711345550525021003 0ustar siretartsiretartLICENSE-DUAL.txt LICENSE DUAL RUN ./bootstrap RUN cd docs; make lib/common lib/crypto lib/server lib/compress lib/win32 test/common test/common/testfiles test/basicserver test/basicserver/testfiles test/crypto test/compress test/win32 docs/api-notes docs/api-notes/common docs/api-notes/common/lib_common docs/api-notes/common/lib_common.txt docs/api-notes/common/lib_compress docs/api-notes/common/lib_crypto docs/api-notes/common/lib_server MKDIR infrastructure infrastructure/buildenv-testmain-template.cpp infrastructure/makebuildenv.pl.in infrastructure/makedistribution.pl.in infrastructure/makeparcels.pl.in infrastructure/parcelpath.pl infrastructure/printversion.pl infrastructure/BoxPlatform.pm.in infrastructure/mingw infrastructure/msvc bootstrap configure.ac configure parcels.txt runtest.pl.in COPYING.txt LICENSE none config.sub config.guess infrastructure/m4 boxbackup/distribution/boxbackup/0000775000175000017500000000000011652362372020016 5ustar siretartsiretartboxbackup/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt0000664000175000017500000000354211345550525023704 0ustar siretartsiretartLICENSE-GPL.txt LICENSE DUAL lib/intercept lib/raidfile lib/httpserver test/raidfile test/raidfile/testfiles test/httpserver LICENSE GPL lib/backupclient lib/backupstore bin/bbstored bin/bbstoreaccounts bin/bbackupd bin/bbackupd/win32 bin/bbackupquery bin/bbackupctl bin/bbackupobjdump bin/s3simulator test/backupstore test/backupstore/testfiles test/backupstorefix test/backupstorefix/testfiles test/backupstorepatch test/bbackupd test/bbackupd/testfiles test/backupdiff test/httpserver/testfiles test/httpserver/testfiles/photos docs/Makefile docs/tools LICENSE DUAL docs/api-notes docs/api-notes/raidfile docs/api-notes/raidfile/lib_raidfile.txt LICENSE GPL docs/api-notes/backup docs/images docs/htmlguide docs/htmlguide/adminguide docs/htmlguide/images docs/htmlguide/instguide docs/htmlguide/man-html docs/man MKDIR docs/docbook docs/docbook/ExceptionCodes.xml docs/docbook/adminguide.xml docs/docbook/bb-book.xsl docs/docbook/bb-man.xsl docs/docbook/bb-nochunk-book.xsl docs/docbook/bbackupctl.xml docs/docbook/bbackupd-config.xml docs/docbook/bbackupd.conf.xml docs/docbook/bbackupd.xml docs/docbook/bbackupquery.xml docs/docbook/bbstoreaccounts.xml docs/docbook/bbstored-certs.xml docs/docbook/bbstored-config.xml docs/docbook/bbstored.conf.xml docs/docbook/bbstored.xml docs/docbook/instguide.xml docs/docbook/raidfile-config.xml docs/docbook/raidfile.conf.xml docs/docbook/html docs/docbook/html/images docs/xsl-generic docs/xsl-generic/manpages docs/xsl-generic/lib docs/xsl-generic/common docs/xsl-generic/html docs/xsl-generic/highlighting BUGS.txt contrib contrib/bbadmin contrib/bbreporter contrib/debian contrib/mac_osx contrib/redhat contrib/rpm REPLACE-VERSION-IN contrib/rpm/boxbackup.spec contrib/solaris contrib/suse contrib/windows contrib/windows/installer contrib/windows/installer/tools infrastructure/msvc infrastructure/msvc/2003 infrastructure/msvc/2005 boxbackup/distribution/boxbackup/THANKS.txt0000664000175000017500000000372310370245065021547 0ustar siretartsiretart The following developers contributed code to version 0.10: Nick Knight - ported Box Backup to Windows (properly, not using Cygwin) Gary Niemcewicz - added client/server (SSL) keepalives to keep the connection to the server alive during a long diff, and saving the daemon's state across restarts Martin Ebourne - ported to Solaris; wrote extended attribute support (xattr); converted to use autoconf for automatic compiler configuration. Chris Wilson - updated Nick's and Gary's work to fit in with the new trunk, fixed some issues pointed out by Ben and Martin, made it compile on Windows with the free MinGW compiler. Jonathan Morton - vastly improved the performance and efficiency of the file-diffing code, and obtained a free G5 PowerMac from IBM as his reward. ---- Many individuals have helped with the development of Box Backup by testing, reporting experiences, and making suggestions. In particular, thanks are due to Charles Lecklider - Helped with the finer details of Win32 programming Pascal Lalonde - Comprehensive and accurate bug reports, and constructive feedback Paul Arch - Cygwin client port Ben Lovett - Help with odd architectures, suggesting small changes Martin Ebourne - RPM specification for RedHat based Linux distributions - Patch to fix problems on 64 bit architectures - Patch to fix compilation after RedHat Fedora's latest changes Per Thomsen - Cygwin Windows service install scripts and build notes - Answering queries on the boxbackup mailing list Tim Fletcher David Harris Richard Eigenmann - Testing many attempts at clean compiles on various Linux distributions Eduardo Alvarenga - Valuable feedback and persuasion to include new features Joe Gillespie - Web site design JŽr™me Schell - Fixes to build+config problems on Linux John Pybus - Ideas and feature requests - Useful little patches to code Stefan Norlin - Help with testing and fixes on lots of different Solaris platforms boxbackup/distribution/boxbackup/VERSION.txt0000664000175000017500000000002111512145602021663 0ustar siretartsiretart0.11.1 boxbackup boxbackup/distribution/boxbackup/DOCUMENTATION.txt0000664000175000017500000000014211064031072022550 0ustar siretartsiretart For compilation and installation instructions, see the web site at http://www.boxbackup.org/ boxbackup/distribution/boxbackup/CONTACT.txt0000664000175000017500000000012011101427717021635 0ustar siretartsiretart http://www.boxbackup.org/ Ben Summers & contributors boxbackup@boxbackup.org boxbackup/distribution/boxbackup/LINUX.txt0000664000175000017500000000202410377374501021454 0ustar siretartsiretart For instructions on building an RPM of Box Backup, see the contrib/rpm directory. This is primarily for RedHat style systems, but notes are provided on what needs to be modified for SUSE. Requirements: OpenSSL 0.9.7 Require zlib and openssl headers for compilation -- may not be included when installing the packages. (libssl-dev + libz-dev packages under debian) Bekerley DB v1 or v4 support is required. The configure script should find an appropriate db package -- and if not, use an in-memory version of the code. However, the in-memory version is not desirable as it will lose information over restarts of the daemon. Ideally, use libeditline as a readline replacement. If not available then use GNU readline (libreadline4-dev, probably) and pass --enable-gnu-readline to ./configure. (OpenSSL 0.9.7 is required as it implements new interfaces which make encryption using the same key faster by avoiding key setup each time around. Configure it with ./config shared , then copy the libs and headers into the required places.) boxbackup/distribution/boxbackup/NETBSD.txt0000664000175000017500000000023110347400657021531 0ustar siretartsiretart Install perl Install OpenSSL 0.9.7 or later. Not a completely working port -- symlinks don't get backed up or restored properly. (run test/bbackupd) boxbackup/runtest.pl.in0000775000175000017500000000504611064031072015762 0ustar siretartsiretart#!@PERL@ use strict; use warnings; use lib 'infrastructure'; use BoxPlatform; my ($test_name,$test_mode) = @ARGV; $test_mode = 'debug' if not defined $test_mode or $test_mode eq ''; if($test_name eq '' || ($test_mode ne 'debug' && $test_mode ne 'release')) { print <<__E; Run Test utility -- bad usage. runtest.pl (test|ALL) [release|debug] Mode defaults to debug. __E exit(2); } my @results; my $exit_code = 0; if($test_name ne 'ALL') { # run one or more specified test if ($test_name =~ m/,/) { foreach my $test (split m/,/, $test_name) { runtest($test); } } else { runtest($test_name); } } else { # run all tests my @tests; open MODULES,'modules.txt' or die "Can't open modules file"; while() { # omit bits on some platforms? next if m/\AEND-OMIT/; if(m/\AOMIT:(.+)/) { if($1 eq $build_os or $1 eq $target_os) { while() { last if m/\AEND-OMIT/; } } next; } push @tests,$1 if m~\Atest/(\w+)\s~; } close MODULES; runtest($_) for(@tests) } # report results print "--------\n",join("\n",@results),"\n"; if ($exit_code != 0) { print <<__E; One or more tests have failed. Please check the following common causes: * Check that no instances of bbstored or bbackupd are already running on this machine. * Make sure there isn't a firewall blocking incoming or outgoing connections on port 2201. * Check that there is sufficient space in the filesystem that the tests are being run from (at least 1 GB free). * The backupdiff test fails if it takes too long, so it's sensitive to the speed of the host and your connection to it. After checking all the above, if you still have problems please contact us on the mailing list, boxbackup\@boxbackup.org. Thanks! __E } exit $exit_code; sub runtest { my ($t) = @_; # attempt to make this test my $flag = ($test_mode eq 'release')?(BoxPlatform::make_flag('RELEASE')):''; my $make_res = system("cd test/$t ; $make_command $flag"); if($make_res != 0) { push @results,"$t: make failed"; $exit_code = 2; return; } my $logfile = "test-$t.log"; # run it my $test_res = system("cd $test_mode/test/$t ; ./t 2>&1 " . "| tee ../../../$logfile"); # open test results if(open RESULTS, $logfile) { my $last; while() { $last = $_ if m/\w/; } close RESULTS; chomp $last; $last =~ s/\r//; push @results, "$t: $last"; if ($last ne "PASSED") { $exit_code = 1; } } else { push @results, "$t: failed to open test log file: $logfile: $!"; } # delete test results # unlink $logfile; } boxbackup/contrib/0000775000175000017500000000000011652362374014763 5ustar siretartsiretartboxbackup/contrib/mac_osx/0000775000175000017500000000000011652362373016413 5ustar siretartsiretartboxbackup/contrib/mac_osx/org.boxbackup.bbackupd.plist.in0000664000175000017500000000105511175154567024420 0ustar siretartsiretart Label org.boxbackup.bbackupd RunAtLoad ProgramArguments @prefix@/sbin/bbackupd -F @prefix@/etc/boxbackup/bbackupd.conf LowPriorityIO Nice 1 boxbackup/contrib/mac_osx/org.boxbackup.bbstored.plist.in0000664000175000017500000000106711175154567024454 0ustar siretartsiretart Label org.boxbackup.bbstored RunAtLoad ProgramArguments @prefix@/sbin/bbstored -F @prefix@/etc/boxbackup/bbackupd.conf LowPriorityIO Nice 1 boxbackup/contrib/suse/0000775000175000017500000000000011652362373015741 5ustar siretartsiretartboxbackup/contrib/suse/bbackupd.in0000664000175000017500000000443211025301345020031 0ustar siretartsiretart#!/bin/sh # # Copyright (c)2004, Nothing But Net Limited # # ###################################################################### # RELEASED AND PROVIDED TO YOU UNDER THE SAME LICENCE AS THE BOXBACKUP # SUITE OF PROGRAMS. LICENCE MAY BE VIEWED HERE: # # http://www.boxbackup.org/license.html ###################################################################### # # /etc/init.d/bbackupd # and its symbolic link # /(usr/)sbin/rcbbackupd # ### BEGIN INIT INFO # Provides: bbackupd # Required-Start: $named $network $local_fs $syslog # X-UnitedLinux-Should-Start: $time ypbind sendmail # Required-Stop: $named $network $localfs $syslog # X-UnitedLinux-Should-Stop: $time ypbind sendmail # Default-Start: 3 5 # Default-Stop: 0 1 2 6 # Short-Description: BoxBackup client side daemon # Description: Client daemon for the BoxBackup software # that allows you to communicate with a bbstored server. ### END INIT INFO # Check for missing binaries (stale symlinks should not happen) BBACKUPD_BIN=@sbindir_expanded@/bbackupd if [ ! -x $BBACKUPD_BIN ] ; then echo "$BBACKUPD_BIN not installed" exit 5 fi . /etc/rc.status # Reset status of this service rc_reset case "$1" in start) echo -n "Starting bbackupd " startproc $BBACKUPD_BIN rc_status -v ;; stop) echo -n "Shutting down bbackupd " killproc -TERM $BBACKUPD_BIN rc_status -v ;; try-restart|condrestart) if test "$1" = "condrestart"; then echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}" fi $0 status if test $? = 0; then $0 restart else rc_reset # Not running is not a failure. fi rc_status ;; restart) $0 stop $0 start rc_status ;; force-reload) echo -n "Reload service bbackupd " killproc -HUP $BBACKUPD_BIN rc_status -v ;; reload) echo -n "Reload service bbackupd " killproc -HUP $BBACKUPD_BIN rc_status -v ;; status) echo -n "Checking for service bbackupd " checkproc $BBACKUPD_BIN rc_status -v ;; probe) test @sysconfdir_expanded@/box/bbackupd.conf \ -nt @localstatedir_expanded@/bbackupd/bbackupd.pid \ && echo reload ;; *) echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" exit 1 esac rc_exit boxbackup/contrib/suse/bbstored.in0000664000175000017500000000433011025301345020057 0ustar siretartsiretart#!/bin/sh # # Copyright (c)2004, Nothing But Net Limited # # ###################################################################### # RELEASED AND PROVIDED TO YOU UNDER THE SAME LICENCE AS THE BOXBACKUP # SUITE OF PROGRAMS. LICENCE MAY BE VIEWED HERE: # # http://www.boxbackup.org/license.html ###################################################################### # # /etc/init.d/bbstored # and its symbolic link # /(usr/)sbin/rcbbstored # ### BEGIN INIT INFO # Provides: bbstored # Required-Start: $named $network $local_fs $syslog # X-UnitedLinux-Should-Start: $time ypbind sendmail # Required-Stop: $named $network $localfs $syslog # X-UnitedLinux-Should-Stop: $time ypbind sendmail # Default-Start: 3 5 # Default-Stop: 0 1 2 6 # Short-Description: BoxBackup server side daemon # Description: Server daemon for the BoxBackup software, # to which bbackupd clients connect. ### END INIT INFO # # Check for missing binaries (stale symlinks should not happen) BBSTORED_BIN=@sbindir_expanded@/bbstored if [ ! -x $BBSTORED_BIN ] ; then echo "$BBSTORED_BIN not installed" exit 5 fi . /etc/rc.status # Reset status of this service rc_reset case "$1" in start) echo -n "Starting bbstored " startproc $BBSTORED_BIN rc_status -v ;; stop) echo -n "Shutting down bbstored " killproc -TERM $BBSTORED_BIN rc_status -v ;; try-restart|condrestart) if test "$1" = "condrestart"; then echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}" fi $0 status if test $? = 0; then $0 restart else rc_reset # Not running is not a failure. fi rc_status ;; restart) $0 stop $0 start rc_status ;; force-reload) echo -n "Reload service bbstored " killproc -HUP $BBSTORED_BIN rc_status -v ;; reload) echo -n "Reload service bbstored " killproc -HUP $BBSTORED_BIN rc_status -v ;; status) echo -n "Checking for service bbstored " checkproc $BBSTORED_BIN rc_status -v ;; probe) test @sysconfdir_expanded@/box/bbstored.conf \ -nt @localstatedir_expanded@/run/bbstored.pid && echo reload ;; *) echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" exit 1 ;; esac rc_exit boxbackup/contrib/suse/README.txt0000664000175000017500000000026210347400657017435 0ustar siretartsiretartThese start scripts are for SUSE Linux. If installed manually they should be placed in /etc/init.d. Copyright (c)2004, Nothing But Net Limited boxbackup/contrib/rpm/0000775000175000017500000000000011652362374015561 5ustar siretartsiretartboxbackup/contrib/rpm/boxbackup.spec0000664000175000017500000002037211367030341020404 0ustar siretartsiretart%define bb_user_id 171 %define ident %{name}-%{version} # In official distribution tarballs, distribution files are copied to # the base directory (where configure is), so distribution_dir should be empty. # This is the default, overridden by the following block in non-distribution # builds. %define distribution_dir '' # BOX_PRIVATE_BEGIN # In unofficial tarballs, made from svn, distribution files are still in # distribution/boxbackup, so the following line overrides the default above: # (this section will be removed automatically from distribution tarballs # by infrastructure/makedistribution.pl) %define distribution_dir distribution/boxbackup/ # BOX_PRIVATE_END # Detect distribution. So far we only special-case SUSE. If you need to make # any distro specific changes to get the package building on your system # please email them to boxbackup-dev@boxbackup.org #%define is_fc %(test -e %{_sysconfdir}/fedora-release && echo 1 || echo 0) #%define is_mdk %(test -e %{_sysconfdir}/mandrake-release && echo 1 || echo 0) #%define is_rh %(test -e %{_sysconfdir}/redhat-release && echo 1 || echo 0) %define is_suse %(test -e %{_sysconfdir}/SuSE-release && echo 1 || echo 0) %if %{is_suse} %define init_dir %{_sysconfdir}/init.d %define distribution suse %define rc_start rc %else %define init_dir %{_sysconfdir}/rc.d/init.d %define distribution redhat %define rc_start "service " %endif Summary: An automatic on-line backup system for UNIX. Name: boxbackup Version: ###DISTRIBUTION-VERSION-NUMBER### Release: 1%{?dist} License: BSD Group: Applications/Archiving Packager: boxbackup-dev@boxbackup.org URL: http://www.boxbackup.org/ Source0: %{ident}.tgz Requires: openssl >= 0.9.7a BuildRoot: %{_tmppath}/%{ident}-%{release}-root BuildRequires: openssl >= 0.9.7a, openssl-devel %description Box Backup is a completely automatic on-line backup system. Backed up files are stored encrypted on a filesystem on a remote server, which does not need to be trusted. The backup server runs as a daemon on the client copying only the changes within files, and old versions and deleted files are retained. It is designed to be easy and cheap to run a server and (optional) RAID is implemented in userland for ease of use. %package client Summary: An automatic on-line backup system for UNIX. Group: Applications/Archiving %description client Box Backup is a completely automatic on-line backup system. Backed up files are stored encrypted on a filesystem on a remote server, which does not need to be trusted. The backup server runs as a daemon on the client copying only the changes within files, and old versions and deleted files are retained. It is designed to be easy and cheap to run a server and (optional) RAID is implemented in userland for ease of use. This package contains the client. %package server Summary: An automatic on-line backup system for UNIX. Group: System Environment/Daemons %description server Box Backup is a completely automatic on-line backup system. Backed up files are stored encrypted on a filesystem on a remote server, which does not need to be trusted. The backup server runs as a daemon on the client copying only the changes within files, and old versions and deleted files are retained. It is designed to be easy and cheap to run a server and (optional) RAID is implemented in userland for ease of use. This package contains the server. %prep %setup -q %build echo -e '%{version}\n%{name}' > VERSION.txt test -e configure || ./bootstrap %configure make %install rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT%{_docdir}/%{ident} mkdir -p $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter mkdir -p $RPM_BUILD_ROOT%{_bindir} mkdir -p $RPM_BUILD_ROOT%{_sbindir} mkdir -p $RPM_BUILD_ROOT%{init_dir} mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/box/bbackupd mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/box/bbstored mkdir -p $RPM_BUILD_ROOT%{_var}/lib/box install -m 644 -t $RPM_BUILD_ROOT%{_docdir}/%{ident} \ BUGS.txt \ VERSION.txt \ ExceptionCodes.txt \ LICENSE-GPL.txt \ LICENSE-DUAL.txt \ %{distribution_dir}CONTACT.txt \ %{distribution_dir}DOCUMENTATION.txt \ %{distribution_dir}LINUX.txt \ %{distribution_dir}THANKS.txt install -m 644 contrib/bbreporter/LICENSE \ $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter install -m 755 contrib/bbreporter/bbreporter.py \ $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter # Client touch $RPM_BUILD_ROOT%{_sysconfdir}/box/bbackupd.conf install -m 755 contrib/%{distribution}/bbackupd $RPM_BUILD_ROOT%{init_dir} %if %{is_suse} ln -s ../../%{init_dir}/bbackupd $RPM_BUILD_ROOT%{_sbindir}/rcbbackupd %endif %define client_dir parcels/%{ident}-backup-client-linux-gnu install %{client_dir}/bbackupd $RPM_BUILD_ROOT%{_sbindir} install %{client_dir}/bbackupquery $RPM_BUILD_ROOT%{_sbindir} install %{client_dir}/bbackupctl $RPM_BUILD_ROOT%{_sbindir} install %{client_dir}/bbackupd-config $RPM_BUILD_ROOT%{_sbindir} # Server touch $RPM_BUILD_ROOT%{_sysconfdir}/box/bbstored.conf touch $RPM_BUILD_ROOT%{_sysconfdir}/box/raidfile.conf install -m 755 contrib/%{distribution}/bbstored $RPM_BUILD_ROOT%{init_dir} %if %{is_suse} ln -s ../../%{init_dir}/bbstored $RPM_BUILD_ROOT%{_sbindir}/rcbbstored %endif %define server_dir parcels/%{ident}-backup-server-linux-gnu install %{server_dir}/bbstored $RPM_BUILD_ROOT%{_sbindir} install %{server_dir}/bbstoreaccounts $RPM_BUILD_ROOT%{_sbindir} install %{server_dir}/bbstored-certs $RPM_BUILD_ROOT%{_sbindir} install %{server_dir}/bbstored-config $RPM_BUILD_ROOT%{_sbindir} install %{server_dir}/raidfile-config $RPM_BUILD_ROOT%{_sbindir} %pre server %{_sbindir}/useradd -c "Box Backup" -u %{bb_user_id} \ -s /sbin/nologin -r -d / box 2> /dev/null || : %post client /sbin/chkconfig --add bbackupd if [ ! -f %{_sysconfdir}/box/bbackupd.conf ]; then echo "You should run the following to configure the client:" echo "bbackupd-config %{_sysconfdir}/box lazy " \ "%{_var}/lib/box " echo "Then follow the instructions. Use this to start the client:" echo "%{rc_start}bbackupd start" fi %post server /sbin/chkconfig --add bbstored if [ ! -f %{_sysconfdir}/box/bbstored.conf ]; then echo "You should run the following to configure the server:" echo "raidfile-config %{_sysconfdir}/box 2048 []" echo "bbstored-config %{_sysconfdir}/box" `hostname` box echo "Then follow the instructions. Use this to start the server:" echo "%{rc_start}bbstored start" fi %preun client if [ $1 = 0 ]; then %{init_dir}/bbackupd stop > /dev/null 2>&1 /sbin/chkconfig --del bbackupd fi %preun server if [ $1 = 0 ]; then %{init_dir}/bbstored stop > /dev/null 2>&1 /sbin/chkconfig --del bbstored fi %clean rm -rf $RPM_BUILD_ROOT %files client %defattr(-,root,root,-) %dir %attr(700,root,root) %{_sysconfdir}/box/bbackupd %dir %attr(755,root,root) %{_var}/lib/box %doc %{_docdir}/%{ident}/*.txt %config %{init_dir}/bbackupd %if %{is_suse} %{_sbindir}/rcbbackupd %endif %config %ghost %{_sysconfdir}/box/bbackupd.conf %{_sbindir}/bbackupd %{_sbindir}/bbackupquery %{_sbindir}/bbackupctl %{_sbindir}/bbackupd-config %files server %defattr(-,root,root,-) %dir %attr(700,box,root) %{_sysconfdir}/box/bbstored %config %{init_dir}/bbstored %if %{is_suse} %{_sbindir}/rcbbstored %endif %config %ghost %{_sysconfdir}/box/bbstored.conf %config %ghost %{_sysconfdir}/box/raidfile.conf %{_sbindir}/bbstored %{_sbindir}/bbstoreaccounts %{_sbindir}/bbstored-certs %{_sbindir}/bbstored-config %{_sbindir}/raidfile-config %doc %{_docdir}/%{ident}/bbreporter %changelog * Thu Apr 23 2009 Martin Ebourne - Use dist tag in version * Thu May 29 2008 Martin Ebourne - Fix paths to bbreporter files * Sat Jan 13 2006 Chris Wilson - Support building from an unofficial tarball (from svn) by changing %{distribution_dir} at the top. - Write our RPM version number into VERSION.txt and hence compile it in * Wed Dec 28 2005 Martin Ebourne - Box now uses autoconf so use configure macro * Fri Oct 1 2004 Martin Ebourne - 0.08-3 - Moved most of the exes to /usr/sbin - SUSE updates from Chris Smith * Fri Sep 24 2004 Martin Ebourne - 0.08-2 - Added support for other distros - Changes for SUSE provided by Chris Smith * Mon Sep 16 2004 Martin Ebourne - 0.07-1 - Initial build boxbackup/contrib/rpm/README.txt0000664000175000017500000000061610347400657017257 0ustar siretartsiretartBUILDING AN RPM The easy way is to: rpmbuild -ta where is the archive you downloaded of Box Backup. This RPM should work on RedHat Enterprise, Fedora Core, Mandrake, SUSE, and any similar distributions. It has been developed and tested on Fedora Core. Changes for SUSE Linux were provided by Chris Smith (chris.smith@nothingbutnet.co.nz). Martin Ebourne martin@zepler.org boxbackup/contrib/debian/0000775000175000017500000000000011652362373016204 5ustar siretartsiretartboxbackup/contrib/debian/bbackupd.in0000664000175000017500000000220711167477470020316 0ustar siretartsiretart#! /bin/sh # Start and stop the Box Backup client daemon. # Originally by James Stark, modified by Chris Wilson and James O'Gorman # For support, visit http://www.boxbackup.org/trac/wiki/MailingLists NAME=bbackupd LONGNAME="Box Backup Client daemon" BINARY=@sbindir_expanded@/$NAME CONFIG=@sysconfdir_expanded@/boxbackup/$NAME.conf PIDFILE=@localstatedir_expanded@/bbackupd/$NAME.pid test -x $BINARY || exit 0 test -f $CONFIG || exit 0 start_stop() { start-stop-daemon --quiet --exec $BINARY --pidfile $PIDFILE "$@" } start_stop_verbose() { if start_stop "$@"; then echo "." else echo " failed!" exit 1 fi } case $1 in start) echo -n "Starting $LONGNAME: $NAME" start_stop_verbose --start ;; stop) echo -n "Stopping $LONGNAME: $NAME" start_stop_verbose --stop ;; reload|force-reload) echo -n "Reloading $LONGNAME configuration" start_stop_verbose --stop --signal 1 ;; restart) echo -n "Restarting $LONGNAME: $NAME" if start_stop --stop --retry 5 && start_stop --start; then echo "." else echo " failed!" exit 1 fi ;; *) echo "Usage: $0 {start|stop|reload|force-reload|restart}" esac exit 0 boxbackup/contrib/debian/bbstored.in0000664000175000017500000000220211167477470020342 0ustar siretartsiretart#! /bin/sh # Start and stop the Box Backup server daemon. # Originally by James Stark, modified by Chris Wilson and James O'Gorman # For support, visit http://www.boxbackup.org/trac/wiki/MailingLists NAME=bbstored LONGNAME="Box Backup Server daemon" BINARY=@sbindir_expanded@/$NAME CONFIG=@sysconfdir_expanded@/boxbackup/$NAME.conf PIDFILE=@localstatedir_expanded@/run/$NAME.pid test -x $BINARY || exit 0 test -f $CONFIG || exit 0 start_stop() { start-stop-daemon --quiet --exec $BINARY --pidfile $PIDFILE "$@" } start_stop_verbose() { if start_stop "$@"; then echo "." else echo " failed!" exit 1 fi } case $1 in start) echo -n "Starting $LONGNAME: $NAME" start_stop_verbose --start ;; stop) echo -n "Stopping $LONGNAME: $NAME" start_stop_verbose --stop ;; reload|force-reload) echo -n "Reloading $LONGNAME configuration" start_stop_verbose --stop --signal 1 ;; restart) echo -n "Restarting $LONGNAME: $NAME" if start_stop --stop --retry 5 && start_stop --start; then echo "." else echo " failed!" exit 1 fi ;; *) echo "Usage: $0 {start|stop|reload|force-reload|restart}" esac exit 0 boxbackup/contrib/debian/README.txt0000664000175000017500000000045210674321622017676 0ustar siretartsiretartThese start scripts are for Debian GNU/Linux. If installed manually they should be placed in /etc/init.d. To create the symbolic links for the appropriate run levels execute the following commands: update-rc.d bbackupd defaults 90 update-rc.d bbstored defaults 80 James Stark boxbackup/contrib/solaris/0000775000175000017500000000000011652362374016437 5ustar siretartsiretartboxbackup/contrib/solaris/bbackupd-smf-method.in0000775000175000017500000000062210761330404022573 0ustar siretartsiretart PIDFILE=@localstatedir_expanded@/bbackupd.pid case $1 in # SMF arguments (start and restart [really "refresh"]) 'start') @sbindir_expanded@/bbackupd ;; 'restart') if [ -f "$PIDFILE" ]; then /usr/bin/kill -HUP `/usr/bin/cat $PIDFILE` fi ;; *) echo "Usage: $0 { start | restart }" exit 1 ;; esac exit $? boxbackup/contrib/solaris/bbackupd-manifest.xml.in0000664000175000017500000000243010744675174023152 0ustar siretartsiretart boxbackup/contrib/solaris/bbstored-manifest.xml.in0000664000175000017500000000243110744675174023204 0ustar siretartsiretart boxbackup/contrib/solaris/bbstored-smf-method.in0000775000175000017500000000062110761330404022623 0ustar siretartsiretartPIDFILE=@localstatedir_expanded@/bbstored.pid case $1 in # SMF arguments (start and restart [really "refresh"]) 'start') @sbindir_expanded@/bbstored ;; 'restart') if [ -f "$PIDFILE" ]; then /usr/bin/kill -HUP `/usr/bin/cat $PIDFILE` fi ;; *) echo "Usage: $0 { start | restart }" exit 1 ;; esac exit $? boxbackup/contrib/redhat/0000775000175000017500000000000011652362373016231 5ustar siretartsiretartboxbackup/contrib/redhat/bbackupd.in0000664000175000017500000000257410761330404020332 0ustar siretartsiretart#! /bin/bash # # bbackupd Start/Stop the box backup client daemon. # # chkconfig: 345 93 07 # description: bbackupd is the client side deamon for Box Backup, \ # a completely automatic on-line backup system. # processname: bbackupd # config: @sysconfdir_expanded@/box # pidfile: @localstatedir_expanded@/bbackupd.pid # Source function library. . /etc/init.d/functions RETVAL=0 # See how we were called. prog="bbackupd" # Check that configuration exists. [ -f @sysconfdir_expanded@/box/$prog.conf ] || exit 0 start() { echo -n $"Starting $prog: " daemon @sbindir_expanded@/$prog RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog return $RETVAL } stop() { echo -n $"Stopping $prog: " killproc @sbindir_expanded@/$prog RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog return $RETVAL } rhstatus() { status @sbindir_expanded@/$prog } restart() { stop start } reload() { echo -n $"Reloading $prog configuration: " killproc @sbindir_expanded@/$prog -HUP retval=$? echo return $RETVAL } case "$1" in start) start ;; stop) stop ;; restart) restart ;; reload) reload ;; status) rhstatus ;; condrestart) [ -f /var/lock/subsys/$prog ] && restart || : ;; *) echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}" exit 1 esac exit $? boxbackup/contrib/redhat/bbstored.in0000664000175000017500000000257410761330404020363 0ustar siretartsiretart#! /bin/bash # # bbstored Start/Stop the box backup server daemon. # # chkconfig: 345 93 07 # description: bbstored is the server side daemon for Box Backup, \ # a completely automatic on-line backup system. # processname: bbstored # config: @sysconfdir_expanded@/box # pidfile: @localstatedir_expanded@/bbstored.pid # Source function library. . /etc/init.d/functions RETVAL=0 # See how we were called. prog="bbstored" # Check that configuration exists. [ -f @sysconfdir_expanded@/box/$prog.conf ] || exit 0 start() { echo -n $"Starting $prog: " daemon @sbindir_expanded@/$prog RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog return $RETVAL } stop() { echo -n $"Stopping $prog: " killproc @sbindir_expanded@/$prog RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog return $RETVAL } rhstatus() { status @sbindir_expanded@/$prog } restart() { stop start } reload() { echo -n $"Reloading $prog configuration: " killproc @sbindir_expanded@/$prog -HUP retval=$? echo return $RETVAL } case "$1" in start) start ;; stop) stop ;; restart) restart ;; reload) reload ;; status) rhstatus ;; condrestart) [ -f /var/lock/subsys/$prog ] && restart || : ;; *) echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}" exit 1 esac exit $? boxbackup/contrib/redhat/README.txt0000664000175000017500000000031110347400657017720 0ustar siretartsiretartThese start scripts are for Fedora Core or RedHat Enterprise Linux. If installed manually they should be placed in /etc/rc.d/init.d. They may also work for Mandrake. Martin Ebourne martin@zepler.org boxbackup/contrib/windows/0000775000175000017500000000000011652362373016454 5ustar siretartsiretartboxbackup/contrib/windows/installer/0000775000175000017500000000000011652362374020452 5ustar siretartsiretartboxbackup/contrib/windows/installer/tools/0000775000175000017500000000000011652362373021611 5ustar siretartsiretartboxbackup/contrib/windows/installer/tools/ShowUsage.bat0000775000175000017500000000010311060461151024166 0ustar siretartsiretartquery.exe usage quit ping 192.168.254.254 -n 10 -w 1000 > nul boxbackup/contrib/windows/installer/tools/StopService.bat0000775000175000017500000000010711060461151024533 0ustar siretartsiretartnet stop GigaLock echo off ping 192.168.254.254 -n 5 -w 1000 > nul boxbackup/contrib/windows/installer/tools/StartService.bat0000775000175000017500000000011011060461151024675 0ustar siretartsiretartnet start GigaLock echo off ping 192.168.254.254 -n 5 -w 1000 > nul boxbackup/contrib/windows/installer/tools/KillBackupProcess.bat0000775000175000017500000000011311060461151025642 0ustar siretartsiretartcontrol.exe terminate echo off ping 192.168.254.254 -n 5 -w 1000 > nul boxbackup/contrib/windows/installer/tools/QueryOutputCurrent.bat0000775000175000017500000000027011060461151026157 0ustar siretartsiretart@ECHO OFF : o=old, d=deleted, s=size info, t=timestamp, r=recursive ::set Queryopts=-odstr set Queryopts=-str query.exe "list %Queryopts%" quit > QueryOutputCurrentResults.txt boxbackup/contrib/windows/installer/tools/QueryOutputAll.bat0000775000175000017500000000026411060461151025250 0ustar siretartsiretart@ECHO OFF : o=old, d=deleted, s=size info, t=timestamp, r=recursive set Queryopts=-odstr ::set Queryopts=-str query.exe "list %Queryopts%" quit > QueryOutputAllResults.txt boxbackup/contrib/windows/installer/tools/RestartService.bat0000775000175000017500000000020511060461151025231 0ustar siretartsiretartnet stop GigaLock ping 192.168.254.254 -n 2 -w 1000 > nul net start GigaLock echo off ping 192.168.254.254 -n 5 -w 1000 > nul boxbackup/contrib/windows/installer/tools/RemoveService.bat0000775000175000017500000000012611060461151025044 0ustar siretartsiretart@@SERVICEEXENAME@ -r -S GigaLock echo off ping 192.168.254.254 -n 5 -w 1000 > nul boxbackup/contrib/windows/installer/tools/ReloadConfig.bat0000775000175000017500000000011011060461151024613 0ustar siretartsiretartcontrol.exe reload echo off ping 192.168.254.254 -n 8 -w 1000 > nul boxbackup/contrib/windows/installer/tools/InstallService.bat0000775000175000017500000000012011060461151025207 0ustar siretartsiretartservice.exe -i -S GigaLock echo off ping 192.168.254.254 -n 5 -w 1000 > nul boxbackup/contrib/windows/installer/tools/RemoteControl.exe0000775000175000017500000046157011060461151025113 0ustar siretartsiretartMZÿÿ¸@躴 Í!¸LÍ!This program cannot be run in DOS mode. $ òMàó¡Màó¡Màó¡"ÿø¡Nàó¡Îüý¡Eàó¡"ÿù¡Fàó¡"ÿ÷¡Oàó¡Màò¡Âàó¡Î计Dàó¡ápàó¡Šæõ¡Làó¡RichMàó¡PEL%¤êAà °п Ð@àœØ$МUPX0€àUPX1° ¢@à.rsrcÐ ¦@À1.25UPX!  Ï ßEÅK£¢á—ÌŸ‚&-²ýÿÿV‹ñNè13ÉFx‰Nt‰Höÿ¿ý Ç@ÇðAAÆFhˆ‹Æ^ÂÛÝÿ¹41V3ÀÇF`"‰FTXû»-\Pø,&ÃVj‹ÁS»ÜžlZN‰PJBj Y–Í|$^ºk üÛ2v,04‰p8‰P(¶-Û¶L @DHAhÀM»®{ÇøY‰Ý^$È4j»6áï MÀP‹Îr‰Cl‰ßßÞ»EƒÀp‹Kp•~R.¶‹Ù%4…ÿ£IÂB¬àz‰ãs@_»ã¿ñˆ‹4ƒ8‹9…ƒ9pÛÈöfzƒxp gnX0½ìh4€yW—}»ëzwdÖ„t.hp«ðaä # >ì©hD@<3j›ekÄ9";uö tì ßµÛXÇ8æAöVÆ\²ìd/s[{c $Y…t‰Áo¶±,&(?“}ûf;L9Åt:j`±;vÆ‘›Æ t zá­V³[jÿ1ÛX ®Üñ=î„——l!oì ·nIVéÑcÜ@^2ÈhT°9Hs;ÆÜ|OÁ1—Ü¢br%rÜöˆ 'Ed!Q.ýT¨;Eعýñ‡F›ÞTè­,ÆÖ¶’ÃtmÝŽ9$yñÝpÛàÔvóˆÌþøòŒwð4F‹8–‹ƒ„¿å¾9êÌUlRh„½1ÛÔNuåÐú»}¶‹d²wè3øv¡c~ à} ™‰Ä‰UÈímG[T;ÊAÈt!:Ý…¿ôèƒÁUè|æƒÈÿ¯|*Á¶·o·àÁD#OHëI<èëèL0™‘A+4 ž1·1³wPâe3lDn7³±¹—½ÁÞ³ÐÿJ;Z÷Û!‚Nÿµp†L„` „ýV)‘³‡ xáú%ÍF;uØ‹íܳãMá ¿U ™„”*ú…Åx8b÷CYº-rúä\R(,fZýòLÒvfN…LBP6Ïëãá…0¤0±˜o ¸/8¸Ø‰ˆ¼9šØ±2Ùu=‚xxvÉ üîP}ñûôðÇ… B¸¸á©›G 3ÿ9}¸~8ÞÖl¼w°˜V±µmw¸|ç1 ”Œ,¸o¢¦]tjWRD$W¬Álc\+ 8ðªrؤ¹ þÅ!s!w ƒMüÿú…6Ì ™Môü_^[d‰ŒþnÉÂ[Ètäž‹L$ „ðoÉJ7öD$ºtV‹ÆŒ~,Yf¹×ëBKU VWƒ"Ýy/v ~éöR™‹qDtÆvþr+þ¶/ü;Aré¦ hÄIQP¡1xÔšsLž6÷úí8‹] ¸§nt‰/ÝgÿNäFu¯ e,ƒs3ÀÐUì§­ðÃA-¹Q·–Ã<£.!¯›Ô°ƒ–hkmS±¨£Ï@-OPï¨ñ#‹]'“9s~m+ñ…!˜ ÆÿïeQïà­ÑÂüƒÆ§ü;CÚ]ÙÁ|߉Cü~j-ú ÐLa|Nô‰M5îµø\4on­£­40¥«P¥‰n‡Üuø2"¸Ÿr·ïˆNÓ}#H<=· 9Þv«·å¿ýv|–¡9s0~ƒÇ(4cÿ4‚áŒðY|íÔÍßûh°N¸(So© žé’¡a qqW§2ŠÕB[žI€¸‚/ÿ;Øu‹YÚtGyÀágà|ë'VvÇëö=—ŠÐš: /ËFFFL;t;ü‰óð|Ì΢dÁÅNx”¬#‡Çñ Ù-ƒé¸vpÞŽU›â~xuìÇï0h‡þël öBUFtƒÆ˜ó`¨b¹BèJ¸_O7— L ŠˆH.R¶ˆ&^vòDCž7j(ÁÅaðfÁðhBª³kàtPCŸ#©aTŸ¥¨¥^4ÅSW~Ù‹o“ŒÔϨÇ-–÷Áæ­ÔŽ® ¸ëÚnNtïæ`ü¢zujdÆÝ¡Âj,#ž _UÕZ&0ÜW1/¢T6kÉXÄWØaBG‚µÐ ðùöF F$,°dÚ@ç.CC„nøF={Å1÷¾ùFßcFÈ'ýFÈ­>?ÈÇÛ…ÿ~Ί1{ô~ŒPsC;ß|íƒÅ…Ú›dÒS?1Åbs´½~ ‰^ŠR$OœíQg\ˆh.;¶F›Ã‰XU$ÿ 7`6T7Ï¿ t*%aúSVÓà Æ…œH‘ çk\XÜB)$ƒNvɈ",sÎåH,}:³œ$*ÙÊÃ$ÉŠEƒ›àíÖf ›f)¢ÎˆF±!n!ɶ‚ü<o¸4‹åI €<,tíXê¥ I8N(“ÁÆën¡f.}ð¢Wÿ¬}£·Ñ~ì¯Vø¡nÛ¼×¶R·\þ@è™ ½ë[‹^kûl.K ƒK ƒƒ×ʶTz"ì½Æ²mßh@pd¸€> =Þ°w $vì‰Chéì\ÆB#!¼®Éü]ØŠˆ;ä}SÕNfn¾5I_€x€x9×À ÿH Hu0£’¥'ÌÕQ!%ÙD‡÷Á,3ÓÿD Òuf¸ |­Åd ª Ðjf ŸS¨ð18ea"æ€~|a·í[ËÇN¾eòÈìÿíKÀˆm+Ë‹WfG‰Uô+V0´ÿöK5øF4;÷w r;Ñs]p e„ktüRK§­J$Ón¾ã,ä~£äF0ƒV4øûÚ\Ø/EôuF‹Gøu>ýe[XJ?Ïr÷Ð9G,ßn7\ð°$ö,À$1ä{éýƒÀ_QÿRñXçX€î3õ;\'Tè„cl-z§ÎAë21$üÖ!o4ÆF)! ;3ñŒT5ã‰6äeÜë7t"|L jÿ(>z-»ìëe_Ô{GN|¾vpA +B׺À‰mØÆ%„Ž'µ´À™’RB>—<%|C´½W#Ä+'L$Kß)ÿ¯†¤( ¸%jôñ!äUŽà“oHß̉Bg¸{Ž “HØ)${vŒ#ÈÄ'd¸Cì ºdçPŽrÈÈ–-<°b]"²E@¬,(Ö[xµ%¤j£†Óza²÷_ºÈhÖxºÙmj[̺ÌXø3dgsöà+ô´g\X_,À[ÇÖÞÖ@N_}¾|h;àˆ0ZG$äñßNLCP8»âm(zOL%_¶ås<°x`TX\‰pP.˲u‚h"lptd¸³¨e;Íî^ôÕ¿ fƒ¿Ì•¥r `!t³ïàä-†­}$Q h*Ñj¾Ò!pá< DB·ê­ÿÈú Õ‡ÿ¶^”š·êtuJtmw[·jJWå8X@ w¢}Øÿp­'Ÿ?ÜÐËý±a·¯=ËSS³ý¿mVëëŠ@=ëg9X4Œý‰ àÀ0àò‚ÿ0¯Ã1fË‚ºÆ—·îI—ësQ@ÿ0Aë[Ú–·_¿ tM;ËJÛÛBe OÃDtJ$$Û‡-o' ë‚>”¸ë4?uö`Ët/,Ýë"P YÎÞZ9)Bë =+ÁoC(X).̙ڢ¯Ðˆc LÜ6#¶ì ¸÷7 8 ‡BA؆[M7Áà4uQHLC%¢Õ)9ñý7ØD f1þ‹qÖo„¶M_HuÇ‚évZ¡€¶ 5ös4Ü¡Ì$[è 4É(hü_€£Òæ^Sǃ Ú mN¼®)lû‚D˜ú;÷j†`mDÛ%èŸ9:uí,©÷4ŒúZ°vÝ5¢;›t+3l¼v*{CId£ï¼*3 ±)Á{•Ù¬¹ö[2ß=:S=>Àä…öë|2Yb¶pGÎÉZ3ŒRjX€EJ©22PÉØ“œ)qµàS„¹ö·/ã„È)/«$)lë@ŽàÿqÇAéÊ]°\*D=ÿv"dwÉm YnõÿMðX;^tDWP”LÁXõø08DÛ PcZAápÚ. fëAÓ GÖ~ö·ð;Á|íÿ6L‰>ë»bí‰D°Ñ0xÏl­âþ Ì1ÔðƒÖÅ€~. Gq›€ ý€‚ÇWà|$ctƒ~ ü‹G¶ÀQ®¾Q_ààÅ#1 Wþ{ÀLó âÆFÚØÇ ˜j6Ù€v;,— YuôÇŒ¸!ç_bsý®x_5×^bŒ¹ »èC[¾‰ï~3 b‰›‰k ›¹§ ¿fðÙÈ:'ÿ1™@8|Æn t&ô2Û´/Ž4[ˆ}u1RŠ1^eAB…m«ë;rs]ãüà iˆ ÿ@ˆÇà•ëöG)žS¦à-0´PUPR®)Ž\é_x/•*Á…Ò%/óƒV,{? []'¼‹¦`ÂØªÉ.”¶lk¨V’ú¬#9/ñ èt”1¾+hHRsAÏ(Ì~¯é·]jR$2•ZccršÙ,º›âËQ¶Ïpÿ†Óà ƒÿ üa+ë#]8‰;{ûÚs™XðöF뤆5ï‹\L´¶E[‡Wè‘©T·>é%r/,‘¡ÑS‰F,ˆ•k63Ù ¢Ä_¼Ž3¸^ÔVjãí l}$r'wv sö dÿµø;!„Þ…írç;hràTü¬w©ÐVP©Ýjóàà íø6@s.fî$'l‹.“ø ‰ ¨.ùx}6Eëàÿ¿_x!X žÄÀ¬Pj¸µX9ª-…DO¸ ŒÏÉÀ¨@m ūĸŽW–Hdʾ  Ûw Àô¿ãAØ’}¾t¬¶B&Œy…|åÜ1&D^ø$ä°ŠdØëŒŽ­œAˆ|9¸Z¶Úμü´^¤Á4XBMkL’œl¼´gš‚Y@tgCê ꂃÃuéï膆AìŠÂ$ˆUóï|Ã}ȈF(½v!‹PPV™ÿ/‹Š‹]èˆ3E-¸7¶ àVèrßÆÂ„…ß|öEót%8ËnEpç…Ó»×À­éÒsëgä@Ù«!: tTÜЇ9°®ÔÕÐ2ó”ût@Øvšì­ŸwÐvàÝ­…Yräuµ¸1Y"Å¡µ–(1äIÜ¥;‚ÇýÄwä6ÞL¹ ÀsÚ6øh‡] ì 9Çì+LBs\_Hæ"öÎqk.ŠDŸr\v ÛÍÀvUµÈ×´[ú:²ÈÄìUu(y€A­G÷-‘ÓËr¸wu½·"ìr®€À¬«½°O(+uFVw†[`T‘tÂÝÞDvp9Å~|q3¬ø F|êkÝVù¿|q@é3\#jýrÌë:0rSPá³Ä=ë(Öm/٠ɲÀuÄ-AcÎ%õ$G Þ42 y mš+s(ës|¤ö-JÂÐt ÁyÇÜÒë GR^ÖTñ5;G­êÈGCÀBT]„:|Wª±Œö)¤_/š‰Ô+‡]ô5è2Qëšõ÷|×:㺤îù÷)Þ&曕F­ô¿é;ÒBÉ ¢9Ø1Õl0ÚÊ ô ãV!2£-|r: á²±›v3ìHÊ(Y0›.ðIìƒÃÛÞ¼lÞþørÔ]ôrÍô5j{#d a»‰…¸* u\ð’ƒÚ¹î)W~): ³ÜBÊlÑð›Íz n[›¬¹v6;l´²pÎv³r-µ&Œ[áÑV<ƒ|¥Þ=Ë;]ä”ôÚÂæ!U¸)!¬¶;hQyµÎ|äl­en‰:»Wø¹AÜ]ävœHÿ>ŠP¯˜=„Ç_ŒLªYm¾]~§l¡6™u‹åû;}fÁžçèrºä°‰‡X9ÙÖ,Ô Ñ°}¡¹ÀíŸ]ä­DëFxœÙo-´­¡ˆñQžÓ~%Í]*vÜ‹"&¾©RȃþE¬ Kîo{Ü9|ää° ¹pŒÊ{POzÀ%#à  šøÌSu× '–ÎAìRßx7d¶¥ÞléCˆ€xPœX€]ÝìAø;Ê:ÿgù4Ež7å}äÜœ,-Û}¤¨¬7° -Ëæàg¸¼ÀÉœ,3Ä´´”3 N–§ ðµwW8mQ±œ“>~e˜Ö¶èK –¹”ø{…ö€~™Ö­í1ÿvT ¸ë/ ]ÀF+¬Š˜-Ù$ÀÄ[U0*³Nu×ÒÄ­Øh|›é´?|Ü" îë=>i!”–ÒM†.FXsêþPBnl\¨4`9~j*Í}¡Ó, €ß~W¡#?ˆ}WKuC’¸jÐpñÆafüwìopýo ¿wâƒèKt1HtHuÒ˲=[,($ A÷)ìë ÇÁ³ÝÅ;Í“©ëEûYh× €ëåˆ6ÇbÒ£( 3Û9_á –1;Q“˜ƒf4¬H¸jcÆÝ§È@›ßœCfŒt‚Ûÿ^°â_aëÞC;:|ÅŒ‚CºSEÌxíK8‹ˆ B‰ uRe„–$£fØß-ÂËÁà f!š%G§ôI.‹Ã¥ù©€ßÈÄö2ö5,„Ûu¹Ÿ$hrÏæ³€„]ÿ(>£‚,ÐëFzÀ ¤Ô4ydáòl;uG £Èa?Cfë'x‹·ÜËu4\ \ZÙ6sóS³È)¶6Ñ&VŸð‰‰9YêªdÜHàÐQ‚ÂwüÐR]œPhÜt^Àý¢«»çÉE7€ÇS4‹“ÆTäLðWµ }5I¬ Ò‰UÝL¦CD"ÂB‰-…‹-lÁ2ã–t)ÓH\›dlxìh2Ïöä¨*ŒuÍ Ýt6 ¿5Îu0æPˆ^Bt#,·p tëCt½oÿ¼ë At‰ B;W™8©ýl²löëÑGäÕ䤱[HÐ|(!‡V* Îa©TBäj¨æ>Uä¾Èt¨˜«r@ébÒU½ç»LDÎH‡8J Jðw>龲а\€+[}ƒ4߀””hXd‹œPgXlÏ9bl…©xm)qP &”Öx®é€Ð…rÓAí÷µ «3¨Påvë'SBC‹Ø@tFY’¹„(Ÿ¸`oö ¼ Ñ(·FþH~7 þBá»tèJ¹“‹vöá4q‰&}ØMƒñ\Açn*$ÊÎS4€·„H'e 6qEB½ý;ˆ\b¨,`ú ‘§Üt ~sxÄ‹ˆ¿'µ`À‰ŠC¼) \Å5`ÆÄ1±Xà0|ËÃD "g©ô¦YŸ`Ì„jT@f¡r 9ô p7t‰µ,žA.ª0©‰ÿ©F4S}@Ü$`©ïè´¢V ÑSQ Û$I èP7xœJ±f8C‰ ÑÀPt“È{T¼¿ óIpÚ[0 Š™ÊftQ®ú7 ÓÕÒDUè7:U¼ ×|‡ÎÄ =eÜS¥ÜtõFÏP;„Ó‹xV$œ\eºÖÔ2}ˆQG_P²T‘OH E¸Tb8kö Q`¬—ª'pfšNüŸ£—&ëÜ—÷Ðz“+9Èö„½äälÈYwÂø†\ Â’\^ÍÖ’4d°!„mc@ºl Ñ'ZrAØmrTØÊz|a—TmJ`ÉU TrTØ|Ö †bÞ–˜£0hÊ£l&©/˜U¬sõ¿ƒ-‘ p”Á:ˈHÒ ÊDñ«B‡ŒTÊekÔ¶ÌÀ+,ƒÚ ƒ˜¢A¡?Ülpë+·h¾ðLœ™mÛIÂ=T#> ¶mWSXU?¸ÐÊR¥6s— Âhs¼ h"B×Ý"%bȸ¶; >3Ûjü‰YùYeûG‚ÆF<ˆ^=>–eY–?@ABCDÔpнáÀÃ_ÉžÌbË:rùvD‹Ë~Dx¥à.•ôv04ÑÀ‹Œh¸‚_¢ s™\FDUüAÿí»=Uô™9rÖw;ørÐä@.Ì f¸š‚K3 Ë…šQN¾ÄÅíøMeDøÁü’‘kmiÓÍûì Ðà¾ôJ4äÏl \ ±/ï‚Fl‰EEôLiN¿µâa|‘€y<Þ¢þ Dÿ€}ÿt9!5jÿ7BÅ BT·pá!;+}^ú»™RûÛfP\ L¸uGëßW3g(}Kua<âßÐN\™;Tùr ùrGƒeø„Þº“ôL;–Œpé>!ðZ{ÖøôEô$*Dc±B,:Hz áWôKÄ+7[_x‰‡Êm *¯އ„5›¥à€³yÅf²²Pž8Éra_ðwèèŒ8!¶ðÎ_XG¥riiXÌK¢ÖÞ7˜R`Iäl²}5£j7Ô3}ÃdÔèVúÜSüèG$«¶Ïv‹ψAŒqШ Õ­[µÂNþ¯. öE4‚Eý^FtµUíQ.P‹ÌÊÏD5m¦l×¹è>.Ѻ™À0 ‹Ê×EÃÈW3w3Ýøðÿ|ÈÉ×@ð“èäjsG€i§âTa¨@pŠHÔg¬~£>U|]¬ÐEÜÍ.Ðø/`ƒ}Üu €1„ª"ZuguVÉ)ÌÖ%)¬í­KI5,˲,>??@@²,˲AABBC¬è-ËCDDžÍ x€<`Œ,HHKàÙBrdK ¹’)¹ØK´)9d"ìd"¹’„ÈHÉ#9ÈSß0@y¥‡+qNëô;Þ~0”~ þ™+ÂÑøë 迵_žÀHƒà ø Fÿ0;Ó}+Þ‹ÃøWNQ€–q ,ŸA.Êެ¾F¨!D/ÐbJBBf^à\GñK_´ž÷ W^;C8ÝÜY%,ví[# ¸ÍO)”ÏPVÀõÿº^Å4˜Öôî…„M8ø–eùØQ9‹ÙVuCÆ…7Myƒd$‚uT2EÞºàaÀQ£ ÿ·úvy;R¦ƒDÿLuÞNpÀm…GPYQd¨Ñÿ6WŠF:B5-@MÉoZÚnõÂ+ò‚:/ ;ð/¸7ó°ö#2Àë÷¸-z9–@¶ àÖÁQ«^@j`À|ÿM þ¶²š y_2›–¿‰Fá~4‰VÙxú$^8ºCêC[â“eCC~ÊGN–eGGG·%uXÆÅ· ‹‘T’Ïj2°®Ék¼†Z<çê½èe äeÛn%A³lX”`d—˜„‰hL6< ‰}ˆÊA>¾€ˆW6/˲WWGDž”\–e{S/SSC"–åû†¨PPP€™»I²3‰–¼Ò)ì• ®DÖ YÔ¬-ÆLœx`žN8âN$Ä%ö)àBNF6 a ÌH8/Ë4—Áø}ö;þ”Ž¹Ë‰uVÃÎuÞ€5<]Àjæ`-Àž´+-t /`·ìî÷o‰shèt3.™î!) ‹~`=¨k°AÞ7¾-Šï @AÉeW­~h!†„%<:‹8v\RÀ×”dSÀŒ ZDcG¦~l!|B&`ªÏ¶œdf¡ïHŠ"QC556GïéFdjÿë`¾„Ù>ôìjÏã±*ßiœS³0uÆ‘¨¶t~((>o…·ª¸ƒ8sZ P Z×ù‰Œx-Hþv{-G;(rØp[9~ v/§N.Ù/X0†ÄzxÂfY6 rјöÂá£*ŽSOQt"7ÙÚÖÕXÖ1D´›‚"Ÿ ìë!Ò¯5ð„'¶´x¯•`/ $"õìV,}l†ÀÍiªz ¡—1?T ý;Ô9Xt~‹@ÒL+ÐRËX¯6 v|ãI˜‚Ž-°÷~#‹€Ë+"|ÝaÚÖ MhähXÊ” qáÝÞ½¢?®F8PPüÍ}ÈjSÓLƒÆ$PVJ5Ò^AzA³x"pP­é½lòŠª[X(‰ W»&A…H0ǹ¢¿SDÁøPë àísîAC‚Br+õrÁ[_À/É.pÌXB8…íժѱH‰å¨éXDü^\ÄË›ÛP/[¹Hpn 8]Ï@Dë@°y„»ÔXèexx‰‘¾é-´áWœNƒ ÒÏ0‘AÆV*´é>b.>ŽD4yPW”éÜî|@`m,‹È ~Ȇþ}uäƒá-ðÑ%‰VÁ† yVÁl?p(8‚x¢Zè3<¼VÁÀ.âÝ q–„ÀT Öc>]Ì´î;¸//ƒ¾3‚õ.¶íÇaÙ±‹É¾ ™­Œ+ý:ÞÓh4¾~¥¯&@Óîì•c|ì\—ÓÆuEp`6  {+Ù–— œ˜N! œ#˜„’8~p@\á$GHD¢^“*[D/(‰I(É|˜œUQÊsXÜÊT—ã~C{¢D¼ˆµ íÄ…Ì\{rËÔï*u·ÌÖð­ï“ØLü^$|¿aIܸ[/†–í劲z9xAP>ˆÏ(G øâÜZÊ´pM:B¼…3uáËŲµƒgg V‡2Ø-~˜LŠzAd!D‘bEªóƒå*Ø‚e“Qqz•Þôhú~ í é}ÐPIßìÅ©Dô©€ˆr0Káhx‚ìn*N| ûz§ý»û^2,{ƒÁ!¨av°äà`~ÈQ¸3Öµ\²Ðn`|ïyÕxGÐï áx;Nt©'L6ÌHSÕGÑF¨W…©´»FtÜt‹âŸ,ä ȲP[K5 zÈ‘A³4Æ¡äÀ ²ê:ðÀLUr-D†bçÔí^ R†º  Wn]5= Õ¿<ÖxSKc›7~¼¸T`¸(1|ícƒÅ³™¬¨Õ‹Ó,ð‰kSYYÏ–î·1–¥FTE…L¥Ú̲l P @f0åjƒîc> 6M³Òœ$jjFİ8z,¥s¢‹øÿMA!Û½£—Qßos‹©eñÖ1îW½ˆ€d¡ÍÞ¶öܯ´.‡iǯG*\'n¡F,t!M$êѨzº%¸õÎ}gK§7`ëëºx™Ò~[›þÐ#¸xt.CÔ;Ú8GÆ ,)‹€ƒm‚>=Îu#å/PÀë ¡#´3Éć,w.‹vuçAkm#jwœì%ê÷+h‰Ýî_¨[nR|W€p WÙþý¹‹=$àvÿ×9^|L«’V ñCEN´9i=â½è†Uáâ@ô ElDrr Ft ‡dÎä·HwÁµP1fCVüN™À/!„  Ö/K¦¬” »ŒHH'@þ/%¼ ävëÖØÉ-•ß0@µ–M€&7sDȃÌâw@aÄ*9gˆõeU¬P0â‰>_€œBŽœttà"çÈ'Ž÷Xioc|Zç QsˆReÆ(|݆s#c½ÜËVnrî'À ÃÔMcèù0pðRE˜ RàÕaü@Eð˜C/(ðÆx½ÎºÄQSÿ؃4Ö¹S ‘Ô! Õ¿…–„Å( ™2¥0JÌür W ²pžŠºÌØp£-,®t0ðR¡@ÕÎdrŸP8ŒWÌÒÏ~(c3´Â}Ì!Òü/¿°³©;F$sÂø…ÝK¶ˆ HA²;V$r錭U`À('îw{][SPVÂ$‹:Z ˆ HW ­ua4 ­•ΚP½¹]›ãt6›s&‹Ö\aé xá½ö,rì*10£.b-9ÜÒ+ÆÄ‘Ñ?"f ïÚ@<…~ÔD{Y 2É7É|jwÉ$“L $“L2 “L2ÉL2É$¨’¸×145aèWVªc¥r¿A°j\Fj/^A!v‚ú÷öX_õúä‡Fªa}YÎF ¼ýøSf‹\mf9t;¥V¶°q‰9‰W~ 1ÜVwõð:f;=t f BBû¿Àÿëí+ÖÑúëƒÊrÒ| f‰VB@;0àq|ÐXq˜ú™öù 2<ÜpP€ù+ò—jü¿Ä"‹S2+ÃD%ýY÷K€pë)w[0!Q=†-jÄÓFw Õˆp•në•brƒaÚÖø 4‰p v¨“Ó‹Ç!L`2mÄC%¥¬Çu åîX‰ˆ®á+}ã5þ‘Õ‰‚@ðd]zGƒr¾Dض¿vì‰AÐ0`ˆ'à…8ƒÅ@®ÑúoQ—w–ð]j?‹úY¶¿ðV~ Mó«¸jr¾ Yí¹]j ä ‹†Mvò°Ë @ä…:(Íü3OŽ0 ˆžh‘‘‘›i‰žd`\£à™XRYËÑò‚op‹JïJÓà$#îš öï:¹ä ]÷ØþÞÆ‰K¡çók±¢ 9E_¢møWòV² ~¾uþ¿üýÿÿ‹þñ>µûo 5è^º ,¹ÿ˜‚§âÑæ)_ÑQ”º0ÒlIT?QKª‘\þuÞGâŠÏ÷„*ƒ¦"mfk¯$l3PelûÜÖ‹¾lEÐTÔYF–‘Xô\ð`Ü|dìdàŠ†iˆEÿ•30²hèÝY \´ Y‰K.«Àm+@8Â/ÿ7¸DÅŠ­]û&ûP°}®(5EV±PuˆcÐ0Ý.oüÔOÿMnÿ¹Ð „uk¨D/„ûë9 Hö®fÁÁåKEû‰´ m±)ì‰Þ²Lχßлo¶}苞Ú{#E›¢Ü]Ðþ™ÃQæ‹]»ßLÓz’ ¥È;Sa芇üìæºtVOöfe±h‹–4µØîÌ9‚Á‹‘ÜA) 0n] Šˆ.#‚ïbC/ýSŒ¾XÖ ˆ}ÜÿãRä vÎÌãˆü‚pÚPâu匆nL0“’x}ƒ+ E‰·Û5bYÏ¢“ív—‡uw¸_u¾€œ]ðë(èìAM°ŠE¶-ÅàÄì%\#šmf0ÙJ»I§0bZ—çè¾®C•Cÿ0>SŽ^Èît4E3 þ‡ CþrjXïØÛ1Q0*“Ø ÿ[KžÈÕÑéƒçIƒÏ¢ÏþwßsQ,Q‹Ï+È„Ž ¾•"«• FæÕ›Toü½! Úé#Ï€à ”të×J)s©¥ØÕÔ6ÁV-³Tc ;SöîŠP°™@ ê•TtÔŠ5W©¡‹ü>ÞZåØ*Ç‹`ÀØ O§Zã0ÒHE$¦à”(Ü*ë%c@4çÌ9cŠun`³b@=7b)ë…PþV+Øu[#±: ‡¢‘‰&È2²lÔTôXX"Ëð\ª ùX‡Ã ŠEÿˆŠEèˆÀ0"x¬îv³?l맃[u] ~ôÜÞcŽTÓ‰²Õ¾ÐÖ~ïFQo` »O;ø²ø…j¶hÕ÷r 5žÔÿ¢ýÖ+ŠA‰g±¨2ÌÉ ÿ­hpIÉGæÑï‹Á+ÇÁè[|!€V#×+ÊDBo@íø+‚jÀ ÐØâéis„hA "†‰­k ï¯ ÈÁç¼+‰Szvª•‰oUè.´9ÅnBûr QC¿us-¬7DÁéD˜¯ÈQ6hÛ9MTŽŸ-ðß­ÎX‰ ˜Ûk)þË¿¶+Ø+Á‹M™\þµ ðÀÊ$Þ Áf`Á橇›{Ó”“x~ŠÃí3 ¡Ù̙樽°‹H@$V–zuà7D´CÝðÁîÐe… ¸Þ­D.<ö¹a¯®øsBôsßæ1³­‰ 믎ڂZÝôJ-ñb·±S…aƒ™Æ´µø²±KÝÂÁ‘};u#¸!L[¼‚qîÛ³ì¡ FÂr"É¶ÙØsá𛤘ž{C°šŽ—;\“Ó’ƒv BF˜ÙŒÖÕ#W*Wu²ú¹Á‚$Ì0ë6C&{Ñf!Ýþ`[ÔƒÀë'1j@gä9˜1ê—Ô¸ì$ B‘ t$=)×e;B+q=œÕa)V+Æ|q¬`ÊHFLØZðÙc#D‹§o†t!+ŽÏQàÁ÷ÛÞL;Ês?–.+–RbíàI¥……{ƒ/@$Ø7fös@+F¦^à8™HV4 ‹ p£Ùè8ƒäRuÂ>jé™C€Ñ#ü{6¸¿úÿˆf–y;Ðw­ë,ÛV_õ †¨HÖÑVtp.ï5rðß‚eçŽéÒXêô>c)¨Åè2 ]·# ÿ7–°iø¸øWUЧ×ülR_Ëž ýBò°‚^ë*B]ŸôÈvè¸vfB xj­½j·Ões Ø—? Y[Ü]ë™÷ùÇÀ‹Ú ÝX Ö E×l8B·7ÝšÓâsJ‰—}3Éî_jo'0ÓæãƒÖ@ƒù |ðpac @¾ÂÝ۰‡†ñ’Œ|¸ä¡ Bù†ý¡ã_¢2 ®$‹ÂÁ>pùè ¯s9Kº@ƒmyÐÁêÐg²žä…N$f w­À¶@þ:.+‚N ÿJ˜‰¹:+‰$‰€@_SO8 ¥„ z„éÀcE¸j§½›´n:ÚŠ!×)€}ž%6.MŠÈøð©€¾ø†+U˜ÏCh½[ŽV Œƒ»oõ^ÙÜ‹®°Ú‰‚#êH÷bgñí²Ùa,€}un{(Ó½S»ƒ0È$¿[c‹ÏPÁæëÜ µK ðº(Íîž&ùÀŠe,Û““7ÞëhmHdnäÂmi9¨Hy–A&‹H©Íxtt39þB¡­¶wmîÓN uP*¦†èàødÀ Áîi …‚°¤vLž£ab…™Mõ}TaË#/9~X#W# qÔ7W2®ZP1Ås2ˆ[UŠÁ!ÆJUùœÿ‹Sf‰ÚÁöZ8éu®@«‚Í0‰è!úä(AKüh˜^‡ŸêWÞ‚r@¥eùt;þö%ÎD"]üÙWSP0Pøì[ÒÕD#9Ú¡WîuÙÊ ê£neàÃÖÁ@AÎ\T¿ <‚>×yhÓ @õAl(@•ƒkÑöa€q`ðÉ,¸82º Åü ªUpQ_h Ey£kQ…FBÜ™bÔ­9‰~;İUX—"Ί>w­¾‹ÁˆM= ut†SEa ;þt+'ž[$ofƒ i>=ÉæË+¢é ®è¢ ’ÉIQùE‹DC£ÔkÉ8SÑÿé*ËÐó;Ùu;ðuÚ¶#tÈW^ëk äèc­T%ìª ‘V­%rÓûn»/ ª]ä«T"rܬCëxsKëäMÐ$p¼ @Ì„“X¼g8L¶Fðh¿²“(F_COUözÝsØ‹S[+ìm[; ÁååÈÉt @@ÂÑøÈÿ…‡A‚ž|üñ¡HÁpötG;­TWèÂ}=þÐ'ç±)Ñú<;yÐÁ«†Ûm?lmW ¶uÅ¿™FF;W|Üov6Öá¶¥1AÃx5) K¦ AlNzÄ¢~BΡú€Á„^ŽVȩ츴>¢/À]{Ú3ëსjk¬¼>­×”.}ã ÜE}è+3ÅÑþag3>ăHŠ4;]ƒ|]­ñ82P|•Z…(1®á˜ÃªÅ!uÞ7JeÿZgM+ÆPVNzÀA4ß­"×büHT‰OAÍëwíMÜü¡„/f¾8P‰JGƒÿTçú“€CAèù+hdEßhµùB”ÁZ¨Ñè5 ƒ¸í³ÐÛ¾Juî‰À˜…bAùì6A<ÁÙò£%¢öj ØZÅM¶å3¥ön—7Áê>…) w‚#ÚI¢ù¤›w õÛªω£*€C‹ÞPþ |i:*ñ¢³ÙízF‚½œ*+fáN‚Àä¨{¶wÌPwvSWz:Gn…ÞØ3ûy<½ÇB#jÛ¢NÄ[^~ÚŠ@­‹Õ][ . 6b,j%.9W· uDu[ø®¾Fv¼ùø ÃuÙÈš¶/tË ¬Lu¾N  ï(&½]ž¥Ú1WO‚hJ‚#.œh¨@HHò_É=ä30V(‹Īx`,<@†à›îAW8ƒøxu~…ÍÜ)_2!ßZ"ˆl è|VþUÌ ·]¥ò‹0O®9áȱP"øfw¤#EèWðî4œäUˆuèuÜÞчƒÄ)FþHÂÂsO©L UMaVðQh Éê~ãµ)¿ë÷Ùò¸/3½ ”Çt‹w-;ûtJì|fÝ<)P!­úÐ’ã6GW¶Sø0†ª¡ôÃî Øy tNä€A€a+÷+šXdûu[ ÛÀµ´V¦âu+pKœfPWì‘C¶ì¸¦âSGSºäÂF4_»u'¬຦¬ K!¹^êFH‡SŠˆI߈¼„Òuö¡#ƒjÇ>˜'UÄ%Au© ðŠ ê 8TÛ%ƒÂdaÁBÈ<¸‚A Nl €c5ËãªÙ@Wkf°¸—«¼Ñ"Û Ä®4„GÔQ f°³ñáÚ ©Ûã¸v"Ü÷¬§H'#F { kÍKehT¥%\P%=iäHºQ#ÿQ"à¡0YWþÜÂG •ÿäP¶µ†.€ Ü9G6´Qg/€ ˆµç{»§tM\uC<€­þ[£t'<\t€{ÛPôú€së6 Àrxl܈zðssr}nH0¼½ýR¤rf€Á€@s^)àsÅ;õ-ÉÁà·¾K²ë%.?Õ03ºe¬Û'6ÒâÑÀëè}ÐRò~Fˆ÷‚×(ÑŒw8R° ^â/NöÿqB„BÔ"I­Ãˆ*„ßJ¤RGsqV$ +x Qþ…X9•,9qu%ƒþ^.yÆþŸÀzC„¢_¤ðÆðèÁ ^~3’ñ¤¯ÇP†“%ˆÖ  jƒÁr 7Ñ ›ÄC[_ý‹*Å[DI+@ÙÚœÈ Ü2Š/ʱà7,·6>%ô~® dÊ RñK ‹?´3A+Á»Ü~~¿1PH0)wü»_¤ ’é¢hȉA¹¼òáÇÈ Ãh²¾@AÍŸ/Âìÿ5N2ÅÕhÈ%ͼ°å°ò•Ý…øe¯2¤'d@^¿¤¯+yöû>@hHhh˜7°oàKdP˜¸84€É@T +=0B·ö‚èbQi’©ìhµdDôFSB_FÞtIÔ— °<9;ôpf=\3ßJÏ/u èLàž´Ï­‚î§§ë PMŠB l©HÇ&}«M¨@ß-Ž…­[Hx„¨ï–ضBw2WþÅ /tHÄB×JJŸêÞ’PÍ2Þ|¬pĄ݋\YKåARЃ¸c4ѨèÐ „<°9 \QÇw¾ ?™wÁèjëWµ°«kÀŒ´™!uI6ºRµ ‹õÈjíÿRÜ÷Øoo †<öèNtDt3€ö Õ-½]%HtHöï&n8Ä8P,ë+ or_ë œ.ëÀTeg (Ô A  oýrÅÝ'·ðóV. €Oq ×už¬5".Pdo4mÛ{‘ƒÏ_«èi»$ë ”¸kmEQhÏOÙÌÆ;Ü™Ðÿ¬Ÿ0|‚úì)jh¥hŒpV€?,uhÔ…ø~íÈH`Ö°Û=w4Î)à³aÓ” [nCÔ€U$SV¬ÕXXÈ%‡Ü6WÐýL@3Éç;ÃÐýwÕ¤±a"ãh0Ž¸Šš«ãÅ+DÞN04›¹–±õ4D÷Á¨F¯ŽM¥ð*hn.NNðÑþÎKð·›gqto|pþ:tgæV&Mï`s ÀJ°--;ƒÀg/Ø^;0ŒÆf p¶ì&À *ý õÈ¢V¨u ë ³Ÿ2Ûëu |ePbFt­¹µªqïsÅn À®FFëî+ñ!aî$}ÿ§·p‡@È ¥ûg|_»öÛÛ„þb„ÛYuŽ;3aI¼°jiÈiñÜP€LAùtó¤°o ¶$[²¤/e¼§Ôz I6 à_#üÀ8ë0}r#ªV MÇ'£[#:“;«hî³"'$´zBš±ÿ?¸ PV1d» @Ä^à Å* ªF .ÔüHXnA­ßW6–$à*9šÏ9sÀ*r¬«¿F];ƒ™xÃa Çp‡÷š*…51”Co0·!NùìÀ¢LšÑ.¬…qÌ¿& ¥×äf˜mƒÉ‰pŬö¾rmØ{C¼ôŠˆ}ŽGb\rð£:m&Y1"ë'ÝLB0KjRÛꬕQÛekÓ8-BRÅÖ,€GHp ÁÃa% Œ0j[9•[Á®ž|ËV{hTÀ6i®D)ÞgŠ:y‹¹ÀVÄ;Á‚Qæ¨A8V1ºè+¶µ36DC!ª×Øì†®PˆÈ!«ßäO<¬ëcàV‚Ø$rý"n—@÷‹!~† -KõÈŠ @©îm…`ÁG/7¥?u‚ík÷ÜW>ìBˆ'£¸_6ô°è…¦¬ÃWÙF ô[‹X„¼«Ô«6'Œâ¬]gGê nt&Uÿ ‰4Ô‹I0€9.utÙ^(ÅÍ€y tPj"Zç´Ã}Ó¤`Döç‚È ÐyˆV2|iª@ÑÆâñ§  "q¤yýk¨3¤ !8Dû-犢ßRÕ}ÏòËÖÄ}¿,˲, 6S ¢§L +–ÐÝìU½´ýRoÁO0 ³,ÓÛö‚ÂW$w*$(6dáAöŽ µJ±ä¨·¬Ó!‡Ü½Lü¨t˜,`uynÕí"•Ô—+œ›:8Èë™  ‰ ­:%ƒ> !¸22-œÅ™ë‹^,$?£Ñ…ŒƒBI‰:!¼6Ö(3 Ãrhð¼MèÀ²!jHáB^ÆYP£9p)¡ +\²X! W\´—ùRHˆsÄYºš‘ŒÕ«‰1G¡A6ý°ÃÐÇñNÈÃ[XÐ6 ‰R;ùS=+QA[øaàæ&õÊ Ñ6’É¡d[=äÄOtt ø@Æ´OŠcO‹p?E´Æ®•Õ›òD 1I¸ÁE0CŠT+Ñ‚îBÑ>êé`v³ØaµPìPÍ_ŽÎ¦èâsÈJäÅ;z•O›\øÇ\ŽAì \CÉõÇê§·@À'z³¨jä L Oµ´z4䯙2+¬7)ÉD¶>¸õ@.od¨"PÉUøÓ1 vIÈi 9+Zž~è%fí ƒ~ƒ·ëîB Á‹v©Œˆ`QªVÛŽle‹è¼ƒþfÅZé Þ-W ¤òÕNúôü|ÈÕÎÐÅÛ?P`}aìl¸º: OÀ †¬'P2Í¢ÀA¹ƒ@A¢À@¾ò ½ºÉyêݯڀÚnwEÈF2h€jž–MŠÃ1Ïí3•%!ƈÄ_Q:ÙKÉ@‰SÊ! d*f¹%{æé>(ûCòhÈ7-l˜ò%äÌÐ XLÄØNÐÉqɹÝ;„Í`U SŒ:@ZʺËÞJæñj\–ø«šÁ{NG `‡\Ì :ï(ÀTKNËW†¿üÐLØ‹ Aþf‹g+ Àf;á‹Á‡Œâî+Á‚ÌÂ)fMfƒÚLJA#uˆnÃ9Ht  ôÊ0A¦èt±¶\ N; sÕ\Àf8$ê’â’\2ddgÙ& @z"¹@‹ @@ß™ÃNQSñöÆ\·Ê#}-½ÜÜvö|~ ~ ~Ë›3Ýh5|ñ°vl?@uçi!?ëæ×Æ7ŠXRZjVb °} #&…lÕ7,¶$L¦ˆ à"'¾e?7Š­’›ïÿ¾‰w éG“Æ †A|cÿ#ì| @ãœTŽQäPC¤h£<+È;Ï~ÍÌ .ìët8¬ú—ÑÕØRèd})Xd‹¦b„rs7!ä±¥Û©C‰]€À É0 CDÛUP­Ë~ÉÎ!ZuAZAþȆ%lA°w„"…,±ÁˆgT¹­ÌØ£Ü t²•@ñ“ ¯ëª¸ˆ7cqHH”Ï(¾G NEîŒ\2pãÔ@?‚ŽET_ÿ 8@(`É‚A§ÜÂàœ–`LÈaИA VGP3ŠnUBí–H?œAJzbDz‡ÜÁ qì`pàœNÈ>ôH ÀÊ\TSÄn šÌ:ù˜‚¹/|‹àr¸î€ë8ûYžœBEÉy#û•¥ JQK#°$€"-ØN`a!®2dæ°Uµ×i‘ š¦æŠb±΀PÈPD?¬VpõPä˜tÕAùƒ5wRp:W¸Ê "PuGG!˜@C–üø±@¼ÿÛû¿ÿ;ÙÆ âcv eE—eáˆWÔ"U¢–_Q !šÑq +rMŽ›@(^™ÄF@Ëæ©é-!äÔoà[©È펈QãXÌÓš¦[¢ÓÁW"nê™æ}ø¿sAÜ€NQPj‚<¬h_² fÕ ®(¸8ß Ñm…`˜ˆF„­È,”nƒ÷ˆ ÕŒIDæd Õß^5 +‡¥W9V n!âwˆwr;ÈHÈ’U n‰WêR[ªžÞM2)#K#Û^;^}SÈDØF¸A ¬°A&Wg!5“Ü D9 ^us|Ô(96n! GÀaTœ®Æ3ØŒïUc`‘I@rØÄjð£V‹>2èr†QvpÛ,|Ò· üNVr*;‡OÉ;Ó"¦¨i^±J¢ajh‘1:Ä&à PA¢\Í»¾$ hj€Ì0¤éxh :}‡Ý6¨ ÙA±uïF ƒÂ±F¢ó/9H”‡ÔZgNŒ ´L#‡DQ œLÒ= ÛËzŒÜM€x t[Û©h´‹Pp°F´[ÔqüøG–eÛ îÒ$ ÿVÄfEpD1‹xy‰¦"ÊÄ}0ëF]&ÉÖQRá,V£A9æK®"¨+‡ŠD§^ïµzË.}‚Ç;„€3m:8Q؆Ù@îl2µD êÓ@ \î7¨ ùaMP/UœC;ΈôVVSSqSì%AG/È2Ò*]V F³ H#ðˆÂ².QE …@ÅwF­ Ǥ)V™×*pPœ³fø+"¼j‹L@Ã?vHPGl&2 d¤ùv…öÎV¢ÂÝK¸8/ Áq Y: ‰XAåixE0, _ß;û¼#Àµ ðw.ÿÒgC¢¢8å2d}‹M í88‰^B˲ $~f‚ÈìãB.½}ÏØÇJžÙ'dÙXm…zÄÐÕ FŠ,ÔÀ0r hÔˆC›QYK-¢3ìsu"QL(Ì8p­£Þ)u‘>f@œUà”¶*¨8* ã$ª*¢wÓÕ«ëUÀ})Ç1ˆ6pŒÌW[Uñ¥¦e+ùÙÍâ­ÉÀp'wÜ^‚GÑDµOƼݛÄ0j4TPO•âô QSí,m2¶¬ ›¦ñŽ:_øHË êziøMf%8„^ ‰ÆÆŽ°è¿Éë VtI¼6à®qûZ{àLE‰ÜrKFÅ¡|IUO|‚a(M..Ç+Ã_º¹oä^%„[]­hT"8û”=zMØÇ\·ô ¾f>Ù÷ý ¶Ñ±:¼9õt+8W­RYP‰Ü%íPFPA³ÚÀj”Dh)á8XÙÎmÍ‚‡EŸˆ„;²‰Ê@8ÔZH=œAxëB(8H\±ÅßhÊÆtÿp`ëgµé[·ý5\IItE2!Ñ)Š/IuS¡¶··?þÌ$ëE (ëð ëë8³=EñK. @o¡ø6<ý®@HQ¼D¾$P€ìÑ Û /L‚ûQñj’ê™ÌsU”°¨-Xªøx~D|ôA4 Qùä¤@x|áØQ\ö¸nôµ€M´h)„!PÜ4âž‹a!\aèu¦í =·§(P(ª<Á‘ P„ÞnVçf*XDöFÑc¥ŽPïj,EÑ„PŸµDžAÜ­ù7)ÜaàMh¼ ÍîSÏ_Ñ(îKj¹÷ëD˜Dá@OõpýxP0ЉGü ‰Ú\F$ØOlˆ^Ŭ¢X´ÉîY€N²P¯ì_€Æ«ˆ¶€^ªþˆ^ äÖ+T¹Ü§ð@#VĸôÒ@@ à|®xàdû^^('œŒ„ÆàŒ0ZÇAA ˆÊ½ÇêP—äÚÔGÐÆ ›’î ë)´… CQÄ]DqP2 rqä‡|‹@‹AÅÈݤ’ ä’Ýæ8+â0N$\¹AbW}rÛJ¶Xhnp‡!uPF(¢<ÿ7€H=(!4bz!PõÓ8þH„<ÞÊ7¢1­9,ôKBCfƒ<_Mø4Š ž%9Šª™ì Ý% „(7Ù'‘¸9»Hš8B‘ôMßåtíÇ „;À€%VÁ ‘.+ÌVÔCŠ~NÐP><3LûÒÐ'©A%à  gTÒЃ“]7‡f9¬P›ábY ~à…š5W¦ I"Ú& Ñ\rD ®N 쿈ñ$9š„Ï ìE jqú z89g¸V9E‰ÂŠ£ä@I¸ài$“pj9­$ž´BáýÞáÅ$œ@|9H(z2ì9ÎÀ¼G;`ü†LÜâFŠG ‹hÉÿ ŠG!ˆF!‰ yò¤9ìE°äÉâ’à V9dCÀ9`pÃ}‹KHQ• ÝÇÂ~qs½Jïjêp.uc:#z•­fÝ$ž Ð.Ì3±‡qWp‹Ø÷y?µâdØt.ò NÅeÏã.HUÀÔ­xøÎëÿ5l‚X&C«­ºd› ¹Ô‚³EƒåúuZþ‚¦Y‰uü/·-h¼ë,}äËAbìeë:¢Zkô4Dp¹A‰Ú#"a¾±„…³l —ä.2:;ñÊõ‰­¨¹¶z\ûFϵ>”!ß*%Ø ‰FÐÜIS Ú œoNmFIt+EDB{.1ix[ |%@nÌ$'ËVíØÌ¢ ‰€”¤&€NX¤ iû œ³~/§Ä†ƒ3h°wç‘*|Z¸m¤ s9°ù”m¤ŠÆÑAôH´7¬FŽägˆ¶×W‹ª.;E µåSû&Ðu4çhÀŠ–)¨æ_ä+Æ› ÆÌ $·€ZïïäÉòä;Ç#jð4'âüê~P¸1RhøG0F˜? 16_·=áj_0 WBŠI Žì.ÄQü¬Œ#ûè;÷uŽaQG¤ü*Bª‘ðÏÂI×7gUÁ8Y â¿.{“ææ’‚ÃÚMåÏðVPdxpP‰ä¨5± Ã6¡»³-}#jÐ&ËÞHó£ âË‹MP¡‹ QYXþÏ € ­ ïÉÈ‚l¸S5I½¾¶Æö¨LUÂ[ ä©´|ãToYë¢7ŠH³ ó@P0’-¨éX¡L:çW*.ƒz dΨƒ¨€'@ÈQg„H@Œ^‚\ð¨p© 7š +~]_R0ÎW…¯ú†}JH@'ãÿŠ&¸ J+, [±0Õ Çï’-· 0ü ‘s4^$·W‚ÀÈ…"(çmèí*hàê·h$ÔvL¨<ÿÙ™Íy ·(„¯ÏñÚÍÚ( R­¥úB,›‘ðéCðÔ©¡š\±Tϧ‚ßB``èRÙ9é#ˆl7w`Ï-@äéÙ[°Ãë;ƒsF<# ‚#5CÀ G«8 J»AÜ€èW.ã…HMÆÀjtDöÔD´„¸Æë‚9dœÛ½F?Zë%~ˆ5X1‘ÄÐÀå`F-³BaÉ$‡ÃДƒÑÙf„¾¸;V´ÄžÝµj½ª<ð¤z‡½ÜKÄ躛S}ê2V–Ðë'O¤Áf8RœVçÃE,Ä6Ï`ÆaSUýh4þ— E¯Zì „+ >¬VO1ºº3uCðÝ„p%$n$ˆÎZ%^&té:¸¼ÿ)x&L¾‹ÒL6ïAß œ™Ìòµëk+`rXdèxî‚.[¡‰uÛ^~¶QŠzBC×¼ûù0rN9wH@ÚêÞÂ% hŒš²ê²‚YŸlH*VRëã¸Ð!‚eÆ¿½×Sr``ThÔU:WÞ(Å=‰šècè…U ÀH j2Ê7p2€Ïf`ðò:º  ‰  Œ€£ ÚZ±*9 d,$0™Zp x9ê±ç ,FFŒ ¡ÔDnJˆšuc•’4EÉ‚ $ìÈ“Oò ‹@ ‹F ížÀ'›\Æ@Xб*b6P%;4P"pyHN~ôTEÿvd—<Pî‚H ƒ¸«;û<,"¶„hˆ[E—•±¸ËËÁ.ÈDÏöìs Æš˜šóœE %«x ̆B] €X±5à[ð¢Rî݉BähëÐÐd{¢•òȪp“žâ;b{}1¡4÷"ˆOð¨F|g#'ƒ ð´M¾²Eº Wð„ Æ ¡P: QœÄ#W%èíg›%tv5 yÙd‡`M¸i ². Þµ@²ÈÌ"(ÉV|Ô^Ë$ˆ9Ų(*½mÈ‘û“ðWäl¼D'M13 ¼¸‹al¹^xY’ìfôŽ4¸3 ÉYWœGY…ÍÚ ½`Â[ÅSK8€ËÈ 4ð|LIDâmšíÒ hQQPKî£/(M‚NS‰Vo@È‹Ð:åZD×*¤° [!# ­’;ÐaQÈF"J^€ØðÏ<ìaÄwìú íW$“|“dÑW¬È&hÖf{CŽƒN(ÿ,4ÿ$P ¶‹7ÉBT‰Íû2ÅP ¾¾8œ¤bQž‹Má"Î×_ûñ@ ©vòCVç}ZVÈhcÁ^d¶Â„‰9t @jú¢Ÿk»Aô, `P-\Û±5lf‹ÎˆA¯Å¿-D¹¿U‹9;Øw4r `W}s-êVÑ™·r%8-ܪ¸vQ+ÑSA[À¿d¬Ç Áè 2/÷pÝ "×r)ASh%k ¬+€0k±5¢Lð sYƒwÜç˜Su„‘Ö@ôÚÛIÀWæýWãG‡_.Û¨ 3ñÅÝuïIˆ2å¨ÕÇ è…íWø9\Ð~0W‡MÔ‘r¡¹|!dŸ¹ÀÎWQ– öŽ ÍPÅ}ø!2|)QÍŒ¶ÄxHlZ •Mð«3:hWöãBEëÝ,~SmK¹(¶B »4Ç+\ûÖf‰ |ç[WDøR@ÆV\E7S‰|ª‹¶ V+ÐTÉ"¸ýRAÃAëØ$sš™a¦Ø–Îv$ÜgLcê<§¤XØŠfþ`1×T‡íÆú+˜DÜXË.^”¹_Ô«ó܈¨P‡ˆž]÷žY®sq‹ÂK³t#‘$(,ì=v†PV°Im¿b[pø Ùt#6—Z|Uà@à¸X±ë鈲d ø5•/e–ˆ-Ù’P×¥*ëHWQ®Ï L5ûÿØ W:&»„€ð^j\ˆ`ÔC¥,‚ÚÉ|F&Ï<ΨuÂ,Üt T·MÄë/ýPoHSÁ$L ,P»ÑH2]ŒmŒ°*8‡]q«‚&øýAWÚĪЎlÃÌf97p%>duvNPœŒsÊä¾4]™ƒDÕû©)éÊ ¶>i©€ä½Wdä tÖ„%Á¢ºØ÷R{äsŠ­ƒn^ë Hø®*«DD{XdkMJFu¹3D]  ì”mœ²Œ=û\{ô8^<Ûº1-tMXá¶›8]Ì hÄI˜êøXFG¸-m0›C6 `¸$DÃé¼7[’¥>˵‰5¸ ¬wÊMRýYªEqÉV²«ý2¾|$À=9…L P[ÇÐpöt} ¸ÏqؼDÇÿ5ìû5ð¼A:ÇSµCs°!è˜Íš!%™ ¾0G´àÚ4pòË¿:êrXAˆX9È5o|dFADã0°`ª0ÏD¹+ñ°3ND‡‘ H³; ÆÐ ]ôÀŒ – £°$וJŽ6®û÷dæKíwgR‰q{¸9 ›,$X $Ktî]LHö¨Äš’AÐ_ÆKAB€ÿ¨ˆeAähÄGDÁºŽ ª”Ea a;ó’³€e9tVòýÆ€°€½ ÅWÁ_u€`0eiÀý!TÆ@0]¯äˆA0Q7Á FXhŒðƒ[΢½H1S·ÉHt Huâéš©ÌüuŒÉhø¼Ã(0áËëjG v69QÎ]r•H¡4PåuÔRÂÌÊŠr°0Àƒ`Ûƒv@$'Œx„xV= Y:‡ßü{(Ô\4àûA lMGÂŒüøShøƒ©lF|ÛïÜ(ïÿÛ¨/¸ÞCÑ…DgÆèÓãðhÉ_ë] ØŸ† E÷ôË.©\³3g¶°häA©jýbõÅ’àbS¼Ì„ÑW’êØ'g3f%xÒw› $¼ÆãÜK-5‰E¤=ŒÔB!Ô„ËiF}z« ¨©*ŒÔдDh‘äO§:_9<î¦j²¹ öŒ s» SQ)ØxrØo´É÷(½Y8ÔÙÊ*P7Sð,h¸‘XS mD0°Â V0ˆ!ß¡ÀjÕѤ€EˆëP©wVœÄ ÐÀQa€î-`›žð¹w YœN8 †„]Jäk„€RHPP?9 . 'r ·ÑŒLp&[Dy¨áj PchIGh=’­*AìÆ%O ÝQ…ce -ê±Ø"˜èºÒl¶l³Q²¹PÈxeA`€úÀf}³‚{énÀíˆ-ôÿW° L"Sª6L•…ï Ñw«>Q%™¦SÜWWô)@(zfû:pljtg.Œ –˜=[† aõM^uB,ªÊ=Ž`eḚ(j¡É#ÛJ$F¶-!CH.L<`HhÉ>–OSpF•ÜÈò)ÄF¬,X‚›Ç O¬8‡ž~0Œ|o{æ@—jÑ”‡ˆ‘?Ãë":UÍgÔ²O‚q!Ãj MPž› @¡ Õ€B”¸«U1€p5.?xkL8ôöšÛmÃÃ(Œ$É- s>T5ZyMHÒT(Ò™#º 38jCK³Rà‘r%1@ Ò­d[3Ù ]U<B¾”l°„&¹·nâ~2€­ LiFHY…À²ÂA@VúIpItv] HEÀ:v Uk#†UmˆepAce„ Pœ>QÈFãq>BVÔj%;›mâ½› DØ eÛœŽIlptÆ©ˆxøhxØ™À¢ï>xh2s’ èkÈB[öàƒ >$ ’ š…DÃlYœâ»l™é¶Æ„¤€³•}$C³f(³E)Ú¢FT_ù£´l›¡ ¿”˜œÛH¢˜€6èUp¨¦=¬šâõM”ÕÖ ò½u = :'*"ˤ¨ÞJ·;Tq²D¬Wjé°´?Zì`{ßh¨h´I ä ‡º‘ØÞh(…VàF× {B…x¡»|s; ×h %™‹H¥éôï‡Ax‹‚SÕ%›T‘ôž³j!YS§46z  Á¾DâC'¢nd3}ËÜ1Á!EñìK”µ9Ö:X # 'G –äîp§5Ùh)+\bæO„2ùÑôªB÷nØgÄ«n‘#Ç$à-{”2*Ÿ-Ôv¨ 9„ëBax£—ÛÔñt2+ãøwQÖS[Ô' ètrŠpEDêàtmï«ÔG\¡šíCëÆ1ðý¶ÿÀÀ1uâ}ð]ð¤ë +÷Žgk§‘=V:ƒUì‚3Ì œ4 Õ_h¯±Ü#”Ãë³hv\É¥ÂFq$‚ŠÃp0Ð!¢y•Fzwr£x4½Á¢Íz¨t6#ì’C²9HD?H<ò¢«b²>€Ìÿ%\aXè{i3TÌÌPd¡œP[ų™4¨%‰¬ä™évPÃ3PVºŠŒlOLHÂë† s¥ÂÓàD°·¿ÑÐÕ€áÓâÃ3ÒÂáÓ~«8?›n-ÖQ=(;·t!>mÛc¿-…sì+WÄ ‹¯  KP`0Ên¡ÿ¼H©Ä":¹ˆ F6ýå*o߉eèÈjélÏnü@Ùƒ ÿìæÈöÑg àÁ îïÈÜ¡ £ä í·dè9€sÏîlmô( Øe€jô>Û‚$€'Ò¡Ø?Ü›i6·”Ô œÌöâEë>8{aÈ×µ2[$¡n»é0¾Œ€>"u:FŠù¶[p< ò k¶í vòÔÐN¤ðÖöÛ[´T·  Ôë+ T«úÏvØëõj XPAnk‚³{ìóÅ—˜3Mlø  ìG ‰‘Q(”J|„̪.‘§²9×$¡4¼‘‘±0, Á_   §€<îq[‚0wÃø@› >±°éM”ɲY6 üä}ðÙw(€¬ l‡EqwcÃ/Ù"ƒìð$ßl€ U-Üc)»,w¢$ÁN$°vº £²•d(·²PV¸„–éÁLÃvo$‹¿¸Ücì²;÷%ƒÁ (;òf[òJw¸4[v£70k…”¼ü¸`.ˆ7—;#,Äo HlÙÙ%hMÀ87φd"ü ‡ K°ì,!éD ذ—ñ¸hŒÆ’Ɇ;°#dÙ0Ẹ̀جEïñæ¾.}£ªÉB¶,l’ØkäG$Ûì4¼+á ,¸ÄL3¤vèL» ñét%‹ì€ðÈž-$MïHM+¨vl'0d„ÂMŠ$’—°ì¼3ŸàMceÒ³¸NÏS%V²(NãLNÁf76{x %Á!ßÌPfê Ì)Y2àÄI+‘ÍØ"<+¯Ù±ÎPéM+äNo šÐ(Dà6Ã6ö¦¸TO¼*3ÿ: ƒlèŒ*{XìBX¡PJ !lØÙUC1œ°d²WÀäÙ RCÈ^9ßX½‚ó•Ý -Ø2¤î/ã“íç–&;>ç/H!0+Ä-ÚÒ/X ƒMØÐ/ ´ç#ö…äüRäS´MlS¨X’ÁNÎ D9l°å+¯€”-XXßB.l sÙ"\6îz¼ -,°‡<}¸´/”Á7{`4ÈÁ‘½evæ"J“-ЏF!;—|»\H8²A0Á;‚0I.¹ì0@ƲeHäèTÀd?Ød¾Œ¬O¬ È {ᕽlgœ_{À_‘ ›ìSÜ O¶aGOÄ `¦²eSL*+xW¼sB¸é›e“M6Г¸½ÙÃô aÂndG?Da»“0d²pƒ”VÂ)aÀa×VÒÍ÷GìûØv%Ob<l%È`„s F—0{EÐ2€œGÈìˆÈnÊf/OȺ_ØC6à_écCä$MGtc`Ò…/˜àc!’/°ÔWòl¼¤Ód(-ys`äÀG¸L r,§ìàdaW ¤+Ð…¼„%Çôd !k#Èe·;D `h´Ãä Û #eW¸­äQ¶ßäe#9‘,ØfÜ×fk¿'Ó›Y±d܇˜D’gÇ‘œ[gë”ge‘²í(Sg·ÏgÃ&aYô-÷Ý¢…Œ… hÿ“‡x“ß0~(d*a)wh.a àãÐd“tÀàiû¼‡” Bo|ôèàAr/ i8iìœ?\i€iWÝÈ®¤'Èi#6Œì ô“j¯ÉfÀ/¸ðÏØ0Ü’Ì[6°b-¸!1qM¸ûjC Ë&‰À°wùHHt Wƒ©–Ä'eWHk§@2Æ’-dG°a'] ù!vaŒ- dÌT!Ñ HH¹©Z pà‰>ùXØ ‡””v`ÙÂ-ÂR Í"†lÿì á|l¨µ›Ül¯ãò m,mc ‘ŒÓŸ1È6œÐPžYÈb’|¬Û,;œ…Lœ*Ëf^x¸€r€“Ü™§bvßKÕé–¥¤“ (XÒ²×½’'t ]À/Pn¸…,çnO#€AKçn–’Ë.  $ 9»ìH+LaäÏàÈž_H8ioÛ d'{o‡†bB,M#” coïK¥…Œ4‰Ù²V——?±h± Dì«kXÉ(pz“?À[Ùf2ø?Ÿó¼äö¬¨‹r#¯p·†O.·'›–}7Mü{ˆí¶Í¶s[ ‚l(‡+/Ov¸kÓL…@7ƒè@ÝË6Í×Céwîƒ_ŠË®Ynƒ’—Õ‘eͲY6o’yÄGQ®;Ût[§) 3M×,›=ÇŽê '¯ëN6ÓíO÷cÍrÙu·]‹nŒ{¯†(Ò@;G¶’§aˆMr¹Ü¾±÷ÿ®ù× Ù.¹Ü^ßçàåüå·4Ë®i©Äï$¶;¡Ûíßëío•kß׿ lw¡Îí+­ÏÌvìØ A‡­ñš¦Û>l ¦°ºlºÎm‰ýK’œê»Î\.‹„ëþühuêç&ºc_Ëkº³Ý–!à{p¼®M“}˜ȃA÷©c‚Û³m¶ÓÇ#ôÁi³Üvû{vÉ–Á“vMwr|Á°ǽíº¦Y¸ø#­79ò•/±Á@ɰ÷BÙ/ /aG¸ýi#Š'ò| Gr ) Ù'&[B.ä!$C2d2$C2–g;ˆ,Äÿžu¹m`HV±ì%ùÃî`…A é’;0/ H÷Î~!;PHSøO+Aºnä#AAšf°#.`¯iš8@cÎ6ÈR\7#g›šn°Áf?n'vlA£®€'lÁˆÌ×®ißê¶lÁšÁòýgÏž$$W$#ÒðÍ . “"/t á)aGx€Aoì…° I3¼IW Éä@#ÔKØ`gT$\“g3`CH3ø|Wä9,J”Ÿ+liP´OB|Ì§× ö™AâSí;òäÒ %%a—{&§ dÏ$'±'[Ù—ñ*@W7!wˈ€´Ê+@ݳƒtM klLóNøx}Ê3LI˜ìÓÌKœ'7îyÉ8&àLP¿ø™Aš[fn&`ÈË'M€_ È6@#”d!2„¨ˆÍ`ƒ ¼§ÇO¬Ø€ !ÜØ#2 Côür¼ä' N !D8'cË4ÝÉ@'KYs!¤ìgChd€ˆ“žO>šA©ï'¼'.’ÍI3¬NÐ0†™ÁSØ£‚]Èæìé7üz¿÷g{ O/O/„,O @OÃdìôü(ý­°g (?“9@îB6“§ä§9ºïpzgד¸3È`_»È ã4Mpè (.—<+(,P@Á+KXÂd`h„P¯È„Ÿ ;û½€]óSÀP_‹e.°gò’ˈ(Q2„ Øœ#4°ƒ !XÄ÷ÌA>Ót×_âBš|ôÈ%‡°¸#)Ü…¡%CH $Í•ƒà$ùÜ3@{0Ù³ÈXRD)+O)÷açäW)3l)vÆÁÎt3)«‡;ÈÒ °œÒ 6¤+ì2ȸÀS>™A.ÔÜÓç)Ñ“'Oò)ú)*4MÓ  ƒ4MÓ( ;C9;Ø`N3*‹ò¹!¤D`#h“gÏp*'x*‹ƒ*l°‹“›£l°A««³¾*ìäYÆ*Ñ'3+2Hל; ìKÍ`ƒ S sANv?+{ƒØ¡d÷[3 ƒ 6ckCœäää*Ü#++愃‹+¿#а“'‡ +¨+°Ad¸ÀÈ/—ÁÐ+WUä+d ÿ#ø—B| , È€ ! Ä2 C4è½@ÈH V\,f°Ïd'o3lÀ†0„Wd#!˜ˆBd¬¬ÀÃA†ÐÔIƒ<ܯ,,#Hsöäý,-KôäÉ“C -(-3-9yòA-O-]È È@Wx€`ƒ 2‹–¡CÏ !ÍŒ¸†f°Â+ÐØdÁ»àëù΀'..·.¤9$ÿ.[ü`Æ!@.ÿX“'O^T._.j.M7ÈÉu.ƒ‘Ÿ°'Ó4­{»cg‡f |Ô.ãßòäÉ“.ê.õ./Kž7„]¬;0#:šnÁ;BJR¦¤iZedpx€ˆ¤é“›£;!Í è¸;yÔKtl‡Ì;†°K;˜+ø òq;3ƒuiÄ$<<.è%øl<'ÈI$m<<ìä _l=÷F=§NBš³=s ø`#rllnt‰!ˆ=s#GŽ“=ž=©=´6É‘=¿=´K'ÏâÔ=ÿÜ=ç=äo£ü4o'O^ >>&õäÉ“>1><>G>iΞ‹R>SXAdhp{Øl†‘CO¬—2È C¨°»È ƒ ÆÑÜF2çð^ ›Á#Dp?@†Kh(!dŒ<°UP™w@ÍrÛ¨G³Œ¾¿òS\6%¿µ[tF5·€ðiìP?$¦ ÿd³gë.HS?AUCSys|1÷ÿtemExceptionž#ÀÙÌ|׸/.7z?egÿ8z¼¯'WVCInAÁÚ—ýrchiveZN7zþ²eg7LZOutWindowìÙÀ”[BufferµÊÜÂ&×E$-aÙ§PAX7 Ù°Dß?*T+bÁjQ-”b•a [Ô´« O׿-PGsBá ÐU¤,„ÿÿ…&"/:<>\|OXøA–Crea£E ËÞ^4nt Ïror3“-·åýZipGp‚[9o²±Ûne]#% 뺭Å Qe y-uî}a®swaAC7²™tcc#l»Áþ0Ã{0} Tƒ5MÓl\4DŸëšÍ9CaEcrMÍš›Íu©RYFild÷ÜçÆUgp+ËnsÝd'M h?d½îu_‘ o§põÍÆšî =tuf]²/63dÏWe7±cá/an no‹f¥ aæn¶›Ï leûãef;ÈŸºu1t7-Zip,„ý©±!@Áuall@!UTF-8!ãÖÚ€ET3zS±æ†F £%TS—ìu. x‰ólÒ5 rh³vïl„ cåaï®ùÜmAëodKr7Âæ&R‡CP½gd›û\a7BiB÷½MÿTt= á=Ì‘MgiíëÞeŽ-o§'£o {/a¯d­9e§ÂX#type_Ÿ¬H’éfoõ·¼.‚½ÛʽU©INëOÐT˜(±g L'@-¤{GÈ€€€ÞçîÞÞÛÞÿïÜãçÿ'dK ¶» vXsa°°* \Ò l;° ·l,„T°C䓽°D D»°É @»?@—Í™_ÛoOȶ°D°@u![$Nì¥loÀOÉe“-_欽ïáÀ #a¯Ï%ÄÊ9îç3ο{ÈÈ€Úº7°—3eÕs›©y ShŒ]÷}l Dg±P[Ã5Ý="/KØTCVΖ@{q'ÙÜ]7¬'èm[!t`Ù›_pu32‹6„ 1ÍqGV¬¬5+'4„A'K x®p õx93Ÿ9"{¦Åé‘Ǻ IèáfC¨³09€ÿÿDeleteCriticalSecž¿²›»InizClos¶¿|ûeHand8WaForMulp[ëÞObj&s-tº€{$.1Thd{öÉ/Sing-Res.dÛí'VirtumAlec ½;æÚFModNNamÖºÖHL¢›½·­ÓmæMÈsag.ÏÞ„W>HsDX„›kÒôyA%y8À¶ÝtŠbu¹sW”-+| mo9”-ÂWg#ÂWì^ ÛnÏŸSh$tP1hñls²ílknûFùl(<Ù@CuoßÊBfÃà=T„eçemp kF¸Íb)e rsºMvSéMö—Nex,p|³” ­ SßG×ÚIqPo= ER©¦Ç“=TimêÌn3b?ÝiOf ië–gL v4Û”½+EZS>e Ží°p@PÂc/ãµ¦Ž£mbL’®|mÌ&%v"¿aYÚ6pIXAе>ÍÒ¦ÿÜÂø_c¾iwÛetVlfd__d_app_µ·ç _fmVá6s‡ cr þjuS-Ø[div7eÏÎÕ‚hòiλµV¾mgŠgs9]+¸öcmŸØƒ!Xl„Ãcp@U_‘`²m°_)l eàíØ§_hßr3??1Ú¿$7UAE@XZ‡m¯iÛRyftx%9ǧ{¹vpue)pý)möCxx|Á:Jî-z·lH^ þÍon2@YA„I@ZYh®ù 3X ZÍš­+³d1ô Úp˜´Éd@!oy×°°ºÖMwBoxKw6PltCDd†eí±ogQ <ÝXŒñDlgI k.8›±,Fø^T>Fæï†oÅܲdñ -’›¹kev }Pб°† AE1+Û°°G8ELÄK@ÿ%¤ê'°"Æà Cï5Dw_! _ Cñƒ 6é\² +XŽ%K/Ôpx­ù&+Ì †WHЧ¼¨.tY âMv×N/ëäXúF„N`.rd)aÙ fû:j4f›u .&' ü)iš€nÀ.rsrcJšnÌëOtÀ¿ßs;– ÿ`¾ A¾ðþÿWƒÍÿëŠFˆGÛu‹ƒîüÛrí¸Ûu‹ƒîüÛÀÛsïu ‹ƒîüÛsä1Ƀèr ÁàŠFƒðÿtt‰ÅÛu‹ƒîüÛÉÛu‹ƒîüÛÉu AÛu‹ƒîüÛÉÛsïu ‹ƒîüÛsäƒÁýóÿÿƒÑ/ƒýüvŠBˆGIu÷écÿÿÿ‹ƒÂ‰ƒÇƒéwñÏéLÿÿÿ^‰÷¹< ŠG,è<w÷€?uò‹Š_fÁèÁÀ†Ä)ø€ëèð‰ƒÇ‰ØâÙ¾‹ ÀtE‹_„0œÈóPƒÇÿ–É•ŠGÀt܉ùy·GPG¹WHò®Uÿ–É Àt‰ƒÃëØÿ–Éaé2`ÿÿ8€¨€è€X€È€X€€€Íp Òè䘕èäôÀ€ Øì—¸ä€0€  ¤˜Tä Hø˜4ä€x€¤ €Í Õ"ä¸P™äà€ ø4ÕhäMAINICON’( @€€€€€€€€€€ÞÛÞ€€€ÿÿãçÿÿÿÿÿÿÿÿ»»»»»»»»»»»»»» »»»»»»»»»»»»»»° »»° »»° »»»»° »° »° »° »° »° »»»»° »° »°»»»» »° »°»°D » »° »°»D » »° »°»D » »° »°»@»»» »° »°»°@° » »° »°»»»» »° »°»»»»»» »° »°»° » »° »°»°D » »° »°»°@ » »° »°»°@ » »° »°»°D » »° »°»° » »° »°»»»»»» »° »°»»»» »° »°»»@»» »° »°»°D » »° »»°D »»° »»@°@»»° »»°»»° »»»»»»»»»»»»»»°»»»»»»»»»»»»»»€€,™ èPAd™h4VS_VERSION_INFO½ïþ  ?ÈStringFileInfo¤040904b0Comments2 CompanyNameUltraVnc`FileDescriptionUltraVnc Self-Extract Setup8 FileVersion4, 10, 0, 16 InternalNameUltraVncSCRLegalCopyrightCopyright (C) UltraVnc(LegalTrademarks> OriginalFilenameUltraVncSC PrivateBuild6 ProductNameUltraVncSC< ProductVersion4, 10, 0, 1 SpecialBuildDVarFileInfo$Translation °DÙÙQÙ$Ù^Ù,ÙiÙ4ÙvÙ<Ù€ÙŽÙžÙ€¬Ù €²ÙKERNEL32.DLLCOMCTL32.dllMSVCRT.dllOLEAUT32.dllUSER32.dllLoadLibraryAGetProcAddressExitProcessexitSetTimer;!@Install@!UTF-8! Title="UltraVnc" RunProgram="winvnc.exe" ;!@InstallEnd@! 7z¼¯'+ò‹?°$¥ŽV!Pž> Á6Ÿ!ýÅ_8ÝîÛà( h"XЦWꯓ\œù~ä ÿRk€nn½¸¶Ä+Cà™ÞvÐcÁ¹$ äÑ`¸ýÃS±Ó¢z’ã®kªµ@sƒÃ´O„Rì’!ôk&ZüM­êRœk$®®0Çâ/‰©¯CÅ[ ˆGJ Ѳ ¤ÇAãÁˆÜLN_=å¢÷°ë…M#h~ÈQ´Š@ÿš»š„£„ÌÚoѽíaßåÚ‚PòÎc…;2ÑÛÊÁÍAéˆÞ€®«þéÓqˆk­]4…zU!†@øÆ >ˆlI»?לÍN‘‚ò$¬†ÐçùÂ7Odt•ëÕ>Axâ¾¼¼Òj W´<;éfãßnØçÄ—hù窼o ÙKRZ÷eoÆíÉê󢩸¡6-T¤PLlSŽø… ,WÒ×”J^n`µ±'1þú< ‡q´`˜V.3;Ù‚1Ž'ôu Õ:Á±¨ép÷ù[þaŒÔûm°K'n‘"STèŒP¸ôVJm bàIiEŒ­]x± Œ¾Î(Õ›-LC3D“ äéªÖ쬇À‡âX](–¿‹Oþ;R'þÐáÅPh¾'?ýâ•%¯´sýM~;°?#îqLj¤·èÆ6ÓüCí" 0YFÌ´ NA°7¶w´¨džè cQBÃp—T¤&bÉ0ºhûñ-m®[f‰~`!y›Ju”sÿù^¹ “1ÄG·° 0Q(ÛÇ|^•œ®âš]×0¬ƒ21O$.jÓ<·3lü™ÝVÄð±½Ì°tâoUš–.P”4.wæºý¶8éh1ÄŽ ¹(Ýèv!zÎñŸ|B4çþïE¢‡ú®>R€4™`žŠÓO–›owopùuGý;hK‰™}C=ÖžÔûŸ¹W¼j_ãgÉ8Œ.îÒ1' O"ÀÿX {Ùà~O›œbpñlöïHTOÄ]–CEæÔxÌű½CWƒ­ÑÚ1WŽ¡ƒ ýe ó âl=”#QH×2ºX)¸¡}ðÁ’|K{[ñ[G‰_)AYÛ½—.êÆ—É‘;E²¥q1+ˆ—Ȱ–"Óu¦6‹ý¨y"Új™Ñ&¥i刮9Åùámwm|ÀnÖ@M¢Ô RJÄ#ó%t(Ëûø˜ÒÒWW"¸Û;&f~ÿ?ƒº~ÃUa§c`ÎÞifäõP“_w+åè^äBYÕÿ~’˜½‡A\Æõ«¦ ÈHö‡&ˆïÒ\v¹a2ÿ…z̵ùz2¸fA[N««üÍûÍóª×1l¢—@²>?¼:Kâ>%dKÖæm¥?"뮥ÓzƒörceðSëƒâZ»zim7wê¶+C¾–‘-…г˜Þ€´súйë¡8>â9A”}0´ÜÙÁ¦‡u1yp'Æ¢Öi‘hÞ¾¨“0“aØÄ;¾re’õËÄ'n€ ˜esz{ÿcªˆ´“ðQ' Ôçd%Žž6ݼí‚&ÍÉß2KšY¸ƒqo³¼¯9Bš[ì< ZÑ÷ÉoØ<àšî>_iÉEõì8%Êï¡àØ‘FS78œO¹ *Ÿ¢"&³Wë2ÆBZÖÉD=–¨û…•y$`»óÇo¬µõr¬Ü%H$ h£ÍÛò-—+ Ó€”ªK€~ ÿu–½äðåæ6ÂÙeZ#HòZa·yý‡ Þ=—­“NŸâ-Ñ1Ù­*>C¼#æªd±é"ö®f`QˆP̳$|½¥š¢úXª¢ÙÛ]Ä[ÜmûûUÁ'?'Å0' %:›x1›Wׂ¶Eú­_Ð7ð» ½bÊùµ‚ :}[éÕÆ7¨úú§#ÅvħEtY`µ2Ë¿@èy|»oU­–áÅÉpñ²™ßò¶›þë¹Ì©ô×^¢Hk œò|>ÖTÛ5Œ»Ä j²½{Ñ“/ÒY‘1I˜¸<7Äà2²'ì ƒªaÄÌ‘Cý¤“È^3¶1c¶O®²nÑǬýCd$É4Q¡£±Àæ|ŸƒÓ¡í†ùd&ábE‚þùq#k5b1uè·\ï Mý)C½åz]þº$ؤ7C$úèïÐŒ”o[>ú[6ÞmÁM¢ ‘r&u:ŽG{¤E®ó§žBwãwÄDÂÊ3…¸Ø8ðø”`&8>QðA»ôr Ö~-× Ù’ÝÐÐ1»ªÐÆãlQ2OÁ‰zêÈÖþ–;q €à_\& zêY²AR3eèÉP¨,†Û&ªŠl`FȘøîÌûŒÆÐîQ= ת”$ž/é_yÁ-Rp¨ðÀºõâµäPŠúõ³5¸%ᤢ,œ¬¼Ú.HJ+lÉ{n« ^¡Š"ö:˜-²úâp%ñ€×BÛéSv°r z‰›xÊD6OÖâ÷ Ôß+Ñ‚ÌáN+¶cÏ™ï\p{K\oˆ[9Êk¤Çë+»(Â<ñ5ó¿N‘£ˆ6?4ÕÑ`§RŠ‚JÞ5÷ƒÍÙO-+,ŽÙû@Îx€>; }X–¶0Ê]¸w¬ô~;¸dt˜sŒ¥wIÎ:îF,’H’«ñæÊ¦Ó¯,—9í5ðÙ\p›Q‹k«uq$f¥ÄoÅÖ *qÍÄEÎÑOYåŒ îŠ¸œ1I™/£Ù…º’¹Z­o|õBïNWg(:àŸ·„x2žƒîµKG´&æ¡ûhÎ i%™’Ñp[< Ðí¯|ÐrFØB\FÚ7k®~ª<Ž—R<¿xSSuž¶¤=«sIì‘«¸ËúŒ°™¿è¹Š5м¿Þþë*÷ÊNºõår½¦ ØSmjóêܱ–ºŒJ3[8ÍŽ¨°nGCÕ:w£û1‰¯öÌÓ ;ÇŸòa;ÎÓÁ/rb®þ×$YãPUkåwjšõäî­®~Œ aªÑ®‡Èñù4%{6]r<é ñÔç´]„IP^…M^ùô>{=T›ÏÁ­«´ÞÍ»;”1nËÏìöž‰ÜmcÃ^nÜi÷†bH[ rÆJ=«JÆÅÏ!,UàN\Ðf4},o‡3ë,Þ¬ì\£æ® ½¿<ÆCïZL‡–¿5&àu¡wàÿã¦ÎDÏ5î¢ÂÊg¯d§‚žä+žËËÆ °Cò‰´¢Uç%š¸i–Œêt¯Ž~ö€/¸é.ucp’䫼Éq ÷+†dl ÔòÜld°Lè‚1mö^‘ÿ1?l>3nÌw¿ÕÚHb0q}7kPušp_‘‡%[öˤp#‚%С×S£•':€kA€”ÿX“T#= §'®ýqy}«T ¯Û§ISÇj“2ÚÜì­ŸHðšTÏ^p˜ÔEIü<*{¤àÿø¼“¨zÍÀ—. h©gañ—é$¹Öðyä|e&7çþˤ"e1ðRwíÀp¢Ê̇R× –Z8æ‚° qžhgÍÁŒ›rOçÇdO!¡YZ^ßÊMÁó<«?qŒ»~ëèÃèo·Ô,ãûƒÃãŒ\Xs¡ÊÚP¥† [Û6uã·t@Í ©‰è Tó¹›ïqmYJgÎ¥©ÚÞrä[:æ¡×‚†åÿTTúÅu+Ù†Y™Ö†Ô3y1¾í#Û\bvVýŒ•ÔeÚ0$Ž×ÛÆ„5¡ó°vfçu–±í at»eh1fskÚ”ÛOÎEÁcöÖ¡é§êôéw÷-¢ÁYÌÐѸìi>RËÁ»Ö+¼8nmag²ÈæÕ Ü”À\õ™§ˆÎõYaÁ¤ÃUAóÊðˆH–•«Jݪð“!*¥Oª{³œüôÆÎ+6 „€ï~þ‡"ˆÏvhb?ËUd‹ÌþÑFè½D`„—‰HL•S9» P ‹zöžÖ&V·ÞwПç?Ü¡8Eô$+P(# C ~?¿(Ú¢,ÂTeÌ!Œf'”“õß®hÛ#3ØÏ“F§këÁqÔkªvÀ,;pS„ù¸Öt;pb6 îkþ³Ž)N2¿ëéZã¥úÅ B¼Ñ’1`ã¿"ÆdßZâ0sv¬\myFmׇ.Gïæ½w‹:ƒÇç{]}‡%` ®ßÈÈ6[!‹Ö®_A8<`l´²yÅÔB€-ëÞ<–÷÷¼æj?†¿˜$jbùäô&S¬»Ù¼ Öº‰e.Ó8Œb^\ˆ}œ‚Õ}‚Ofòb¤œ¿V†‡ëF>Þˆy—·3$[컶ΨÜ(Ú{Æg Hz*‹ îoÁ…Êʾ<‚OäËõÛ䃧ßÛH{öf¬>ø JÍ]¶º¡6#6õ‘é!HÉåwèúÞ™ÔAÀ8´4èÑÅ*|.‚)oO&Pæòi4Ù˜áîÑÀ>†$PzjèÚºjtL8ôÀ·u ¬óyÔ+9¯¢xñÄÀ=ü¥ÍLš80Án]$éðÜuÑdb7aÇÄ{øa_9ôä«ÿHs$ŒÉT™qGT6™êµ©ïªçM÷”UÌø3zß¾6²‚dÅJYý8ÿs·B\¤$$æL-ÿNþ$ß"å7$<³B'¸ƒKòÌå^Lh£ ÂÌ„•ÖË{×0 c~$\EàSÍ‘q¥¢­S¨ãk/l„NìÝ3|î`ç¡Â¾yîsàø~L³ÚI1Á$ÃÜÂ1Ljz×~ÒáâÍvÜ>“ÅØèÇ6Cƒ :u/^±X›woj¡ÐBî°è©Âx2Œs’‹tV±ëáñÒMCy6ÛH¼p–pVÚtZog|¹ù*eµ³q¥í«£ÉÀ³†è^ÝÆ³ô ÁbP…kÓTüq‡QW2d¼ÅÂLBQ³9mä~+ô¤c„h7xª_X\öß­ù¡„5kÆãu)˜^MîyR)Ù·­ÖG€‹Æ¢¸æô)¦TìÔXC}# a“HI5F©s­è˜ÆÙ &^áH•Àšb`•wü8XFžÄae”úänÄ ‰yO0ÚçÍ¢¤=ôm{aªÇm‘ù}žZ¿5b•êJê÷¦Ü».©¶º vÌw5„é´µ‰ñ ¹‚Õ´hJšÓx"þß¿¡çõê­É°õÓXu¨”•S.`-xØâþ4ÇÆXÇ‚‚oâÆFÄ$Ì£L(«;ªqÚ“Ó $S}Z¥¤ ×b1’³o-{î/BÆ,Äüu—îŒ+jr£Â–òóÖðì_XM¤hr#sêÅlñPfϘ-LOxÎÒ™ü5”›9çVâ’ïoDŒPc\Ÿ2‰ª ň%¯”ÜqèãÈ[«™‰èÇì"ù¨iÛØíP≨âD|åóÀà<3Ýq‹S¬ªï’õôšw ξ@kx˜$~?Ö&iÞ•D¼Í‹ÒkS´Ø=jA•°¼¨³=sË:aa#f*Išွ=EÚ…¸ç¶Ûq(¼u€C—4,†h9ÛûïpŠúÌ)Ä´, Á¦ëª•µöz%ÝŸ‚2zók™Ù0ª¤ÃâmP€Žö"±¹ûþ…ï€}62:ûn«ãqÌýŽÀì)éU$ø˜VÞ#L ÔáNõjŸëpÀo–\|û^Ü‚Àé*¹½œÖnƦì¯a /ù2Ý$XáŠ.èEÞ'ÉhÅ'‡'–öÎ=®’“΃ßòç)vÐeŜٮeÊ%’©ýº6ÓÔõa6fa.:1 H¸Gó[ž¥ÄAö[¤ k7BRÙE®Ýñxú7ñ#J½Ðù›lúK¥ltâÖ1|֪Ȅ É^Y¢—RÕ[Ö(æ ÖÌæ")F2%­†Ï‚¤áÜG S ’¬öoê¢â–âIM'6f+Rÿ£s=K­»Éšf°¯4|_{eIp«¬è’ð£šÃó!ôhò·æÌ?ѾpÚyŠ5ïK}㨪¸Ä Ès8sŸá<æ€}ÞgÚ'^‹sÒ}íÜ| eÐçáçbÆ|Ð~~pLƒnŹ{ÅÎ8a<*Í5û¤á÷ÿ[¥ùP“kMŽxôroKG²½4Âf6ô¾ïGXžDcugê…f«Úœ€j–¨‹”S'JÚ¸îwŽ!W„ÒŸÔYæ­<‰"òöŠöüÁf•<¶Ê3é(ÕGZÛÔÑ5 ?î×åãҪΙ¢Ù,Ý× 6ÒtPW fÇ¿eºñ5­øÉ+¥ìe3WÍ ¨ 2Ç 5›K×"g)NYY–WÛÄCqÿ%d‘3¾•ý’,f3 L ëØ½'$ÖtZ¦¿£°•éí ·µz á» 4f(²‹š±6aÃ7‘^_êæ‘\:Íp4/­¾Ðâ¹ÌÉ]CGÍ%£’5Pˆ}jU7¿¯ÓX¯Èûÿ¢¦Þ|(Ó"XjötÀ{åhFʧ¦œ”í¡ ¿¦ zöúÉìœ^‡Ë0“™\ÂêE@aPÅsx*ƒ»f@:æFˆ—V\¯¢óo4¢ZÕ:œLE¤Öɵmó> wdjU1‰N_¾‹®4‰3àÔK(PÓ¡0tvö¤ÔÚ¢Â_—þ$p¢È„g™õ·Ñ­¡ŠihYZÈžáoÄåyF„à©G¯ ”“-Ð+`M5”øyFQÏüw@ÿÜ4ÑU´ó®(O˜bxÁP©¤SC©+ª"¸(€‡ah’.Ä mÚfz/ñ3F£E¥C¨¤£'·}Êàö™¾z]9®ü†¾èR6 což} trLhçOé÷‚6´š:­ntÁ#Ö¸/p3Îm<<ŒBDåˆ(Äû«¶ø<Àx½=o˜æP]Ûrâ§dÈp:@ý2º}%ø@xkßä¬7Õ°uÔ‘-}Žõ­©E„8£ûF Ûf—§Ä2À§ %½ôrã4Ç–£hQ7—ÚëƒwK'÷»ˆ=iOŸv”•þ'úË]¨V’‹ÃèéS|‘9mtµwD«bêF¶/ùùÞˆ° ox=Øs¿'^”;¶ØŒ|.ÑZ](ÞåÙ'P˜1íâ,›OÞèFÞÍ‚ªÛ1Šk¡µÿ…üY’ôQö¼Vë‡ íÏ#sÝç±Ñ˜Ü4$ –‹öéK­[ñ $ѽŒæ¿Àûe¯4yõ¨/Óe)´ÒBðƒé~ ù3½ÑÞèÄ`‰¯‘o–3¸ç7VY;VÔÑï]Æ dÞİTŽ “±Dä×}}£6åÍC»6‚㪠ƾ²ƒ8khD‡ø«[Æâ¦³QÙY<FƒöT-qS¹´²I!TðAnB6]ˆd»$YKÍ|ö>è$Ò`¯>eq £”¦pÒ®H¢”îÄåµ^'©a´Éм^žœ3×µ¸3xç{$fͨJE€n:&XR4|Ƈ¢Õ`áçГGBƒ{}…ëó“ËX«h¼šØüÊàÃ’æ¾íD½ˆ|{ühÕi©!Ťté#9q#ˆ§ã݄š$¤ý‘[Ñè’–äC“ nó©œc‚9!¢ú×4Ø8…¤û&§ÅÉ‘þº)¸ÏaY„,A£­^¼°Z²ƒÀ¼‹Öc°!Ù~/¦ 'º³¦ñ5êOw><îSU´Ü£¼DäÖÏïû¸†úAjõÍ4AMyÿ[6Õù[€yáÝQ¥Ÿ5‡¼²Úý õvX(ã ~Œ£™<;ð{©3«£ÉÛ«j‰³wDp øCüÈ–èN?÷êyûºuñ°ûîFœ­=£øY¥^_$X ² èæ¢¿ ª. P2ÚPt2ìHÑ͘M 0«åväY)7´©3÷4ßfÄ,ŠéÔ7ªiæë jV©¤ÄRÆUŸWwrÜxCmnû²#` ,êø›YÄ4z"œù«Åo¾¬î³‡²¡¸ú½“qsœùÏ3Tó±¬lrþT¤HŽ»€à×ßz&ðæ)Yr% õÖŠw\øuZWDÒsâ:]½7z¤4> OÑ ìÛŒMö_(ÔàÀä h/м]2gÈ>ø Â}ü”‹ ×[@ÃzÏÁP¶úˆÒ”çÙ$x%ØÞ}B‡ÕdÁÙF ‘q¥ã¹ÌâÓì,@ATÆÇ9¡Ñ{Ê8/¤$µÙ¾ð A6ßâæÓò3!ÁªTûú«æ7ËÖ"<Ž¥~üí*»Êi ,õð%{Cû;ò' M‹¥6’(U¦Å;…+èÄÖT;íÙ*˜¹YT9»ªuœ ‹‚†«Sqµ^óìŒoã9´ÃVbñ×ÊãIyg–LcT}5ôIª¹…,æ~£¥Xçl¦|Û´Ò!ßË?|#UxÞ4[59šuÒÓê1~È\,Á㚦{g6¥ xZÊ)­ç?ø#Òî8¶ãM«5ꪪ\n‚\¤bg~‚ ,ü=|Ðjåø¨ ¢öiÈæ‘/ô -V{€˜[®>ëöáÞ‘ŸãJ `:&ŸÁtËÇ݈8mkËÅ*ænK¨…è6MqLeæaîbîL8Áô`Ä'§NVNÙ‰‡Â€ê>ÇrÙ­Á-³ýódUæÊ§HiÏZ!ArAPÂÉ(ÿêb)/³ @– g…P½wjOÏYÏg ^¬ÄPÝÍ\i‘#ÔŠØHÒû¢Ö-mÆ«[Èê¦/¤cÔ#êëy*c!Ù¾½)­® z.ŽÁPë8òêR-ïCéÇû~ÇKí ÎÁxT¬@«?î uçFm•“¹÷†u´dãü£ƒµÄ,×OœÚÈ»6kÌñx»„DPO3‹Î’qs5Õ~û¥qSÖþS(Bë>ÍèÕêša妬MÃl'"=h.‡‹5çTócÞ[fb'òû-¢â­wsuPiD"‹ ÒüÚ¿“Á|çœmXP!ôÍuø ÆÇõ<kæ bÏ–Oz¥uÚnkÇ­ß?¢s”DIíN›#K¹±JÁ®-Â=é{ÖŠjÅ­!pðî\ï +Ô]”‘·)ßV)“åøƒé‡~.…‡Ÿé.Èއ/hTŒ³“ÿûFúÉ1ÈÞ§E8¢šƒ[¾~ã,c íE½¦[p‰äºÙùà›¾syLx#.íœxÍ„*aQ÷7Ñd"2Ÿ¬-fÈZÊdW!ÓCŘd0Hœõq ©£¢O}Ü …zöÏvŸrä!ÛeÑù}ö\ÛI´Ò]zˆ­ÍÔ/YZ¥€]peýÚ¥äHͤ§›PêÆpVH‘;àGO.É.¢ä´ª&(ÇÎ^ÊPQô’€åSÑp…¿²cF…A¶ÜØù¼ÑVt~:8–ÙO®hßð løÒ¹é»Ô·nêS©"ÎæÙY\Iå¥Î¤à~¼ª#GqÁÐQ[ÍcCyŽFOª2¦…fIÍ*€8—W'{=x(ñJµŸÃ™·ôFªa895q3ŸÅemÄfûA¼\=÷T|?ÿ¦Qg§ýI°Â„)î@xs©„î¢ÓǽPEèê5)†¸ÊMP #½Î˜{x=„Úz3&žG—9 +“™·ÛRýaáýâÖbà ;ÂqCzŸI˜ÕÞeZT#¡0zȸ³Ô*%JdœÌÊæøêÇ‘íà©<Â#ns;ä$ U»VB´€\DáÃê¨0!l5ú;»ê8„Öšüìs¨ÑÖÌÝOþ¥cÞ©ç-ƒ4Tý‚ïªZ]MGò=¹Õßôó{ø9t2¡_F«‘%ùÂôÒ6A‰W-ûF.£Àá-ª‚° ’'ñ èÀÍQn©þú±> ×)5¥ÑáÁ1øï]Š—”™°ü!~rbûÍåeËh†˜BOn:Ö@ˆW þçbïdþ±Å%•PØP:°ó¬ºÖjó• %Γýþe·®•úVi{µýûÄ“zìj¹éåô€³$O¼Ä–—Šî†-]E©{à‰ºàŽ‘¤–.7/V­¿Æ}ŠÖYfÑã ’ú¨ûgÇä\‹§çÍzŠÀÌ~ÚÚë¹êq´Í¬”ƒ8éŒ%hYX;q×~j"ß"PRBªŽ‡Ü]\ #H†ÜT¦!¹##æ’n¸.Í'Oƒ@#mý é"˜ÓBºÑCÞºŒ–¤¢N[¬=>;ÌÇù0¸L=Â+=(«±tcûj±Zá$.5뛂Ð$ge‡åo»œî)$¾ŒÌ’j#·ü:6,d xxjAoI©;‘¶Àt~þƒC…qè³aÜàP×Þ$½+:“¦y ?ŠØß㙺xÙ\à/øŸÑÙâÏ• S<T@†(À‚—Ø,2©….À–Ô“ó¬Ø9×1N€3Ö2¶CžÌ>™ÒïÕR§+Ò;ݳøgE€øµpùDi+rçÜ£Á·/ðcðXu+%Ò¥`üŽmI75Uµ© Õ¾X‚ÞðGŽÆ}âV”×Hs ùtÿNÀ}³¢Æþf#e)í@QÊEÛ+šæz<¸vLâäWxH*­¥ šEÆóêÿAáÆˆ£Q/oª F<ª¤A?((%÷É.ÊÁïf/?ãw?•îK;A>?¼'›§´4`’Ó°Y€ ìÊ=âçØà&¢.jýƒëW!Œê,ý…‘oëàYÍAÊcb>;”×S~O!n h-òŠÕ§þ£ƒEž>Ѥ îêoÙÒ:ëÆçÏÃÐΉxZKý¯*ÕóÇ·RM²Ã²1L¶Ý PÏù¹¦í$È™”^Ó…Æä‘ G#^« òÀJC"tbØp×Oº×n´ÔÒ”ŸÆ.Á™€œÔ’ßéÙïø¿ªgZºH0.÷¢Yw´ªSj|ýÕ4ò3{ 1t'îÝ›•ŽÒÕ½òî\Wtßc©ý"ÿ ;#Ë…ÙÒ£Ýø%Àf9~Z°}ð²áÓ ö¨öçö±P8¥0h /WëôQä6dÝ\(\Ý…  E@ ]åjŠ¥ê6¯øO±¹>)û yK¯ÿU–&x7&ÚŒrTÔúò˜.ïŸ.öóU.:j#±+Ç這¤½þ%®Kâñ:P9–½—Xâ¨&ÆK‘‹0ÔEèi¤VÂÀû7:L-¦“¶ÂNß_¶bòÅd"¿P85 sÏT3´eºh×ätÈ §]눵[‘á+nT!h}üÝ–ø"I0ŽÛ˜ôíÕcB Òž°;4H¾†Ÿ7Eû§Íu<Ö·Ì}â«Á‘G-ª1‚|7þºªÃÆ JFr§îHص%a_$Ù(1\&BMÈû¸'_ö*_Œb…Ô£„àð ã@½=î ?ùêæç ü“´´ ­} € ÀÔ›ˆuq 0D9'gÐÓa37°<¾biN '0*û¶n›Ê¨cæ÷»LGáF;`TçÿÄkx•[^O˜–²œñÑ…P’Lû«n‡› 0NïŠÚOÐD¡€%ÀRÒøül/§Í€¨O`ÍlÅ–Mš{h¸I²ŸîÚj€§Ûíyõ"å 1&ì³Ph„ü6àœ­{½¨£Áø›06ÿ±]jñl]ÙÇëãyIAÔ§¤nªÞœ?Ï6Ø60èˆñÇ _lôă1tͤê(Ð\œ2(ý¯Ô†hVqQRúâ!ÿaõA5Þ°EêA«6¥Í>’°î JìJåŒKm€>ºmÝvI‡:Z&xäŒ|í•? ‚‚qê¥ÞBN½6P{ Ö i])þ'`Ë1'‹ðQ,·¶b_ާ&ñ\ ÿ1™Rݬ*ó³ãO¥J…04Úst]á#WÚ—Ä)¥%›üf¨¦?‚s{YÎ"fàD}Ãõ#dìÔäÕž³ÚÐ<Ì„zLU{تUgæw0{beVbþs®~^fzÃ͸-|L0Èìüh©¬áös¦¦+Ý%6 ‰9¤»ññúYåz2Ž2éú•¹¢MËé†ý€YmUór6]ý¹³ç2{x¹f@ƒÝ£Býsò0œ¬5.EûÏj¸®(ÐñpåÞ˜GéoiíY¦¬Ð¢ì܃06‘b¦º³NíņÝX{é^Jó &Øö-ãÙå¸Õ:Ÿ ø(–q»i¦±(Ýg»ÌðE¨Ÿ5K9$ŠÁÛså#£äxiÃÖ’bÛ ½…pß9>» óŽUÇÙè¾³âØF ¢K. :$Ç[_h P 8`¡>ˆr7Öo̾خÎÏ_Múê.f¶¾Ý ß “mx3Ü[ªc–w³Åéœ×îV¦îãE=½kJydµÕã-¾—Ë8c©…ºÒ7fÉej¹«¡Y¦ßcƯvº¦Õ~‘`N¤RL~YÙŽáðbÛö€IæÚV0w1¼VºÌˆ‹›å™“–í®#šáK) VO¢N·'é†éÕÌÅûE‘ÒÞ[âÙèXö}J®KEœÓZh®ª½žÚ(.æmëv«­¾™d>Ðߦb‹¥”!’l‘ý"KØÚ ÃPˆþÄ.¸³Ãc€±Ÿ~/:ZçÃ-fBbÖLÌ16¬½òWà v‡ç6„¹Ïû–÷^œ--:ÔO ^'wÔõ"ýä„ ŽŸµ.Þ‚cG)žo18T˜¤ž™ªaž®¿Ž©ª+Ò]Âóã¹Ñ¤ÊÙ€-'¸° fýáB8¤ÿ_~`õªl*ÂPÇñ¹Tþ^êmù ûº ½®±’Qø$)K‰z-¸ ÕV»·üˆÐªEú~%:‹KõL¯èZ:ÙŽý$”‚óúƒc÷<öñðɦ ±ÓâdcÆå8ñÃýÖ‹g•eÃÀ:ÎÙøýó-:zÂì²zè. ÙÛ¢ŒnWI%‚–):hSŽÈžÝ1×uèšÌ+ãר)þe¼ý>×s§Ó +X‘dYŸzë©é{2/WAåóÑZgÁJòVTZô³4Bµ‘=:¿ç`ihi„þ³ÔÈ´}M÷`ˆßºéjyÆ@‚zR^¹§ótÆÍ†úAs'#|zfd<>˜ši¬…çNåmc`?”#Ùð× %Däe¡‡Õj¼g_çF-J¦Uñ?jµÎ{ˆj±V×{ó=Uoâü¤ ü GŠÌ=x Ë饽ßK8»öwšÃ]É¡cã«R­"æü¯"û½hÞÌñ­ï‘l)9W®‹¹;a¢Ùš|âv„ßcš°ùÂz€4Lž{h#9V,ì Åb¶™€s0äþ!'€+#×lÄT1Ò™8Ã?§ÄÖEø¤•A¡ë‘d¡4Ë7Të*ÎûbR`3ÓsP¨“20é7hnÉ:i€ˆ`ïnaãpÃÕa^•íJäØ óÇKŽmÎÑe3Wç!vKÓ/–Sí£ë°ÎÏC t~‚jìÝæW½iù+wH"³TІ’§öÌС©ÞH´µlÙ˜4¿°¸6‚22—òÀRËÍþkŵ0hÎæÜÎË` Û •¶aÎd|äãäÒ¾«Õ4´É?Ox ~€„,Áܲ¶’ЀŒ; ø{ßÊy:{ÇWf¡¥[LíU“XÏ*/þØ í¤æ$ÔŸ%Ùåxq3 }gwp{ÂK]©ðì†pêPÚ~Î …u}âܶœ«Qäï”ûå¢8í,n¸¹2副W&òâ×úÜŽÿß0¯åx¼®úµmö¨@@Àhöœ‡· Ô¡f¶ÝÖÄw%Ôۭݹ/!D g¼Z²n·8^ŽãqVI°ár4œê…æÔSî(êûS¾TZ’ˆ{±«sh,˸Ü;+טYe˜!KZLïbpÌ3œMuN™€¯Œ¸œo ½˜^åC…3#ž Bý…%AûÃ5/ij Üò¡òo4Õûø–!^ YX~ÊC³•ÐõùÒÉŒ8ÇË*˜¤›×Vòf–r—‹R’›úÒý'âóÿIî…·¤Z¨-Ö â¬Fç½LTŽ?GP7¤åÔgÿ73ÏØ¾«º3¿Wprî#<ÜÉs5€ TÕ9õű©D·~ X'Ño×j\A@m«=]pÒlª=‰`Å^Å ÄR¯7Zôõ7»kÈ+¼al fb H£eHk$Ùpáš‹ Tn5òÉW+[ÒõÿeÔtóC$ЋHýRq²úÅýFW×Yöþ†#"ãX2R¹zsTªL~¨Áô*ØÌLó×Ûû“¹n·;¡´r‚ñ§ xz‚7ÊšrÙ– %É#)Þ”~$%„µ>h‰“,ä› UW·ÚceûÌÑLiqˆCvKQ{~Pûfá ¼ú[Ñ4U%±ˆVL?îýß@ŸRL„rá{ÇdŽ4=r‘íõž§á~ôÁƒe)Tœ?ÌÙq€uªý™ $uÒ!v÷7>¥¿Ø“ÇËOS½`l¡=+¤²†4‹æ²‚ÉÆô±8ª¦Óö‹ÆB2wvFõ¶JãÈ% îVv»/‰ŠØñôàzև ¼°®D$«¾[EZ›„(pÒbÃly³Ã{!àK¯ÐÂïÖ¨B(Ik²ìV œOÖŵ|æ@†´ØÚB|â³°B<}ÄJn¸¶dw‘zJî -èç¸i¦±ava‚­äBšÌpŒžòk’¶ÏçáÿeŠË¦î*Ø¿*~o ›¯_+ã=^.ÌX£áËœ%´>Ç Lÿ«Â‚òˆ¹`ãתá2Š‘?JÚ.ï*í¼viëÇ€=;È/Š“h\0ÓcÝXXTcQ Ã4OVÉŠ{èÖ¾d(žÞ˜ŠÕ§Cúšd™ jW*gfR&¼]‚»è!r„Ó|ÍYœ)Yœ™«T^Kp¶Z½káO¦Ê=‰#ÖùŠç'lÖäU×€I7¥=`¤xE˜Û§‰%@zî8ÅÅX¼h!~ù4k×8l¿K[Ò*´¯Æüe]%™þ×4Е‰ÛJætÊ` öm'Ø={€Ï‘1¦ïüüÛ‰ÉQß1&I¬vj?©;³:T ñ‘õð‹Afk8s±— ¢ÂÔÕ³Œxü´…ù8r`ÒñU3þ`Õ…»\?©Ê²ÙŸ1^ð7¸AäE&ôîN€$áCR®h¦ñïÛ §qjÂ|t­M]Õ+oÐM²Ë ãsÖ%‡ñ8âH4O÷f5hr™I;Ió™0Èyó“øÂ–ÌÉ$æÑnY×o ãlbÙÇ9"’U:UÖï„C¹ ±I@Gü)xþzgû'wNSõ²ÄÛ’*%uÂK²Ç‰øx]_Jb–—w¢Ãìk°%•ú7Ñ»­{H5LÐ4±iu®ô ƒ<¿pŽvÝRêÃMiP®$rÜŠ|xí@ØI(šs3;ÑÊÈ•¼†è2_‡å­œ¬è9ωÓGh†å“DM?P€º´•u–ºUrÅ0ž·ãäBmROj b¹×öj o½CCIEÆ eQÿgôœþ© ïT&–Žp÷ì»êôÿ”/Dï|æø¿°ù€`1Ö•©ñ~ a{RÄßT–¢[•¶qN†Uãút¦‹fôðXŸÔOyÖökM<Ô²ŽÖ2[J‚Oˆ­øG=èÒ¯¿‚{«‰Úíj÷1I'îi‚V@D«¬‡uA-þ]VEŒ8ðÆ«[S‰$†{ŽšÑéì;Æ8fèòkœIÕ¡Øñ¶qèï.þ4WtO2¬ŽnW­V„8øéÖ­սߤá,~ïþü§ä@a݆Úl¹©%R>Á´jÕc\8ƒ,üÝï]a‹p#=¸ýÖûæ©:Ä—s{¹V°Ü ¯¤1¸óÅàîyq5ñÅ"xjN€Fí)u¶²^ã\Œ-X;‘å°$[‰Šš®§ýõ¡»È6Úÿ‚ë‡ßEÆå,¨ FÛ×ïA:‹ ‘·FÒ(¤æ%îܘ _\H‡E$4íer³¤Ì¬T7…ÅqWMˆ^Ø/ñh¸wRö4²âò…ýÙNVi¦Ž¢¹©¯@6&%vmζnŽ ì°koØmçD_HÊVoá¦[Ù*RŸnü‰+Iwã̉ÆVB,ÒPˆ¹•>˜¸)”4ð×ÖÓî.½JªJàx¹[ºû7—u›ÁI ÐÎ×@3X¶&àÕZ?Ä2¾”x‘[ÉJNF(…ÀIù PdY›‚ÒO¹CgÕqTa«ˆ]=BÖ‰&14PkSäcGmdcL¢M"X¬ëj1,R>±‡ñ.¿Œå+ ¸ë4Ò}HúÚkV›æ”^¨²7gù¦Åèü‚^|n«Ào¬>‰2ð# ›~5<ñ²a°HéËòå–âíµ±šÓbL§˜@ZI&Vñ’ã ÛVûú|¹>¤žÒ8‘0>š—àX7uŒ¨9?<˜¶Î2)¹Ê±@°Êv!êFr Âù•K4³S3;9£¹AR“_%}ÇPªJ4kòKg9špøÆRQ=ï+‘!ÓFºô>A/<çîå²Wc¸äJ„dÆÙ®³ÏDRä(íx…bénD×Îу‡[êék—¼·C(ÑJZIV Žç$I'4!vÓ] X Pr{EíX>H„tÈ›fð”VùJØ9í÷zñZlH (aϾVNz1>îïy*ô¸è(QÄ›×ÁnŸs&4Jäe#¨ˆ]YÑeÿs¼C(±õ¦/þæÐÜ@týã·57(XA îõïöE튼™}¥ÇbT(¿Šûâ2{Ç” cû ¬Âk)ÝQýRþÕ¤ˆ0ÀhÓA6a÷™«bëc¨¾UÜœÛf) õ…c%Y}ŠÆRV/dÝùÜ«š?†N .í¬J§dµ$~»ô.5‘txãPá»Ã[Êz4ÔN±—Ÿ?Ñ¿.³­ÖÊ»H2É´½$n±³KÇõ6{®^Q‡ʨv„Œ™×ÓtH]=¥W:«¿Ö cXæžafÜ6P ²°•õH¨;5/Á]c‹Ùì7¿ìíK×¹-ëÏce™°¦øï|{Ý¥Ó:p^Èò‡dûî—øý-§¼ïn† ¶Í¿qáX :õLýˆ¨ÁÔ]f;Ü3]ÂFŸž˜²pßUR0àå[ñì Žõ4z·…ò«Ý º5®Aç;Ò`BùÈœp=ŠÅ—uEžÓ-œ<ÞÐ4yºü%Ê~Ä÷“½Ú7D„Nº–*nõzB×úÝ€º«í#µÙêÅm¢ Ì›³“‚ø‹P²‰—gËk•7ª> °Çƒ†Æ¼S(å·ˆG¾å{ðѤS¾IÔªNºÈ¿ysÊÕ¨¸¯ ¡Ý.ŸÒ‘WÀî3ƒs´çFðLFƒµL‡éM€ž"ÍÓ,ä×›Ú÷Åaši”AÞYìÆˆîþƒE>¨¨@/mÙ[û̪p’Z°uëÛ.Ö§Èý5·éäðDë&èO¾ ð“Zx„ÿ{Ól×ÙC`Hê‡-`*þ›¥F-Ûè;:£ \LtI_§Z´{:#'·,NJÌIâ<žÒ*cíβËðØ4ZˆW@Ì#ñˆ÷¸Ö±8R!Tû6z™å¡áÑkž†XG~·¬yîo„®NKÔº/¬›Àv¡<¬Õ‚7¶(gןL´“_`(_4{ów™Jö ÖP%£±ý&Ý ÒD½O*c+¬Í$$9PI•ô7³hkÿE¨qùwWg›¤'Ý x je.%‚ˆ'"H(ry:õI3óñ(®-QÞšXò¨b*‚ÚÎfÞ6Þ5 øÔ£5RGÜw^à‹ðÃÁìé|ÔŽÖÁ!8íóøP þŸZ›wÿÒþí|äb4¸Ñ°ñ"-ó7yŽ}afßÅè*ê¼Íp¯©ÉèM™ü¶9ZPÍä݆A\¨­GÃÜ{;)ж‡…Žð¡gZg¨é]ňáfÎh÷kîMáäü“÷íô°¶GÊÆ·§¢c2´­º×‚`¯TM5Oa&úêêHõê?ßÁ p‘Œ´v?¥lHV²’PÏÙÙO’Ñhè),üB £u’Lø4; ˆ’ lói(—¡»î+u: Óð˜¸È©#tDpK3"ý¹ { à eõÓbA°”£ÎËoKVÔÙ½êîÖ«K°û°³Î9‚4ÝvdÊÅQï܃éȬkŽdD”éK›ÞƒpVA”#ÚiÕ±GvxDp´Ð0r'k=DiOvð3ÛÖ{h³©bì¹ìbÒ‚Wï…]³ U†cotö†Z® \4£°Nèù{gŒš¯¡ƒ‘º ‡©·+é<'€/Ìø,"þ{UÇßV…è|•Ìœ²ïÁžþ¢2t”†9–½^¸«Tú&Ž`–Õàa÷A®r§¦O>o3y2Dv%fï$)Xº¯v>t ü^Ñ «oM;ñÝ>O)ªÐC±ÇÕ´´ÀJi4ðqê¦{ËŽéÖ¸Ti§O] €ö@Õ¬¶F!7A€µÔ"yU>0eØpëi0«‰i `=BCit;g[§ò‘'„5³J¢b(ÒµÛYL‰Ù·³‹ak©Çwlà܉#™8Pl÷Nœ&?í9§¹ÏêœÏqðÈŒø´Å(e°| –Ÿ~©c&QÓ=÷ð¿sOSîkf÷l–ÂU#«iðoûìEeÊÛH´@$>ŸÉâ!Ûµ<–B5­al¨³žd3^W»Æô¾áÅÔæWhÔ1>·xñ¥5¯VÐýë\î=íëK®žÚæÔmÞuäÊÈ 1)®:Ö𸽕ôióëqºb5ý>AÚPçC11f¯bt'ýBôpíU9»ð³ «åsçà/P×ÔÊ£iS°êK6ódª¸jÍ9ƒXáp ‹ÌGqÇø©ãW#œ]^î@*>`ýº²ž·ªnVux”ý¥«OZ+DÁÒ²îüÃiz4ϯÛûR½’¡›ªz›ݧÔ.dެr3›,r†ãN¼*µ5Ÿc×\ ¼,ëFcaƒ)Þ,5<Œ”žÜçþðÙ2ü×¾¡QØ>6ç…¥bAK^"º%SãœíþÎm2£?‰‚?ƒÿ+îu›‰{§[휇‡³§J­|* ŒVÿ-ÐRÿÆ6ùtbPñÑàÿòþ•TÿÁ§j¾¨6)tHLöÔ“ã0gô‘@ö/jE[¦ãTjï„]¤‚ï\îÚ®:wªí0‡AÑ:‡+caJ¡˜É? :ôMŠaªÉàŽi´áq Ñ0óíW9â2´ÜJHÙû„tpÈ5+&ºPƒ§#ó€w’µ™8Åd_~ç¸Å~b]ULPø€ØNQ,ëkäÜçnÁ»9ݨžôB^_°ÅþXT†ÃòaC+Xýo´]à ^tKE=9ðt=s0\ò¤Þºó¾½€éwÛõVØ‚YÀ^Õ¥ëmbwÞ"±õï³àQpyŽQ®B“ëèþSÒ9¤¡;þ¿ƒ?aO!¯9–Rªî嶦Ç"×ÎÔ h37(‰ýyôl3VrÿŽÌ„U): Ž”Hž\Ë´˜å#5löX eøÎ¤õØþ.AVÚ{ŒçÆbSßÏåç‚”!y~ì¿Jpüµyý/®Dü‚jSB”sÌ/àðŽÒ«w£•üNš6šžød“‚%VÐ Ðâ*z„°Ý¾kCÀà‹5Z<ò?‘| ƒˆ¢›‘a^›íÌ.äYs }þx"ÿ³`#+í­(épGÄI‘ä:"YHLâ‰QîÀÀ\Öh®¸&{ãòýÅêcœÑ‹Ó ÒXÚû%Ÿ1aëtö(»¦­yz\èA.Ê÷gòt¢ýÌUAø¶Õ§†T~ü­ñ‰ÀRkÚòØáë™éTñnyW<Ö0|!´Ó“ Ð^mÚw°-ž·èc>ë:úÜ 7ª<¸Þ—§æ8Iu ] © “®5ÙÌŠtt“Ûo1è‚»ðìÒãôƪ ¿PJ$8F¡ø³XöÖ9)Ó&1v¦F,M.èê«9ÒüvŒšËißß뫉{£½Ëo™H--Ì h’ Ÿ=0ØíéÛø²t}ü/`u‡ê w¨´¥ãpÆòÐù¯§@Áß}‹»ŠÛ¸ù îÃ8CÜYÎQrú:B–¶A:ÈvZ.®D +È/ÔüØÈÓÒ’…‘úˆ•P9K'À.ZUÉ’dSvFÉN4:wÔÕ‘ªrKÔZð"Ÿ½ô?Á¤»üs÷@–žËŠc"–íÇ9$uZ|X>V§X+þ”-¹¡÷õ™Žmähð¡Í+|á[þÄvʼèBXÙÀ\” hÆ#{‹îÊ}áÏ(k?›4v¦êHж_:m`¾;êÝÔwœ-‚ë9HJ;òä`5ÕÉøú­Gc}©EQ^]¯¿Œq¼v;_Q Àki`:)ÊcSn; $®É>pÊdÅ4ùÖÈ‘K#ËIö»Ñ7Ú¦ç¨íKž«‘2mšOWþÿ®¾¯Ê§?­P ›Šhx’Ÿå}§; «€ñ*\":c_©MŠÆ 3P½ØT;Í'p‚y¥X?&‡µ÷;Ò ÕÀ–>yøÙŽe5JÂM´8EÊJÂÅl¼HI>?aê làü·§Bo,Îõp+Ñ#w4õ1CLÚYØS!ú@>GK»ðä˜(loîSI8nm5&ëL+?ÎrÖú¤Žiü¢V&ß2ñãyÝ–.¤·ßhÝÆ`¯½øÝ¶S{Iƒ×¼Kº>ŠrbÏ„!lJØÿI—[¤éé9$ÔٯÆ?Ew}¤!O‹ÞgôI2Ý;(U:t¶$ÎÓ“žoM÷„Ï'›UÙ_¾o,÷Q! ÁÀ}ÒmÆtœá`cUožÑE„1Õ–‚MË æ]ÒSâÈ@#Þà_LIÁ^)<×ZJ› »V ¶™¡2Ìoù(¼„áQpÎ9OÀo yàMoë¸¿Ê ²GM ÿÿÀDл£Öv+€EÀã_o0Ö°›)<(?³#)â™Å¥xS#¢ºšë›×Ù÷eQ¨ï”A+Ð:¼~T/”Ý9&—X¬Ñs~h!Ú ¥|ÎuÌëxñ@ºiXOÿ`ÜË-&]Éþ•(É3` ub^J²1ìj_Èèc²LŒL Ø‚àÝ:ܾÆkdãQÜÌî†LU„€™d´×‡ ×w$mɈ!;c`ƒäƒçÎð³QlN¤1èêb0ëN×Ö¹‚×]Hò´Üè@] °²ìõ¬‰I¡ ,Ëõ ¹|"Z$3À¶ý^¾´‚e'‘§Xݨ2y0§Ò·œÅN1ÚyñޝºaÅdöådP5çsK­‡uî‡vÙúÆ06ØÆr@‹ÄÜ‘J»; CúBÔ²r¡'}hmÍ8«¼Þrãà¨Çÿv)š‚ÃÔëYMÐÀ~BRRVÆ÷{lgœ‡i`VÄ •üÿæ «†©çv¤¿€iþ{6üO•íãjÂós À ©F¤±Ðþr#œÍîGz§È›Ë…} Ö§Qb7˜ßÇݤ•<-]P>õΛIÎù ÝD HÊA:6ãÇS˜ïceüYôý‡£.ͦ6±Ò3Ðæ¨.£I›ÄTW ¼êúƯ…X`j ©Î$ÎPϵ­oFº*úE*_æN„%æClíÓÕdBv}8Ñ®–Us sá¨EàöKÜê ÚÂd9ùTíÒˆƒ p¼×Ñg@<¹þ¿œ‰sE¨IUÛù@6ipt§ cõÕm³¢N3²Ìëlé)Ù2Ö?9X­&;Ýû?´-‚%´ çØOéº× 9ƒNWè°0xqD5¢Å¬ºM•5ÖFÒbô/ܬ_m¸öø=•Êç ¸ß?†j¸ ž=MdMzIÍŠ¢c%L6ìÅf„QÛÂHfviš¯ÕyTDÚPÄïÂ]XPXÄÿåÜ“gés-Ãøw»LÎÏÊÔ °‚‹‹zü@¶Îž)Yµ&1mYøÙÑì@ ô57š«ˆÃ@_)f;ÜO2Ý!,Áž¨Kv2Ø£ðavœ£v-1¡H`~žôO¼’?›Ýaö_ÿ÷:ù…tÐDë,±íØÝ¤” Ý{–kg7HÏc%;—ŽÚRµŒR¯ŠGø)ôï¡XÙ]¦ë°É¦-¨p°ûI27µpû¯KNÅO†Šlr¿ ÙÕ±ý ö¦i‹y$z³S)$_ïA:¼Û"°ŽÚ ÎPé@<ŽÌ-tË)œ©°þƒÍzôKoœ?éßâr“\= ZÁÒ%j¼Ã°7£Ñ›ÑéJOÊaøÉ©üÎżP¿Ó1Y‚9ì”zm«!ÕPÅ)ìV¯èñNºdŽqÒ¨×atІ< HÀê]`U¶ý½«ÎÝ÷ ºùPF˜Ÿ£Û‡R'á‘lƒP’ ŒËðÌ$¦BŒ¹s¬Åü²7$nÛ]¸QÅ£kW§ÿ{.©¨pzÜz¹®¸µ¢n‡ùŸ¡¨«))“R^ ïì|T RäºmHR"‚¹v¦H…— ­(?#Av$ ´,ξò€¦¯Ä¨.H\FÖ‚®Nw }8‚J¡”œ6fÔê¸~1©†‹÷:§Ú…Ç…› üòTˆuÔ™ 3ë:Ì®á•q‚ìôÿ µíð±NuÆU¨9³¥+¿~æü<½óÖ#Hî×>Ãà»°L{ƒñþú„‘:褄 uOÂ(_ ”é¦5ÅeÀ¤8”¹>à&<ÿCÂ?7-EH+Ê´ üŸÖ±²DI8¿GÒV @ —Leû­9Ú¿”Q‡„Ã,+»0OŒòD”ÀQ] ^ü `Ù^mߨ˜ ‰ù93á|˜á&?çroòÒf‘ÈÖî¡qQUPJ" 'ü<½s»TšCË -•úyí¿p ™_bùþ£¨Àyî0ÿVû‘f¨›'¿ÒÏuC˜… ò ñE‹•½ùê ØþÃU>]ë",7ñyl~8õkcݶ€˜{!tÙ]ø—`p®šW\õšì^MÙpIÒ.f´m%U>ú1Ú„rXŠ×âìNÚ}¸‡S¦¾ø(ö{àCf¹“Û0úqT;¡PŸ:ÐY"ÿR„¿ÎoLÛîäX gwi¹óÕRpÆZdyX³\¨/ BÑuQ¿ü¾=Ú“„ÂÓr£qæÎµÒiÑç>Û^~»»–ù®½‡ôÿV§¾SÍKßl,/>…°ŒU³*‰ÝÙƒõ¬Q·ÿ,É[4 7Ý£!¢{:™e9‹î|Ϟ˫dÌCžRÍ–é•'4¥ñV´ÆÑÅg¸jFûïÈ ¢]?â¨Y4b£zø÷QÏàÌĤ´ÅùK¾žo ¼t*Ѽß+k’BwA Td‚ÿ²93͹WÉ=ÇF‰ÁCòMÎòŠAã[ë[ƒ¤¯„/Ëf ¤©/¥/§úWv™³\½ )cjáõZ űá7M¥šá —¶[WH3U”üóÏÁjpÏm‰%µ_$›æLnçèÛ«ABo ‡zsµu³Ç-ðu*ÌÇEÀVHZB…‘aw~LGníÆ ú›s($æ‡#Ûßµ‹b å1»ôNÑO*óˆk¹¹u\…é«ì]Ûù+Õç„–'p Ÿg«×Ρf÷¬¡„ª§«üRûYK{A`îÌ6õÄUI¸9vàæ'&ð$v;à û]áÿ±Œ>j—› ûþž&ˆÞ"§ °´Á}ßaîn¯¼ìªÚîÔú»œâ@v/1IðŽñ¦·$û’ø¨Á ÚŽ£gýéMüÄPª6ºkjô²åëvbñé¸XbÍ;‡®òþˆæòT†”PcQ¸¸­œ¼óƒ¯é™ÖS¿ 5\×ò’î }¡,k{´®ú¤¿,%!‰C›‘œV•h>Þƒ¸TŽaC? ï‚–õ–ïK¿±6°‰vÒÁ—5aÔãw]K\ …}d%;©&uú²n'zy/¥pO38ó#YÅÉcyŠ3q“ÜT,÷¢õ5ús„äaš÷*’S[.(Ì$¼åOÖÏKïÏú ð{ªPž÷\FþiªF‚î!HF¾ë¢Yó´IIxëëœ>m*<´µXeîMG.›¯ÇÆWZ ð9 æöÕ/ÝVØØÕC¸€ÚŸHƒ1YtÞ~±Äô¯ “Úq0o÷Ï'Ø'¤—m¶“êÄ,Ô™¬.éÖ„€#(šŠhƒÜ\Æ“ùåm(álòXÚ%»å æòDˆb÷y,ðuÙí´haîÍñQ\,!¼–ôf2ÊÛW7.…OäÜè_¯áyÓQ¯³ =’?¿HÁ2ˆ•/M”Ãj™þöÿÕªšŠêdç³Cl+Wë+ëÃíØ š®m&Ï`zÐKë¦<‘È(päGæ«vlè&U%èWrêö ×UVÇÜvn¬0Š~| k¦;r­EŠ€ns‡- çê‹÷~—™¿$Ö÷˜PÛ·SåX5Þ©†l5fî.Š­î¥”1ŠQ Žüè¦MU-.¤wAÕ]Úð|è†-Û}“5š]Œê˜Qüöœ¦‘Š@BÌÜ–…Hv¸yŸ ’óø-\o?¢—X´\hÑ ¤Â{°Û;BMè˜~ ]¤nÄÁ…k:¡‹sâÝD¿ùs-ÑLooÛ[x^’xx"è"]@Ÿ±7º2¹›GÔ>ÂKòºÞqÅ¿cÙ>!S⨒”ƒhïýò¨¤¯;r›;)Àð¨À´Ýk¾ëë 3h O[q+‘lñ_y€ý¹tg’±8—±ñ|‡Í¿÷UÏÁ.÷Ûi Lª?JšízøÝèRVU ø/E§“ŒL!¥úûQ¸Ò¦°ÁÐâQáÏ”o:$E ònxžïÄ;4‰°CV¼f*óy{†ÂrõoûvövÙrº·Ñ±wª`B£æÔó\¨;‘Üò—k VáûCJ!$ŸK„wý’FTã\]"æ5~¿&è D€ZöJJQJ¢/Œ~,ã¯c0š w’*j«ð)lîVM“á‚ õïjÔ 'VÓs]g (P­<ÿíÉHðÈ"x‡*r €l½Ëï7½)ü>ê´­žþæÕÑÍ¿<»¹é¤®“عy`\o,ãú= ö†ýtAXWS-×̧>xÂÙ±Â1-êZÈ}œøíÕ›'¥Öž­^»¬±f.è ˆÖ¿À÷ƒP´è„ç~)³7¢w0ê°\¨:jì1”Óp€H5rÇ,1aûüŠgî¥ÛD& &K ‘ëÇ1žûÃ3¤œmŒú„aâŒwüLVP¹±œóÃsÁÝPÿ {àö÷ÉßÕßïHïê®kzü—Ñy†[pÕ×ÙwüÞ}ɨ·÷‡þs¬a¿Ó¤ê“Ðú=í„ì€ãÆ-µ²öëÏ®ªÎ½¿§Ü¡’%Î h ¤yN_vË ÜîÛáìVÄõÍÇÜ’¯j_ú »üG+Aàq„7Úu‡ûføþŒ¹€P2sJkŠ™.ô´²}¶æ@¤ïšîc‰ø -æ˜lôÁñSþ}©‹hðξô‘ÕVRC•Wg›÷è$5™ªÆrfp½7±iZüd¡x‚PŒÒµ¨…qíöf•ö⾊›Ú]ü8Ÿbáö˯h€ØÌbÖÊ_$$¼‚¹Ò$ù[€†b¸y‚Îÿó~”#2Ý5:^{hòs¦X nx ßµüh¦™atÅvHRï Ne€„£E¶lF ” ©¸`,ÚCfÉøè§î˜§BeîBÏyÝ;›õ€ÖïÊæŒPVÚ†d¥ Ê¢?_~’%/« ¥{«XÅQüöe&°ÕcDHx{pŸìò#-lø„æÄÙÓæs(L9ü†Ì€¤mÆéÓ]|?á5×Ѫ´¡†”Ð 5wô911¬ÝĬ)ñM#î%ÍÁA^ú!V¢î‘×µn6¼õÚå±j†Tª_ýù:P Í…E/ Oë h ¥‰“&'×ÄŒ Qèì‘-oLLéɇî/†”£Ë²ÖMƒH¨Êê{•jC|]u­{Üä¹:Ñ+0wÖ#“HG‘о´v!'¯úÚâ3£ Øð(ÔòÁBZÛ`“Lm´ 倠•^UÏôÕk”gí!Þêªôq|µ±sX?¦Ú8 ±ß-®K­Áj¦]Åð¡éˆ‚?Ù±f |F^ÙQ†ü=×ðƒk—CËÌ—îÑÍ7:H.•tNSùÛÒšÄÑQa>}7­Ö膼Ùårš&‘üâ\vw.\ø+ñšîa! 2é¢ý€4§úz…ƒÁ\õ¦÷Å5XÏÞ+¸²•7w†TνîÎñX‡2ŒŒñÿÐyBßÞ·5¯6l’û •ˆ­Ìf¥CÛVQ>†3j§RA¤AUÁZz‹ øx6á¶@FƒrÇD娰(G‡é¬fù¡÷ïç”zž·õÌ -‡—~×®¤žäìúصøÜÑÒ°b?,†7I_zd8›F{$ÈVѨ×(Ñ ¶wßû] LÏó†þzµÆgø{'ezªVUJЊb—SZ̧{ð¯ËÉ«ä®B3£ý˜Z {«‚z~@ëlpÓ4…YØ@ò\CìB3ÈGYÍ$EŒÅ†?ýo⯮À3MÝǨ0tÕN dƒÎšlûz¥C E¢¿?q²°ý…{f/½ä¶g2O`w´ò8ꤎ¿Œ½¸õ~ûýp™€³ªÃ÷Ìã?Ï·¨Ñ ²Š»’…]òÉùˆo¤]gÏ<à3û×íZOž:™ácH`«*4¦ $fÕêúoÖ45ˆ¤ÔC0Wÿ,"0ˆÛ0æ€QLìŬ•£Á2¦™`R+`w ²¾•kŸ½ÜÁ Kò@Å[ÚrúïŽÎY;ðå–@ üf¢zèÙw„hãíõ/‚}%áøCâÙPn‹¯%  gh†ÑÜ6†uФT$ÖÛÔð|Jê 8GÔÝØk¸t—Úp0À+M–G¼C„þ£¸OÂBfFÒ“O ûÕ‹*¸×xhm< öø»ZLÆî™m—ܱèÖÎ?3t\SʵšI:ý|Š˜¿°á¾:øÊKtì~"+w«¨@ùMúKC‹ñ›F,ˆb ¦šr²ó«Œ'F‡Åäü,kzRË?vVÁ¼ùjžä}-Xõ<6P „hR­i¹¿ÜÈd^Q!}«¿5|Î;ß²éÃüÍ[ªýTHÉŒ>@„«ø~ÿsÉVj$_sTe‚²´é:ÑšŒ­¤o§%-2ðY×åí¬9xqǃ¯ÖD;~}j‡[ž%áhå ö)׈\ç0wóÆ´¶:º+S*ýÓç‹­*âç~©ÎÀ¨ú§ƒ¬=6èf/* ùfÂ7SÔ\!“‹×Ä2iW6u,Ðg¡^½>=¹Ò„tJ³ó³mQ`.§ÊÇ"°³`TRk˜2÷dsxdÏËì t87”dÀõ¬0ìÎΊU #ÈÆi¨5jT_O·õœ&0‘ã4 q>8@“Æ|F6doö¾ ›ãmÇ¡ƒñD™Ò»ÜþÜ@k¥[ðÐFb,‰ÅYñÓ"…l Uè§Ò÷5-EVÕ±°eLó¿÷èG¤å~%‚+SÓRLëâÅ{w–Ë„Ã˜Ž‘Ö§#f3ëü,íhiæ8ÚE"¸OÄœÃ<·W™ xЪGäè{'FgDŸ±1Ь$äH÷8£ÿ\à n”kwg6¬Å£m²@O„ÏâÏbármÀà>¾™pÍFU¾aJRÙ¢#%çE­U‡(¢–rÿ+¿äYû(õ&'ÙÜQ>z¸wµ/qMOÇÚP9ìŽ^Dø’Þ<þ»@;âô½láb†ώ͹‘ÒÝ0#ýjKŸNÆ9`¡ƒÇz2ÛQÍ^ÿ’bŧ÷Óc¢¦fg B‰ìž‡kžÔ̵nÌK«Á«ÍôÎ]óq÷øÒ­}ð_Ù[&Iäc¶kð?²Î8¼IáÜîÿ_½ okSšz¬7þ¶÷2U}ÅdzY+º3óÏÉ(Åü{bp²Þc˜ÇЧÈß>Þ(uþ¤ƒ…,rL†ËwÄ »¨‚ðô úeJ¥ ¬[êÃø0º»à* ¦ÁP5×›WÀzÃrùMý*J…ì K·CÉÛª°Ÿ¿’Çð«ýw{í´œsÿ•дTâ<Âè¡çîø5ÑM­£lÅs–Áçòðõ§„,Vù73zâiûM‚—Ý¿bFÚ!1Îè{s{K. v/w.H•;ê¶ s$0“sUäNÞµÊs%ìžn¡AñóØä» ‰PF%Z’ŽOºµIk>gÚ«Õ1ïÀ©ôž’U¬õÉ$µE¶«×.¤üU ¤Óºu`R~’ˆ¢8J¥–c)[ÒŒŽÁâp¨B»—ÈŠÝ:/¾&b*S†.Œ•á·…4ø=¾`Kˆ1E…µ"mØ|l>fÆPëælHEí¡DY«Ñ[D4f?‘KP[>ÝsFÖ±Vdâ¨>ðnN\Ì<š—³DG{‰íçþL—V5)FP0°ò¸‚ˆŽvܯɒ„æE¯OªA¨=MaÁý_מ¢1˜…²›;L¬A庬ˆ ê/²íM‹®Ø%Áòöœ²‹0Õk:û2#¬Ô+ $8Ç1ÖüßQœ<;øÓ±:¥­!…ªQ\ˆžöÓ)áû­0‹ø=Åj@[Äe,Ef;×X=¸)tjkª:öTé*“â'6¥ÞÃsÏÒ)×Û EªŠßš‡Äïªo4Û?•3ø¾VÉR IɹER+Ãl¡m¨×’ËE¬° 3f‡ÿ½D>Õý²œšiРY1lh.#ïŠ6VàÖ`¡–.ü…±×cÍsWéW¸¦Gbõé`õvrßàuÜsÞ²Ë<’*Á›©xôªN>,!ü&{„}-é†â^xª½Kâ?poZ,<&à|âÝôÉ·'~°¥òpâJì¨à"ž+Ë•ð´—ÃPèîò|¢ÑLJ0]ÿ޼ßÑØLr…ÌÇ,¹¼ífHƒeŒÐ%œšh*c8â×Å~ªm×ÄÏ=Vá hýoìób–ŠÏA‘H2#¼æ-#"5(XZ€ÏsÎÏÆ@ðŠe,l` è{j=Õcˆ!ÓÕµïhKǾè#×+8°þ£ý‹ û4dSàNÍaϢؾ¬Ÿ|B›ñצ¡|tÔý¦:m-åÂÂ(Œ-P/¹`Ž W›“áàÄu"(aZÁH}P°!¶|E6û›ÇPY9¶BŠ`ö ìÖå“ój^gþw$¬—3ÊŒ»ïÈó½Pï%_E‚ë \Ûô¦#5¡Ïk-Éz3B|3qc9¾UÿÛu…Å@ÒâhÀ)Œ–Ý Ì€ö`ü— ó6¶aiy®Åí°”ä7µ +?q« gäýF=6ðgü”À ìóZš–Õ*]*àÿ®°œò¸@.€I2 ‰¿èùˆN‚Ž öø2QO´&†f ¦7B8dNƈ„ݺ#/væÅPq–ƒÙrçÔ‚±f·[Ëëgê"gvÖŠ,ùêš÷Kz}<’În£D¹™¸z›©¯§a®9‚ÒlÚÁÑ¿œš3Ù‡3ª3*¶œ«Þ(ŽS²gÔGËòùÍMqŒf䛵¹eŽT@‰iVST•mÕ^ç@uT° Kk&¥ª†êv(=zd%«C¿15Z” gY§·¯¥Mít4 A¯K·L4-G7+@€“^–{EP©`´@lŽÄž4%s ®`XEì8/n²¾§!Ÿýš[[aJCòõÇòCÒøOfe+&R@ûû _‘nÂÈsÂ?ÇF.¦,KTýöëeËõ+ìü1òÄ\’¯ØnÌ]Nƒ¤fas¶âð:1Ìn˜WüEK£ c¥A®¤·Aüû^ÀõVRpš÷¨¸aÂÒomÀÙñ'ãjº'`6Á{ª|»÷ä•—P;ª>A5ÿ‹ø‡ûr¨A˜=ðt<‰õ© +ÒQžBOßg·«#Ñ¡îÃF™T-Àpâ•…\v.oYòúpj+÷ˆÛ#)¨D¶ÓÉ{ØXãܪàLÊÊÕ Gq²E¢˜Â?¡´Ð&’DAÜ >«AE!JAyÐi¡óù×  ¥ (4œÌîÞÓýb5 ñ½$³#-P«“&¤Œí´œ(àQ‡¼ê™,²5·8%jÊÉ%\½MÚ(m›sÀ±×cχ÷šc\ˆójª–e„ ÊP< ”ÜòI»l"ë2ÛÏ`•ð7ß/ŠSXDR!¯'_#]ر J‘¹ŒH-cÖ®ôê ^×h÷tk?£¤tŽv¶ØdµèËf^‡ôÀQO&³VüeAl?Îô;?×%O`7${iLvÈt¡…ü2…? „.ù™ü%G1èáè‘Ë)Œñ¬²² B°&ÑM{Jû^™«’°¸°VhA1‘Wu¦q”í®å˜Áç?ú1Tf»¸î¸!r±ÝïR1ŸlväýÊušyéªóØ¡X'Z–V_šÊ}ù¦V`f­ÛÑÆ„êd˜ýÈ+hXnjÑ{u^|Öw{@‘ œã ˜XŽÁrjdžìÈ#ãCÉAZÅ8ÜÍÙKοîyAºrz õ`ŽóÒ  '¦^‚Ò8aR¦ÚÑæåñTÙ‹¡†˜qcºüÜw`†–C)zx¯o\Kñ{³ÆvÉŰðó²º'pE‰>L1‚€D˜8°ˆª2¹’X¦à¨ë{x£Ø‰Ômîk†ˆ}?™kÉ7¼—‘+ ƒxñÄ|?r•IZ•h6Ÿœ(ñÕ9C%ƪ­/œ`=DiåÄ^tŸE—«-êƒvµÐÛdÎÖ °´¸e`¾œ.8ÀÌÖføÈß·¬é­VÞ%\ð3ñ„)"χ›„Îê–ûɾìN€˜=°lÔÓ­´#RÚ=B«¢UbUÎT‘Yç­E€Ô±Žø Ê™teo%qY·íCžø;¢Pä»&Ä8J°Ô½ár±ê½5Îï@ÁR¼5«¸ †»¦ó~>½±ŸþÌ;fZñ15Ó5ËÅBzË1(pISû¾æîж¡“¬È4w í½ROq½lRF[[+{oÚ\)§’,ÁÖ|àÃc…6;Æ|Ô„‡d–PÜe•„'Á¦Â §[¿HùñòPýe²òq¥WN1‡nF5eò…Šaì¾³]:ÈÔt ÊâLÝRŒ>z/„Uq÷éY`}»v°»Ê@k9­ÏF¨ø·ÐMe|£0Ù/ŸþfqDåc·c€SÒ C®Õúv£²ãp$ÅC›:'É,ûÈ… ‹!Z)t(ѱ°Ø\5šÝ—7M̮룔Š®^)Ñlh ÐæŠãu]” y!ªxoÆžœ1u—Ã÷KRÑ9»}÷3ˆ¥XÞOíKêÅ.!,Û —Ô6xä$6Õ1õÓåkö'%Èa!b‹Ý§ÃÊBžw×O Šøq9i9™0€…ƱÌ.@ÕToëh»?øð¥©<ë7—¥ØL4ÈÈb…/!Šú~èÜñÆ­îöè­ ×m`Ò§Ý7)`Éq¤–z•ã¤êª€³2c jƒ€Ds/SäžÞÑ5Já.h³)¯ÐžFª?̸uJ™ëIƶ½fÀ âF}9½ÜšùÌøuàüƒ*F=›Ó—á•H,“j®[Ôôÿ½Þ‘5/ÝÎ)®Ìb1(ýE}f¸~¢9_o‚? Ôš×Wªâ…Ns©šŸ' ‰ V"ÿ ¿dL¤63ÿ?²ËZò‡‘zaŸ|Tã 5æÔ°©À¢­DYJèÇ+QǶ¬Ú9«J“Tll÷Ë>ïgD}S¬Ä/!¾¬M45lUyÚ9……<768ŒñPªˆpí´òÂêï<‹|¯XM¡bì˜`Π5§a|·xJéëÖ^WOïmNÝ5É"êk÷M¿Œéä8—ÛK‚91â½éÜW˜>[¤×ÿ]l’QK89æ^-$néêŒ",Ââ1”Ð-£íÁCð˜Ï"³rõæ˜ÊkÞ€¯ÌcÃmË*(º”ÓgÏ«>â³VÅ9EWÚœïŠKBPÕ¯ г³ËAlÍÌèš›H´uÞ² |гÖõãÿ7²3áè¨òI©¯ôšvß–['p›i3¤Âmy(è¯ Cz+è”jz›|Ñèvs8—ñ¦ˆKœ8ø3G0¶ôܹ”@¶Â]8šÿÖ šŒéáózNþ܆)†±— !\j{eI¶+v;+y§îõB‘ê5 öMÿì¥CªÈ©‹uúšÎó™4ô"6–.Š[àgÓ_ÅS*‚(“±(q \_OÂ`ðW¦}$BsÛÞ[¹-Îîv‚I‚ SŒ3iI7Îà ûtOaDþº«îEêw4NXóò2CÏÁn¨Û eó†ß×Ã;¥JOÜuK%yаë9틯T¨YŸ5†Æ˜n7Ê")#g¿ã{–0‰šÒõ£’§lAÝb"-à¡F§úïÓd–ajÂcîºÐÅÔ>gËM¯{¬M:!øµ‹–e~ÄO ÜÉó¸;6zé£Ú)9²àÒ7Sl†ZXMˆ‚²¼¥ ~øŒ_횬ÓE¸ƒœfmê&üq~n #WǤCG"bÏØ]‚#yšŒlÉñ}³Ð›Ú3¸Ö2Âý'š]¾ð}U!/× g‘Ï©JÙUÚ]96_c…Í\ƒ5q)àVHýÌß¡u_¢64¯¯ã¥¯`‹ËvÂÉÇ™nPbÛh†êÚáD;¡ÈÖ~üú€°* P7@Þòe¶Ùö0F‘‹Ñ}ˆ9—ÁPÁ_q™u¯'–¢(`Î+,âÿ‰™Îõ®ÐóžíSCL˜ÃÃZ»’Ñ€€ ªŠü@]2TßëE e•ÝÒPï„£ö6,.ì Éí¡˜¼‚š7߉üIänØŒ²C!9L¾H ô0@ø‹¨‹xý<£´>pºÜ¸[¦Žç|™–³ÿå‡Ó— ~È}VãýAyh§sQuð,‡fÜc–õ–cÇ'KØeôa"a‰__m wU©¯¶îÕ·“Ú¿¾uZבì¤ÞÓüS?"ÆÐ/7±8á/ö¶ ¿bG(¦|ø½ÿÆ—ŸÛý½ï~¶ÍL³˜T¢*æ¢êå^þ½…%cHN®KÓ/»l.’ûÐ/L£Ù|#9¸ÙNùÛWúLi'w}¯^9P‹HâÅëXG‹VŽ¥z-‚ô¢]žPY“$s`!eTê™'™:)Ö3Rù`ˆx×K¨Ð‘žÜj#Ù;Jdà¾x¤ ™µDÍhþZ³ò^® @µÖoçè™:, 0 Kƒ“Ïf89´©0§Ð4áê,éL{%$`æ{Ñ.R¢SC°$÷¤ÇÇó. ÃPÜüšÓêåvB-ÖÎ0x£X˜|úô]è…i=‚#ë­žŽŠÝ_d€» ÏÍÿ~ìð;ÂÍÈ ÊØ¼@t ûûÕÈôã4&ZLôؽO‹½ƒ³Çoo[¿f¨,ñ¨µ„ðÄíWñz®¤e$ƒiYme‚FA#š·Y®™Áú÷cüQ-wC?»#UôYÔgf9*~(ãôõÇË^z*(ëoŸÐ½´ Ý©ä–ådÊ/ÄP¥ò±—})qJü3¤ ƒç¸Eà‹¨óúô…!A»y´.ú…QSô)WÐn6eåsr|î™ËVããóLÎØv.wÛˆ¥ûùE…6ê;H˜=ï«êRI->í2ÃØê1(NKà«{é„£q¼ÞŒ "/ÕÇLo±5¤ŽwFBÇJHX|ÑñNû¹ˆWÇøISH?lUÅÁa³„1%µ¤†.çUÅ9ÜY!›ÀcÁL8(Ï5rצÙ6—ªT"©Ó˜Ùã˜R¤iW‡<÷'(,8Ì´&ÚR¬{õ‹~ßÊÅ/Òe^ü³w[fý*E@Æ^”’‡œs†«ÓÝâœþH?­zùo% A†nOÁ~‡ìŒEñ€ÁoÚü08ë «wu¢==Nÿμs«+ø?*v±ï²ŸüdÿìÆoí=÷1€<¹àºî$4øšVEò£¯FÉ‹c^í–RM6B”‹?³[²g‰VI('>'øºµUg{ì;ºl*u°ëKµÊ–öâôäÖ«cØ d ¦çÖ/Á?~²ôÏLe•þ¼Ò`@˜[r¸‘©¶JfYw7˜@P¨2`&šÜ oÍX™ƒ ™¦u²ó¥ˆ§ÎåWëmÂ:ˆ¢G®k2ûTÇJP­£° ;ûÜ6ësÏ‚xØt®,k“1ù°5y‘s?ýV`g¿à£6Ë;°ðy!W;ŽÃØ›`¾“ Õ³xy1Ûž)å­ ¯Ü=ÙcKÃJ§,6:3®Ä›V"äÍmJvd¼_êWñ:Ç®mu:F…VfFEÒk>L=,$šwä†"…Þ¹ºƒ[â†qp+¿gD/÷Ðo3˜ögî_Â,àvË|ö[ Úƒ$ðJÿ—Téò.bðÍwaßæ5àbrzQ¨é,„I˜t©A—Acè6R‰M¥­à>+iï@Wl35®²£†—™Á»­Œ:ÿ}&U:¢b„»ìú™JÛÃF”¡ÓžÝçäÿL*ì{«|2 è3ò¸`ñÖÙ©#ì•÷þ‰_‡»,¸ºøW<)¥R¥’ÓÝ“8/¥ªÛQîø8|GúÑ$f¡é%=r„UŒdÿ¯Àf—Ô¼ÎR×:æ8ÊǾ°ITëŽëÆÙC†-{î „@Mòú),ˆ¶0’NBGò7¦{¤)9ݸ8bxZÁ`g9å€H 1¬!‹\v¼a¬ÀŸhZ>5Õ™á&>ÒѪí 9æ0Àžr*q±ü°RT®kŒ©9\Í0Ãvk yðÂA)—íØä}ójŸcRÓBdA¿é1°ëw}ŸWué~~‰‡Þ ŠwgÎâzƒFï瑦ñ)®IÉýó(Šr„A¸Ó"OÚärÑÏ h4A·ìÞ6Ë* 5™Ñ £ôÈqrX-ÂlØ œÈÚûÏâU‰@ Ö%å ¹XÅ™Í*ª$£G³Ìaª¬œ×”î”,–ò8èïƒ-Êþú¶jþöÉoèÇ‚ÑÓ;Ñ2{åxl*Q£+q=ÄKðRµÂz’ÝS#¤SsçÚWk™lÎþ錤òˆ[—^w‡šHƒìLÖsµ+çÇâÁÛ «x;PRÂ¥ºÚéžÚ²ËmÂ=˜J)ゟÊ×RùvV¦ Æ?7×Ù¨#ÑÈQðxÕz€v!4ÏôµÌqˆ;9e·ú†fz•Árà±Á^ôk¡=—{¡ûf=Óýí]Ä&›t7à£hFÝK"ßÝAñ`50GÉGÛ­/جaÊ€§ Ú4|’|†0@gxŠE “€cô_üê>:ilz¡´^ý`Ϻª Hžx¢©ç'šc—Q%ñHÜ)¦URzîQ `aý‡o•°7ðÍHè4ƒº;ð»D©Ü0ñ †ãfq?­ØfPݧ尫œ~¤¬|•aX\Æ#%~7e^˜¢p st¥áöh¶ÔøErÀâ…û•e•*®ìɦ¤Yèù¨2nÂ_Vç žæ} ]‚¤@/9\êYŒG5ÿP<G&6>¾oe{EÚrѵ]²wË;mluÇ7t4k&jbPÄø³†ð2ÁàâY<ËÝ^0ŸÖ…Ò–ÄÆ'Š|œN…‹6\u½{aÛ@‰#î5Þ[m¼.XøÛu Y¨÷6Ñ6:úˆ:*¡Ù¬¢æÆýzVò=ÛâúÆï«]'u+A—κ]/‡ƒ_ˆ©‘Ì­«sŒgWòájI-ª‡ÎÌiÎk‹Œ «ê·³?ŒÖ¥ÓÊGA¸ñþñ!íjò7~qÈy½c'©¯¿é N,ñqh±9Lôì”níëQ=“IO…_þ’ÇÄ`=…r+V‹ð-àÐâ]Ìøê¹ h¨Diª§ëTÀL§!H@¤‹ÔÂ3þW?è‹Ð@–€„N'›±•1f™ÈpG†XîgxtCm1žÙ^Ö±é+3™Å0w:\r$eßÀéžÍ9ûíäõ϶8ý–±Gioi*(®:ô t šé£[ Õˆ”O¶µšõ@2£¬Äæ 25bNV`ðCž=p+Ã5Ø–YoJŒi–2½Ÿ7*ïöŠ<àJG@)#AIzSƒêÕñ$FŠÂßÛèèÉñeF_&‰a?|bù&° UJ¥Ó¹„Õ0”ãÅ[¡"_Gfçh«|—¥ógH@ìÆŸó%]J¶'DÒäËM(z>w *'B<Owm¤*§ü‘!‚2Qº^ÅѰ;¾JŸÌüñR£¥X5ZȬð¹_=WDˆ­ÉœøŸ?§Ñ1–Àâ-Ít!\ò¨Š{WxÚ—&ߢ:ü¦YY‹n#ã€ÉË¯é ¶V@lf§Ò‡©$1hz •-‰]gnûè(zRd¼ÄÖÞV¡°3ĄĞþ ÝÛ˘%4k~Á’ü{£Ä ãèz¨NYâu"©¢Ü˜;|´\¥ú삳]“V´ºs´òa¤šd6“•¸0Mjò¯ÂpÁ`+a¹É.ÛÝñ;$BÕjãœIÓŸjõ?O6ÊQÉyÛï¶Ä§Îu»NXÛüCÜ;ÃÃàð/¼ƒ´Œð‰ƒÑ†&àÛ¥E§ï¼w#ÁCx¡°@’é3ôõ™‚FÑ)ÂÛ¨¶–À:K!Yq'š®äÒC‡ rnSfo C;©r D dn€ I=³W`aÐÑ ~dg‰KÞÔ%dQqÀ÷þ§¸¹þÕneO ¡Ü°PQìDØùÄH­#gÕ[ôâImZ΀µÿ~Hí ž9ô);PLCY£4Ö}ØøKzÛ‰„ÈTDm›ùdö ¥çáp[Xh+Öa Nòú¶FóÒlâóÖÂ(‰$jÙ!qƒ-ÚÞ&ŠuÜã$ùrlÀS´Þ")[XÏÖ#ïÓ”Jó( ê¹:9:AéeÇbÐOOm(Y’ŠåÂÇŒ}pézo5(¹àkÕvßÐ0â]ÊÜ{åÙ£=é‹8Ä…d+ë4w Æ£ØDH3é¬!(Øîø:€iá90’íx‰×w~]†|Ç®‘… Z‡T£N¹lG†³G+ÿHÐQ¢§t÷S*…xk(aø§ké`aÝ›ˆÞÿêõû“vH Õ‚äÈ’ví»ÞÑüÕAaɶ[ vºÍdÜ·˜¥näƒo‘>î1‰CþˤÃÕ‘Eƒâ:í¿gRQ>hK£n/q³í8—á"£”ö=l¤PxEŠ Ÿ‡ ¼Ä=äÌæüÌlȬ·ž›Xþ¥AŒì£ ƒ´Ç* r7ýS^“;»/o‹õœ…,°ò©x"â]£(‹í¤:+†q®Oâ"ÿm»'ú®\öH•Csx½¾ýÀLº¨$<ù:Jqâò\YÒBÛUM5©ÝØå‡±1‡ 4ú5z><Œ‚î…¡4†Š±%ûx_©õG˘N.!½²JC¬‚9RÝgÅ|~ù{DÓ&=î«9ÆmÆ+°_ýczN×kÔ’AÿÐ9¿0 ¼x¿Ù ›—£4‘! n éê¦ôµ#É• ï™ñæ¡‚9OcéÒù[ÈÛ;W>·)ªoa]ÃDçyú\‹=üÄyUÜ$÷ò²Ú¥/¹î¯¶ݰF±ê`à]§À¶TÂ+Cô¼À„où´)Æ…›æ)És-ïŸ}ƒ»ïnQ³í ˆ› 0†7;1Ãg¯L˜AfÛ]{FçÜívD 9ÓÊ ef,®µmÞña;í—ü/ØõXTcEÚ>ôa½GFçËòú«ì神Ø%^B±¨“¹ˆ‚µp%ÒqóͲ;0…óD"J'?B2*³ƒÎ_ü¿^Î ”ÃW”òhÄb ,‡Õi5±wDØæWÚ‘SZÕ´9F.ê`%$°q¬×õjÀ>b7]güˆ$È–28$†qÿ•!Øo,c€DˆH4Ï}.…¸?ŸÍÞÌ-8BÕÓ¹©£=gÜvúàÀÎ…µ’d•»£§ø–w´“À桱Píä2Êé$´E“§ŽÏpê°±Ó*Ь'³Â)åÍ|³¹}ÅW ’9‹âÙiñû«‡ödB¶ eåÈü¶Ý`›Ý$Ø4Œ­ýè ýC!œç!÷.ƒ½…ƒ ”ú&¡p@f ^¿ø®<œ¢—55ê €Îùâ^…ÄIX¢×Ðr±óùGË ï Àˆ3Çõ(¡á€(=œZèyip¸¼]§5$ÏHþ¹Â6©8´¨å7ÓSa[èõ?@,-ª“÷ ì3%iÔ·»ð*?¹sqþ±)]ʬ,&É+ä7+#J‹ÆÕª X»##€&6ø·SKÈê%¸íM÷†':ÇGÒ×²Íæp××#SõߣŽ#ê\p±ã‘‹aB˜ç–ªI–HÑ…ê(¿ëLϧê/ê`/Z+"mß9ŸO¥ø9ÑÁ‚K¡V=ø6…@”3“‰r#÷Ó§Y—{ìe³5鮑ìÌ¥Îk›-7¹Ú>£Q[—åFafò9`š{(Ȩ̾<]BÓ_Ó£ªð‰•Ž¡ï‰/ÒÆÇWHŠÜÆïwýöxΚ@ëaX¯©_C®Î˜ÒØ*MÕS§TÆr¿q€©''ÙâLòy¯7)^Þû{EÄ•.{Þ¾á¤R6çHÒûó«j¾¢:€aj<掬—ë`è³&Q턈ñ[a̸ú`h“x+ÔÚÅÃU‚?N ™ÏZ4 @Åé"þ¸4Ž*›¨[ì ž[I‰mã&9KNÁSÐSgb¸l­>ËK]NÐJñú2>Ê\;ƒqó Ð)Îb¬?¿òy¸› ;È5(êøR ´ÈuÕçrÂ\l\»“µX ÿù_qü̜ӾHó9|Çŧ2^2_7&ÁY^ª»H8vc ‹wÿÈC°tÛŽt‹SëÞ×? ÞnÖz BÙÅ6Eñ¬Q€x5*¼ Ï ³´]Àî³/˜ï„ 5û<:M€#À`t½¸cÊ;:P«¡P÷ºÄîOšéHŸÁ·UaáÆ'OªJ§¯¢àŽÎÎl½ýq© ¥²œÆG7-kXÌœMîqÈ^o2qnsÇfO—ŒwÒtŒ:Aày]t +Ú|°²þRŠìË\è&;“Î[|ÉZX½5éÝÓ™’Õ‡:Iû»•» /ŸV_¼ †ÃäÎg±Pö—8w¹jÿY€#\¸ÿRCc-ßù¾ž#ƒo ¨? )–HY÷z0aýŽú¾‡ÍØi:‡²C¨D ©PGtº= õÆ£¯Á¤‹Þa'Y×ö$´²06üaX¨3Rù’OЛ´®V(¹ Ö­hà¬~åÉŠä÷‘<^%ËJeD;ñ¦]=Ÿ JŒúUhʰB]ðR£_Y$H/]ÑF ú7±2•UIJ? µdWÜ+\µÉ¾Ÿ¤2œ.£Ñ ¿Ün×·/qpšvU=mm þe¥ý>ü»!O•çÑØíáþs‘ÄÐÊv⟥¬ö[ˆÜ ù•y1=÷÷ï­?¡ÅyÒXÚÐaì—¨îŸü?„diöWÝ©ÐðÎVuPž|ƘÿR`§­Ëîâ°ûžVŠ‘­˜The¢"Bf)7Xøn °â¿ý)Uû‡ë$Ã9‰î‘ÍkX„ ß?=”²Ó kyPîÏwá)±ŒvïòE¹¬â²É^NëöAøKøkyVر6ê<ðQiáÃÌ3Ô*>ÏsXå0f9e»ê{ÀM«JµäYb/ûólè?:$UZ ÏzšÁX=ûâzâSG¡®=ëU#‡A m™| °`i·n·¹·Z‹›=;@<ÇÒ”µ¨4çÓw–Mä±°YýX¥hF.¸{¢kŒbÑ,ÿc Q$Åf ¾d»|Ãì$ÜCqÜÇAòÌÂr~¶ð^Y ýmÖ0¡öU*á‰äÙüÜæF‹OÝÇ8ö}¢³±£údÁiÃ÷~Tø~º=ÆGEè1;·É̘efØF?FHãqr_`üaè›M—ÍíÉöß×±<3h‰6ŒþPXÔ(Fð%N¤^oV©v ï±Zˆõ#­”8rE6®àÙHô|.ÔœºØìçœñþ} Ö6zàR¿ËB@À¯Á½+5Þî¡¢UCnI;0‚[ŒÁ ³*Ã"±ÒÃתúø=MnE3e§ä×^e–TÄ Q•£âÕƒé,æoöµQ¥u ¹1ü±Ï+¼–t‘Êëêü;>RÛ“‘E#±ó¨:AŠýõý ¶=´Ó€‚B7A>*4ž:¿åÁ}A }¾î¬ üȆÃ+­È¤êôç7‚4éCÄfjå­DJ¡ žJB2ɰ$…q³7gÍÞ³Œ¦Do߆¡1|ÕÒŽ”¯ã‰èD·U<#ìš¾Ï?L¿°ø¨Ðf_Ç„#ºrv§¥!haU8Mt†šºˆ‚ éöy#ü œ½^ùd®Ð€¹Ê~L þÊÖE²nÕ{„QÕ”}Ò'ø®³ñš»Çè2Rä·›3zŸ¶Ò…¦g”û;#¼ V*üÍÿ>-‘AQ‡³dgl¨žôþ-Gt ËCz–)Ùà65Égç ýÙ@©ó¶S?©*–Ät`Ë/Ú§GÝP’)t­¨÷+š¤TF¼È³×”¦i uS©šyv- «)9’à‡'·º.­Cl5é}Úçz«^›·ê/±—O¥vëA…~%Ô ¿ðd!0cú7‘¤R Áo2Æñ·KáG½´¥ê´°€É¼G¢-Qfø.ã/›ú˜×{z€BU}ÙÓǯ{q÷/Ô.0B®£ŒF¨üÙWf¨Øêã=ô'°§Þˆr@ DÀã¼ïãQÔƒ#³ã4)$\вííó¡ÞóÎõ’ÙWoõê&Ôâ·q/ʯf·8¨«ò¿¥é÷Mƒéä}É6.V÷Ê­*(»ñþ©¬¥þ_¸~ý÷¹nr¼:l1~Z“¾Ëù7CKl–Œ$.×*÷@?Ìê„"Âަ„‡ƒÛíÜs©?g]ÉTxP]ì;Rv]3›99‘jÞ4hAx›àpIþLÆp¼kS‹ ÙËÄŒ‹!©©îPUIŸ 2=4sséŸÇ}Œ0æýS3£(èTNº‰«Ï¾j¨ =õ»¿MxA”k±w¼d‘½1}ÇýëëysE)à’ÒÞ¡&‡—ªî¨y£Š‹T™v̤¾ëà&9¥¨Æôh@ra¯I=ݰúÏÌ/‘Ʀ9†Ÿ°¢"_LK½“Cä5™}Ïñ^äCÁaÛDhø¥NÃuÎd\ÇrÌ]N’|7º@*{‰âƤ0]h`×Hüì-c~#x3Hˆ®%]¥!>Í˸Û)Ñ"v¦&Iî ‘ØÜ3kŸþï 8 üËÞoc-T°0Û2>ûOu¶Å™¥Ç#Ø•/¨›× Ò¶‘{®î(ŒÓê³»¹5®Œ3ïâ "ÎþM(ªhé;ï…RS•;Ã:6ú³ZQ&—mu+Á]ùL¦ÖHÒĹ©»ÝÎ÷³õã\cûÖ’ðÒmšY ¦7Ðル´ÿçÊ2ªh÷5fÞ܈q{F†Ry|®t¹¡;ÎS ‰#c i.zóÉ¿ŽB^æv}ghÿÞªî<ÓÉpUß½ásËÇwR°ò¬ô0DÍÜXÔ¤nÅ ~‡3½Ô­&Õ™ ë¤ÜcÑ_E¸88k*4 ®o‹hSwæ”Lwryî³WN®'¡Îé*éãD“Žš:Ö÷š‰øU"‰îjª®J ES‹,·Iíxt)1A ùi:rÍä‘]¨qËÓ ¢%8 `N8H“6HaμǻPƱ«øB&~¢Ô÷ñv#ŸChÞ\2…J ë‰i|‘=¨  jF±­©“œÆÑTø¹~ï”dìakÏ×¾E¤°#µÃ¨; ;ÀŒæ 9e}k å÷™¶…\×´¢nøt“Òbi½¤ˆ/z®]‹Ú{PŒ_é?¾òy½$ õ—‘+{0ôPÒ턼Úí#ÉoVØž}í»;í•´2¢Ÿ2œç²xU+/ §ˆl8T¼ÛÒ(³T*t‘ñý†ƒ9÷§i°BDï{*Æ“À¼¶v´XµZÖ³¯xkôŽC˜t!¥saç@v‘vùúþ2ï5Ë‘$Ôú¢"nÔ×”c½ís䦔KëëýOCýßS\ÝüÛ]¤5éÚÐÛ™çÊšDæ]©»iÝnù×ý©È j©eN¼QA®7>ˆsÚ.R_&£U}CX?HQŽdZ!"‚×76ŸUš—lyfò2¿9ð(I»¼›:óíŸÝÛ‚HPæÊ>KÄ‚»„ oå,3ÃWË>燌©'ê¸$‘´"CôBMqC=ˆ&çWõÑèT'ˆ,( è îçVì+©Eø$2½*ÊD¡©núŒE­Jo}|èû0 ; VÞ¶ê'ÀUnÜp±ãb Éqvæ›X”UìšÖ¥ô>¿ÙwÞa|NŒ‚…P¤¥Œ›Í¼À-J>ªE÷çžÁ½yl%džcCJyâ2õ­7 ;ÇÍjÎKK3Ïc|Ïç ±­úA•fm3ÙWÜ•mmÓ\â‰1BóCìÊ€õø[}e©¸Ù_ÄU2–F.…oúÊe~Œ(ݬLϽu?v®M8í mIÌN•ÇÊ9+àOTÒK!Ͳxí‹&δ•;n«B…nmöëãýd"±RO+˜˜d—æX™½§˜veæh¹‚u¤÷}¹;¶‚@ýÛŸ=¾dÝ,äÜ9MÁmŒù@Ý%r¸Žµ2 ¼PZF2CƒYXÒÎ DþcïõÊï >¡âT¬GƧ sâŸ`߬]Ý}“f˜6®¥»Kòú2Ø\ñ O¿zÓÚàÔgGüÌÁÞ\øŽBí•Ïuøµ ·ë£-œÑ(ß¼Êæ«`(/‰½ÇáŸÈôS¡ñ…Ø5~7ŒZ÷ûæ¹¶F9ܸæ¾ÿAG(]âñö}ŒOAÖZ‡ ¹ôá®e.¢ó´vocj¡aŒˆ£V3Tã©Qt¾íåÛGü YÒ}‡×3§X›ñ—;d("ÆD í_Ë,JlF®`½tJõL;±î»ÁXÇü˜-žþrÈR®•øòf_–š€®p'lå2BT2« ‹úþ¡ä´d~™/Ÿ¹évá €R’½Pô)~üb,ñ\ÙWq&¼®Hºèf‘fbכÌ9Šò…Œ¬»VÀ£Q§³ÑéÑO†Õdƒx“^oßDŒ9§n·ÓyX^Œí“cL «ä^öA¥j‹ïü-İVâéýŒÒ'åŸ"S·®bâË@?† åZœA%Tœ±æÁDÈBšîX‰¹ROÆ^l»jÓ§¼‘“4 '¦öñÿá—¶9W~’=—„ƒÜ¬ó‚-×3bW:R‰Õ±RãLMŒ¸ôÆhz5Ä¥½ƒÊ“›Â*ò¼D $7…VÑw¬JKŠsÌR9&xù«ëÒµgó—ॄ¡YpÁXGZÏÆ-ˆ7;ã—‚˜$™ô¬2Õ‰úéÑ¢¿&Ô<ޤ~¯Únéñ-N¢³œd[=æh5­ÒùÍÄ6ÓîË`îŽÕ» ÷:p±{ƒ”Xºî©€÷«åš{ ØSÀÔ?Öâhá‚ÿšõJœýŒd|»Ð%L úJœvW!(V}À©q"<ùËyÃ:qQbyƈÖ?°É]RdœŸ;©e¯âôÆ”dr¶ Œ&~\–ÒÿB{qÐZ ê ·½„ÇãU§ŸpYœ$ G‰•~øº 9ÀnÈ=PÓÃѰŽ.QVŒ²µüƒð áðfXü–'¸X ¦âeœßU­¹Ç½;>;cµ#•f3»jÝIÂn1‰ b’<¡©Bä¹üˆpò8å4†´H0rà­•ŒêÖE3‚³Ì2üÙòò”–bá.72~…HµXíâùõÎP;÷ ðOj ZÞl<ÈNiï»t“¶xdº$D—|'TÒQ š+Aáe\ò|-Áˆ,¨& 4H˜­¤™™®äRZ ì ž®œê2Œ¿Ö&ƒ" uÍía˶æ]ÒH¦•dø!-—%0/’ú£ÝZÂ@9_A'™2•¡azâÉÿôË¢U¨ƒ Ш`(6ÉVsãÆ¬¥W·ÌMêÿÙ4˜ÙùŸ†<ìÚ­%3fñKy¦”a>Z4ãeªà˜fe,wtdRíCŽ­ª±â+TЛ_-sQY]œ– „¬j pŽóµ7j£hAôäù4¸âs,Âyv“è=€Xî°»61#F#4eÐ-µ!Jov¤.=* v뿸( Uv6Úw¼¬û%̆dëânÿtÉ¥Èïï'¢&®X¸’Ô ápüVÐ-ü:}}…3%Œ4ÔÕY-Úô§ƒ¥eòŠ9Æ´ÚE·h7D¦“b‹åÄܘ¿Ý)z1 òùùËèʈ Ñî7>Ί&0“¬ˆšŸTž˜'lf»Õ£üv ÜjÜódÆn>£êù{e£ôÚ°D<(Bü€öC*÷wæ2ß4!m¼$kêǸMDØ¥3ûê¨ ÏòžÖåÅ8^Ì»\”°±¬J„¸Ëòû(žc>Z:­G«_ª+­4°­ÔkŃ„H—@q±Š{¯ª“h{„¦Í—üf‰üÝUÒà:5AJû¼êäy¡atë_—òi"ýRÑÊovéOÞD´´r=å€È¯<Ñ÷|LMF0h[!MVÞngÌ,òJ¦óNŠ„¹è© <˜Éã+¥«‹új ÁxþסSæRb`ÈZÑQ¥¼”TÏyOÖœ½Çõݨ¿3G/tÞÑ5ßIˆžnZBgµJ3Sä›3W¢¡Ö ˜’ªéh¼ýpçiyìRÒ.¿¯‹ý§ö€qˆÙ H±‹º´wÔBpš,‰jsFQçÓ]—è#‘°ÈÐNñá+¸Ì.;>?YÍ&¼àÑÂÈÿóýž].G­*Aدҵ„[7ò¿ïœÀG48ƒ¤…ÌéÒ¹â( ÝWœ óÝñÈ¥èä“3,«(âžßäz/ mÿhFäV8×;,C8PLÍÂI¢¡‰òÉØ£yñ„'ÆM|8_+P…™UPû#Ë×Ó»#ØêvQÌÌ4”ÎI|g(2ÅÐàòmz_ Ô, rØ¥šz\{¸žäQáaª]ðãQœ˜ˆóq Z‰ˆf»$CÃþ¡”Ht“/¹Tô"/EÕÖé ÁæûVV¬(`¤“¾€$ñ-ÇB¬Ç­Jk5“5…]ÃŽõ·Xy1¹úG’ïÐk{Nª{´ù;/Í8‰ãC›ªªŒH+5ë€ØZôô~?iêŽ` ‹ I‘g(ÿ´˜_ u3}ØÂ6• ,x¾šdÄùy°#Å ]Z꘸£÷û¸b4K¿ðÇŸdù6LOÿBƒ¯Ü•‡¶¨ *LKKM,Rº” /ìæš©?œD¥uŠ@v^Š.K±3Wd\A*~ùŽk¹ ¤CÆÂèÍ¢¾;åµÀxsD渢?hºz•B³ó*±Ú&F:-”Ö7Á9~·`‡sítpßß:ŒASœù×|êÚ4˜Ò˜®éÒ2ÍÀÿ Ò“ü_„GÈjðnwUšå>EžŸÑwCà¦eë°€Ó"kÒ1 N{™„V› j+£}`ÇÔ®åh˜ŒtQQ£ëý ¶ìÇãUa1Å oîX²8Ò¤ë=ÑG‹A ÐlS;ŠEG¹è›sQ‘#ÿŒã4•×ÊÕqÔy^ìŠÐU†ý?ù;"ÝÇŒ·®hެúVœ£âpH)ÙfÃV¢"÷²kÔ{ÿÙ ÿ¿]DM÷_U0vpùÃy„ÆÍåM(¬¹¼éšÙcëpzîü™>p¶:wf›ö{)ÙRñμ:ÐõÕוAEX~³ZNN@$ØAä¾½KI¶S­ˆç´¨÷y‚Td%Aº@{êb5h„ Þ·Šƒ8úrC#uÿ Ø3ÔÚ¡¡@‡²qÆ98ø×à9òÃéÚŽAä®3н D¡*º½ËO±¤Úhm9„D2ô$ˆÂLÕùxH°ÿmð¸,ˆ8=`<»GoŠ2µ!ÚHÅëyW›>wÁ:_ˆ™J<Ý×&'9eÌËí$Z.I·«ð‚Þè³Àä 4YjbEv XuRq;oßHë’ÃCi–e»49ni1y7{]–1-À-Ìe›z›D!³æ1?g<¸ƒH,> ¾á<ê=Gë%86²êË ~êÆì(Gûºðö܇ «§ÜƒÌÈZxI¾Ã,‰ƒ›ÿ¥6snTd.mˆ§.§_/Ãý#GõlE%”5Hé$ZÃ+±Œ7{jœœkÓ ó4„¾aiÚaÙ6ùI•#È JÔRù^Íû”Ï_Ä &8 X Bn½GØÝyˆ5›À¾ÇÊ®T¤íªÓ@•žX+…Âõ×à3Ö¤˜ú TÑì|ŽÇc<20ô#M¬þ#|ó ¯dæÓ2Vµ¶S톽ÏqòÉw¾NlίԲÌFüAì›BÇÈP”@|8­’j9Üf=n©P& Ø~d¿æ¸ˆ{…@¸så¶@¤çi&ŽõE¯§.ë6ØY{º­Xçl‡}FkÉO= ÝyÄ$i½¤igë ÊŒ:™à,kýñп ‘ï“îTÖUwÄN~ èPyåÇ/Èèy;ÿXÆ‘ ÑÛvrŽÏÂëïݳG¶5õ6ü+}cã~íÁî»G={Ãdb²”âTwUÿUøáÚ¨P vÊB¹tµ±Z|£Ï‘A(Ž£{W WËj¸6jÁÇ™bÈ>æ¨é5|:µKuäÝ™6á‘}ý’¯ŽýŽ` sP0bÒ%e­h‹!$JxR=Xþûrè@´L2”XÐJI± j;²*Cº Z7Œk{È$ø¾€'ºD€Ž5  o{GAàmÈa«ÿ&­>.h M’{}+ý’¡?ŠŸ.M8$È Èº(_ïÐ$mFÙo»îocf+Ǩ,­½ôÛF*²vü6h䎼"½@zjtIšâ葌ùH˜JÊq®ù2µ¸è•]£ 1C 螣Ð=²¶ëkˆ­àSÈéÛH.Æ2«ñáF1]ãi•ƒYÉ×’þu£öôä)>ÜÐ#Cßñð¢°È‹æ“ Nšb ì.D̶$†­¯ÿPa:"IW1`Ê|ý YðôŠrÈ7 7ëÙŸ*|\{”2+æ#J§<ª°4!¸Y>$§STf„j‰¿RÏ¡Gi÷@L éºy†ç?Xéæ'0;QÙ#ëø¦Ë Ž-\Â&(G˜ïæÁ=.ò*l½û$ÈãO ÎÿØÀB ¹¤ÖB«ùpñn)¶xwo˜Ûåú´Ñ#ñ8.”© ßPþHZr ‘ëÌ7C-È;Ýf bOÑ@VQBɤõìç³"†öøK‡Q›6»îÚ4œÛîÏöº”¢'bÀN^_0&imO…­à±ïX³J~Ò&¼ÊeøË@ö’ÕQ™µjGˆzâáØÇ× ðøåìí­åY˜R,*.%•’Ž3ezÌO™ /•D÷\vÏm,ÌDF¯M‚ª:§µòš´_væp仞vÓ¦Þ€½‹B¨™˜†çHiY ìeL!›¢¯"e¸ø<»ø±G–xGYÒr϶ØÈf ( è˜O›ª0-p¶O ü㬥YÍe¨&‰#2/¡X¼Uî*¢…"Î(¾7(PgÝ:pŸ­’xOŠÓž§½¨ T3 $Э¿ÏÚ²×<Ó&:ú½(e9—r¾(Ü`ÔÛ%@´SñŒÆÉ[ŸÂdm±mšOϧÓ#È¿×C05èPê?…ÌÒ¦ÖÄäÏÜë«TJ`™•xôà%cË7¼ü§êéí²kÒ|KAUnj.l9Xµ_™_¯™’ŠÒLñ]™ªH¾W«áËõÜ£#i•¿u›Èí)P¥Ö p}sÚqa ÿuæÖº.Šš\£ü)œ¾÷.µÛZØ8à-\_+ÏL9~Â"Àùü›<Ýðáü*dù^¾ øððzã7–ÈZ²^—H>û,Ê|ju7mÕ@Uº)ï_ë]Pì¤<ÁBMʰùIúeªQ…)ã²>(w1•-uËÌœùõ‹ÑvŸ—ˆ<Í0¡0`bªåFØ}E,2N#÷CÖ ¹´Å¿ŒkBA,Eó']Áüh3ï÷:¥?õñžÞ¾î»ÿw6U_­DúžÈM(ï="%¦ÀÞ{l|{ÍÖ&åö¡‰MÊàÎ_sEhl£‚N7¦ðLWj´È‚Íøì_`¦Ï¿ÛxðgªÆÌLÏâß#µç_‘&¼]åe:ü:så’ðE¯eΜ~cÐÁ¾Øx¼qÑ(#û 8j« yÎßµÙy“Ñ'GeçæC»ã:to*'é.O7Km“6K›d6ž… þÉ…`½às…”f¡’‹—ÙFè:ðYÖŸ¢TM1¾Á•ST2q®9Šj¹m¾³÷dNêu¯o•!.ks°û¥F}¨ €€3¾ñÄß`²ÚÎJ•M¤Vä·ÖE6 í&Çxö„É2á8÷£ìgå4†ëÀ{üý¦ð,A ìÖh¢<¯£Sy½]_vÕ‰wü¼î”œxå@p¦—è fÇ…ÄØK±…ú»Ð>¡(ôST3'R–w=ë#¤O#îÌÚ«“~Ò®¨s-PÝ(<• ÚȨà£!žmºðê=s*ÿ…ò}ÙË‚âÛ8+lÉ’]„ªI,1á.¢go¶ÿuª©òwö.d"€–åU[³†6â\§öÄ­žÔ¨kȲ¤s‘˜û¾µø ù7íÀ†kÒ$÷!~°ï­Tö”›¹BÅëÏ.æ g#žÙ¶í!š}ˆ%»©Š‹„yËLGVzWF2“ÒàÞ|ÛˆJ+©e¯·¡¶Áº™tp‰4#*RÒ"†n²ôEiùiíŠýTy ñU]Šèº†ØPIº$ñfš%eØ`,,Ù{ùKê®6‘, 5¶½´x"TÀÚȺ:¢É%‰¹!ûâ«JåË4s/ÌÊE’C_–W°Uk¦²Ô“w~=¶²û”t$¯~¦a]tƒ¸^ÒïgoŒXT_#… ßyǃ}4›.,]ýb¸á;{HDŒ‰¿D^eØKªŽz?dt2ê²Ìœco ×Ëüé[Ö6𜠱ä‚ÝÛZîÓIål;À¢ dbŒ2ü«["6´Jû‡ëÛ‡†µ3·Õ Ÿ·ºC´rбv€H/,krì²®®ºRHžç†±†)2:h¢3¸³¶³l$ A*¤ëÉ倨÷†iƒ¶v&FX!y4ìú´§ŠEm‡å’|7irnOÎÁÃóּ栆ñ"®¢¾å„Q`‘wØK1Œ*‹ žäèaÎ\½àé34}ˆy} Y¶S#í§{*®¦aY|'{Ö(aX¶›µ”w,@ƒH©8s" oMµ¹u†YNýG=VuÑ-éÄ3\;¹HoÇüEdÞ°\§‰Wô^Ž"œ íÚÞÙ+~&º×LöIÈê$CƱZ®ÊÓ–-S¥‰#G‰©r ‡ɯ~ífÑà0× Ѷ![‚«áÉnµìJ½ÎÔÒË}]UT¥~®©! Ó€ìO#‚âR|ÍÆM ƒlöÿ¸ªW¿‘n0›ŸóTP”Éuwa¬ö/÷@“E„0ƒ¥{!”„õ0™[v*Çm~p!¿KúÎg¯Íä[˽Z>õÃ}® ÃÈF8÷2²jRBÌÕ†·ÀB ‹ôðàSŸëû:¸¯Ôéâx޽̆t:@U¸Lœ*D@äðhU&ᙋŒR›7FÂÕ³›ÈAÈ«eÏ5i3þ&Á Ä»Zd(Zbܤ'ê¿[±JuÔΣB¾#^Ñ4-[ÒÆÕV–;2Ì–¨`ػТÎUÝÂ{¨ÙâOr3æ,á0ìÛ`^£ðeYÎ^ˆSøv®ô]_{mIyýÉ.CZ]–Ä$1!Ák ’o"Jïº/I}ˆmˆì©ÌÒÓݦ­äÆ`ƒ< ÖS6Žÿ¢þ/äkÙ‰o¿PQ˜³Æ­Tãз»§*å°)—ïýXE†Œ‚/X§¬éïXô ù)_¸|R‹§fãt¡?ÏÛ ”úHM ÕSàE‚ÇpyøqWÚ[/n?}¸š8­ÞÜì _³ã±Á®‹î?Á¿ñI:j"· S×È¡ÛÓ8e¨«‡ —qÛ:@y£ú®aÏ!“TEü4ÿgåÉëcÖõJ4­"‰ûÒH³ÕÈwK‚?×ú bÁu¬ÑZ,ºË’QïPþk<tÐoèûÁ>Fî©4œË§ŠåGG‚tNŒ~)Òt!e±WtâU¯†näHÄ®ÌT»û½Ýµ½½ºAu¨¢bÒTÎ’mšM+üº½XN.l:L`ªÆh†%Ý¥OÝÂiwVìû;úè¬bS 5àÐêž#/n1ö}6Ú ò’ù˜3heH†€£ö6ã—{ü;{‚.g»ý‡[?󆸦-fQªW<äð– 糟Ož¥æ°ú1ž¶oJòRzŸCa¼é†Ó ãéDêæy6]¬ŽÄi:u“Ûž‰Ô)A/¹ýH–Ñr):BnÞÅ×ZLüKÏ%IF®~£….&¡;WË.ÂÎÈ8þ–ÿ³BŸ’à0?‹×&Ùá}ÍM´Í ‘G~Üìr•}¡s¡Ý¥ýó>)çñ;$e lF9f™c)]… ¨²¼¦3[²ʦÀQ?„iu-`…Rè9~EpX³l?-Ö{NŸf˜t=ۈأQ³b¯Œ*çQú•)ÔLHÎj:&’+ìhúvªV5ˆæ»°Ÿßd]šd¡á\^¿ÄAîtyqpzÈ¢­gn?u#"ëÿ[ Xˆ¡vŽwÚךfÌY¥’$ü4 {+×sPúÖÁSTæ<)ÞIÒw Ú.„ÙÚinè}Ã3Næ2G± ufž¢[¦û{K`ÜÆÂÉ)¦ã¹ÐÅ– ÖØb(o¶Ûj^oçÇtÑWO ¬h)*¿+<ß´•û&  ‰€Ê#Åœê¹{®fKî¢SîÂú¢inY‚'ÝZš áà$Öæhñ`LÃpehQ&UÊ<™`"~w¦{ojK.ËÖ²ÓÞÙëªY£?dÂ>œ‡‹q„äÎd•¼\µ€¤Óòc’è‹h<¿ÇbÔl"²7”^ÞÄêÅÒ.µfž{Z±WT‡Õ-.óëÎ1rY=º{¾Ü5¿þ0|}—lÂG´Àȵ•èí7k˜yžC«ypÈaRþŒy‰6¿[©Ån£× •ÙÛ²C´ö¦ã¹6ñJ­H<”±/1oi[déUшÿ+žRgÿ?½ÒŸ)ý) ÿŠI§±“Ü ¾o‚~u÷»Ì9Êpµ1Ô¶ ˆ.æ8©L×í}Š%ŸÄ°wìþ§Ê!XU:˜:ßÙ» ýÇm]Ù˜¼„ù2%‡úwÿË”häæÄðr†šk–´ÎÝ”k#£Öë©æXÅ\—1óÿöïÐó ý«Â‡¢yQ{W°ù_tᨾ[œÄØ Ü™f.º­ÕvĘñº®Ö”C=h—r-¦©†î£ªÀd„öÔÕn8Íäþûøî´áî7ÏOl˜\ïz5q•‘C}s©ëëFŒŸÑÑ âôôð¨è/²v5ÎS«6·YÆù­w@¼85§õÒÇ ÉÅPø¤ïX8§ñ Jù‘ #Z5Ôîç=R —N+^p¿Õ‡$ØUh‹`z"Zg§æ¾Â*-QB¾f`ýH~f½l?Ö¶_š ƒe˜[—˜»ÿ£8¦eiÓØß‘Ð'9š1f)]ìÓE+ö¯M~0O”Üàrùõ –-`¨0÷cf\üöGøÂŽèÅãæã"i¤)~¹­tü”ØÌÏgã-)ÿéÉöÃ¥œh}Nâ¨À~uƤå¾iŠ4Óœ$fi@}°ÞÒí¥ ˜ÔDããÚCT¼:e YíÞ=rzQ6œŒSR¦”EaÉÕ÷Êè™…‚> 9ÔÕí,ú‡:Z²˜ÓŠx)ݹ©ZcÜ`ÑÒÐeƒN§LM¯ÔYá©™ø<Á°ïˆÁßЉU”ó™ÕHSë*hwOµ?Y‘æAÍi“] ÃFiŸåvPd‹ ¶øXé÷i€ð8h oíP ÈyŽJË‘U¾0#»1Tj›ö]aiHt´Ñ[fϪòDüvžTk}d™ïg¥M^3¤'5ÛØ ]^²Oè#§âèÚ\-e,±h“°Zƒ|VKª¼äðæÞÁ¶F—Ä:æ†. vÖI Óñ|§ÆŒ†ÕdPky…70uƒXZá8*+«Ñ­…+Â_¿´?Ù^¡óop¤´ßÃíQ«›³g6õP¤ˆÝ}¤£Cž¨ä'È<Ê­ôCyl…ÿ¨ø@˜•Õäs¦TÁ]ŠcæW):bAKÔÿÒHÂÞq!¦t‚¬Ë؇÷D&b•k žýÕüûýM>ÉN8ž7¨¹ª°™Òƒx|ßµ6O•ßåq0ðŠõ·Œ2Ãsç^ó653w(ÅGв SdG1þ ‡†9a‚ÉÚF•_A5û]ü.î±Õx¶–${8¬\á¹ÅKÙ±î>(1¹S$Rxõ‚ÔØx¿úÝщdÆ6Ï ŠvΚIV²"]%¤ÁæÜêF>Kž å ¬__>±2\‹O~óƒŸw7C%d ¤¬Gù·È¦}„×e¨Ý )k[>ʼ͉9?8mœÜ;¥‰‰¹€xj„í±)þÑ5y"¾àgx$'£¶:„ì§w³•öÐpés4¨£o£'¥'@k¬‰è]Z@ÍnPz°"?{02®¹1 tÐ*¡Ó<QÀÉ6…$„ ÑÀæÍð=èÊKC§ßM¹°"ïî¢pµ æ¡°M]©äÂ"'Y·"K ÀÂßX9ª¹¼Âìòß¹LÙæ:¦½À§¬$Ïd3nª+«EÎU\  ¸;{äÎ_õɯ§rù¼–O¨/6\ryha¦‰ñDoÇ|ª¤·r¿üY †˜ôür\ù¼Us‚DkE‹wž;ÛtøT:´ÍN=‡¯&œ–‹‡þ Ã@ؼù/Ó$ òZ|úB–~u$—‘âÛΦÿZQôloÚH/ék_%©ƒÏeDG§u¶ðï³c‚‡˜v$Ó8 Âú$P3öD»Ð&óÐúsD…î±÷G÷´.E=6…lzé JÍ!âp‰yÚ‹IØ]#Å·‚’³çøùb¼ß+¹Dþjx·ú5ƒ£›X¥khÓ^C>ܳgT1t7“‹±¤o§­,¥§ÝæªÃXýb—›:ˆ¯‡þtðú¡GÖŒ±*#Ð…’ ÂÄAÏÏ>¯,¼DÏø‚O3ÉlJç£áðø£;æTµwš¶ûçM×iwõT5”ª`¥G!Œq>ì`RÜ(øYÀäÑþœ´nÉô$¥Î +8šá,¨Òþ‚VóSáõ'à™"d@KÙÂj)Ë:ÓÁä”@â8lêØÔ_Ö+4u€BàÁ  …·Â\XªüQ<ëjgôgôM]Ôú¨È¸P¡®¿{ l{Ô®˜dòʼn‘¨m}gbÆnaÀ y"ç`ŽËP^ÅoÞ ÏK:RDpR×…†ïÄol6¼{%J)áRÃ_¼Éô/‘D=û×íÎ6§–YÉðß`´Ú 6š ¶Ò~f°Ù×(¿‹Ä*уª ¡à¨d:ñ?øwÃÌ«„‘ÐCùõ—ºŸPô¬™/öôhŸ]a”œÔbߥ.ŸÑ-U#~FßÃ…1 Zå-ý,œ¢:Ž0èøó|úI ,åGwŸ5»Ã©}¶8þ B¢VŠK¦Ež.í¡HúMà*¥-yß÷xüä1sª(ΈL$4ƒÐ£² #ÄŸøþàãä¯= WCi@x\`Ä~t©iãûST ÃO1¥ •¯~P2X@ÿ5ZÏC–DSÒæ/cƹYžÌ:O¢‡q©8S›–×rè6Å• Ñ4@:9“‘j"Dî=yê¬fètÌëLO¨¡d 7j+BÂû¤‘…™g&OHJ8 &üt*?Ùö]®ººëÃæ]Xε8sˆý¸–y«¬5PŸ˜¸uµ……À4l¢7ugžvß÷ç³#ÏzL@f¡*Œk¡·|g™NL'ê(G»ÛSÑÉŸuFåjNª×2fiƒMœ;åCq¦J)´3'‚[?µ5Ð~úÿ·»‚WÉÛý¬ÔŸÚõ«\Ùß•(›…©ð¤•*ÍÔi«!-· ý“Ö~°ËM|OJØ|5wW[èSÇã„PÀ¶°£a ëwãFŸÛÈ´—–F”mØÅá4\›ÓÏjF¡±¿×ì̳™¿Á`šH¢|FÎøfW8ÍAÐÃ1µnµ· uKþÕÁóî†åÒúïy r0sÇç¿ _:‚ ½x'Xæ>sb¾ŽæxßzÝdÿª$nDË+z¢y’ê‘¢ÜÓÈÇcÂD`qHÌpïå5éd®é–àCуÇ3u£ÿ´ã‡Nc Òµî ü1ÈÌIýçÓ)ý~öf¯B³3e£ÏEôÃùn§QUßMO*×G´ YF|»&Œ»;½LT­«Ÿ€I (Ü %fø>lá¡+ÌÓs¿æn÷‰µ’ð\â#$v„ uÛù·»ä–‘O@³Ô;ÌTo¹-èCO÷>ì¶KÔ[¾Ò' ]±*w6ȈsS‚pµÇÄ:oõNlÇÆ åÎB*øm27ó<ÚÖgMQ;¿Z#ÙÿrŸ~lò/qH ~d®|›%Iˬ¥ìÙV<­›é×[éDY×–½D0±HGv1þ×0{™o+5M’&ÊLìÿ´˜"ãÆcd!ò̰Ìg‘Cš˜ò冫‚–ã¤HbN«2çͼ”M“à?n®è ˆ@Œ Ü „ÜÐ"ÌùáÔaô¢ˆ™äyí=ÂnŒŒ9µÛ!/K®X$ ù«ÖÒ†Ÿ£uƒ;ãîq!ÍijSðSÿÌ8?ˆGZG@R3†,VlŠB£› …£‹–ülÍÇ,3ÚÛè9žá:*Á´SœšŸbƒà±¶p)òRš ¬.®'vycH1¢£Ü¡„nާw¯×ÿ¿~Øßê‡_“Æ1³Á$%ÙEö«Ïã=÷ßàœ˜œLë92‹Ÿš4l ‡ìÁ“@w÷0ÅÂÇþÉwY@`I篃¶ Ü±g2Ìá(ž*S&/ÞÛ‚w¨Hët~tóôËžàŽÝ‚„õQ~º¢Î&1Wë@Ϧ>M˜ñp¶šÃ°ê§§«óµò`– 0ö¶_¥D[œ%IUÎ¥ô“ '±=Aµãk0~êÓÐ ~Ƥ4’ÿÔ1Mƒ"ëªÏˆ<Òïæ‹¯¬0ËPø¥¥ñZ}göÈU„Xd ~N]E¸xjkÂ?åÅDSäFí²öX§¶¹ö ©X™ È]ZDæU›i @×^vÏ´:Oùùì)L÷“ÔäQÏ ‘¤P¸Ä»bQü þ”ô°Ë¬fÕíïVcÈFªñ)ð}ãd\´$¥©Sh¾QBhõ ê‹×öûñ~{¡¼a%¥ˆ«ZZS¾Q8rÚ5A߇yÌxúûw™Z2F€èYÜÔ™ÉÎFˆö”Uƒ0ÍEÔÆ‰)>^Œ­cäź?F zQÔºÿA×tq7d¶¯~VK±O~9ƒ®æ„R[¹ߊJ·M ±È¤À–ÐïßÓ­•6i;pà+w“qŒ·­æSS«Ä‰ ¿Ô}öBê]w÷?,él–ÄW†5˜ÎB$ŸqÄ®nÅOÎÒMÆ¿…äƒg-ŒÝ“Wï Õ€Ýrâ8$Û4#^":+-Çø“/Ôuf?`Æ*•¡Å‘ß6šÏå<Œî}KqÒëhhð?«ÖiÕdøÞ+Í,¡Ç¬ZS~¥2å´jBeÇÑÛæÍûcï1‹Hdõhœ3B CœУ9·Þb’oDµtU[u ^ßp*^ùºÕ¢…¯éн²òW󄌴¡(ïT¦!Ò¶ÉL´Ñ gM¢3>tò¬¶Yà!¸Š¶@¥T'„£‘×Â.§´ï}gÂΔ5òf€ƒ¯ê®N*»Ð Ý‹pîÖ.¦Ìîac7ÿ"úa¨z÷GÙ§×»Wr•׫8)󗳩gzMOȺ¹q.Ckõ;üât-¬ïž¿,û}ï¯Ñ[ç/„’k×|ƒ$‹µðâ ØðÉòÚL ×í ë ÒÏà`¥]Tà\ÍJ,_m1‡ N£&hVXS&X×ù঒z™·`™‡à ȃ‹Wsªj(Ò«ZA`ì5Ñ`j­ÉÏU$3#ß0€÷ÿŽÿϺXè_íš-âåžm˜ü«Z‘vDR^—¾¢ù»H_—Þì´À‡ ÆOUkW¾c"_õß-Ÿœ,œæÂ¹ÚÏËZX±Ž›0½¬¥¢ ®±˜Z:‘ûìŒÉï÷5»…µµ·Gܰæ0€Ü!oªîN¯þ]ªPG5œhÁW»¤Âú@gWªŸ{ÜhÉ‚Î(\¸’§ǣjõ:“ÝþDŸA€ñõsæzÒ‡ ’4AŸ½Æ‡+C>µâê<Ư,‰ŸGûõÜGn@»mzÆOa–ú|ÿIF@ 2Ê;¨c Nm£Ûp³¬VˆÉ54)}õ/ÔN­wu‹v•ü"âgVLj†’! ¸®úrXôžý ¢Ä&²%Ÿv\Æ>)5‚Q9 ìds êØ†Ú?ÕN'`¶¾{@Èɼ‚7–GðôõÐÙê¶nawºVˆ´öãƒæ­Æq«ÄÕsa¹W‘ªUZ«eŸô±˜|’ɾû2Zû0úý@dœ¸W«zô„õ¼%¿Àîò~7PF½LGø&ïop•¥ªNŽF ÀÂ6-ß“><êd¦6ÄìcàöB1)f„o^m¸¸“Ð úOr»u›à¦à»Õl²Îœ#=“™¨ÊBÝ\ÊÉÆtñ àŒôV3‹uŒJÞw6¡wzÕAú¼É¬~ÒJ‰rÍÙu.Ä+ò Þ³¹_îùFå°å øpÄl6¦‰6¾¹º4ŽNkš3Ä‹<' ¬òÅó%Ì@¡`‰¨Z‘ØÝ$æ'†ÅÕ[*PhÀ±×¹’e÷—a·o™[O$e3Qr^.ëÆògŸ‘”/iø'º è˰¦Yк€ìñãçCAjÝuå»ìuÈ„ת^˜¡€¹D9Öô0Ú±ã{Rü½i»SŸ0‘œÿf¿÷ú£ëçÙ"!™÷­mbé§Y˜™Ómr|ÈiØÂ–B_æÓûxS™{ Øì¥s^ªuj9ת;]À¸mïA†ÿXpeüW>žÑÎ+Awçá´5¯ø»£ÌÊfwi7èÐð_¯+…¢yÚ¦sUq‰®aj <¹ŒI¿›´÷2T©Å¨òœM0½ ÄaÙ‚ŸzT¾?4Dæõíæ.X¿—Y@˜õÞh¡è¶Íi7д¥ÿy¬x‡ …{PH“÷epjPõ-¬µ÷ŸÌÁÇ¿³ÍÇ0gijSy ´&Í!Ðòh¯s,KïRAGŸYr‚³”d_vG˜eØŽw&–Ô2jóÎÞŠrf~ðÝ™¼½!Êlwh9Önÿ 9æhLnÔÚÖ|K´hî‘jÍ ¡ AQ ¬ýí{¯;u%b€ûcieVºQ ÚN6AÃ1æ ¾°52ö©$"ùn9HvÊœnäû¾§YŸ3p/vˆŒ¦T if´Ü»&yþ²ã‰p"uh¹ÔwÂ&³ØÚ'Œ*MÐø.Ô´‚ÿ†«åd3O^Uë$ë7lãÿžhÝÄüÅU¿ß‘[én?>@@jÈaÓo=Ðxå$ÑjŒ‡fYÂ|aúdjVéOåóR~®“9¢36F{È.xZà@àâzø|AUÜ(ä€ÙO.g`+üM´R%yýBå³p?/ ÎÁéEÒN† ­ËAAäÒÝŽß:Ÿ«ä'àš•s7ãÕd¹ÓÝÆ@ÕÀ/U(*Øñ-” *®ÐDCby”8Æ7&È—Dp6˜2²Ž«]}ÖÊ‚)á]¼©´ìobS“lÈO2ÆÑ^Ç’-¼$Nd¾0¼)¤.Ê=1g‘mÃ)¿žpüê};š´ÉR…a««%¦üïÁ›(/ Ù¡¡|e‚Q §¦ì¼+*ILT²d‚þ¥ÙC]œÞ¸Ås Ž&úé@"ò.ªT=o ×ElˆFÝýEýžýÖ—µdp;“ z*m¦h%D'&hºoÆ[õ‘už0Q;û6'RF(Û.q)ËÚf“%Õü:7ÊÇ4Hm»£ë݉Ë[d ^Þ´3WkXi¨QIzÊüÍì8R¯oÊ9öùÿ ü’]}w¥µ§6ŠÐ.Æ:ØA9\T›åþ?![¥òcðè(asf…¦…bÑ,:ø4QQKÙ½Ej•-/éÞhLá<é‹mI=:—Ú4¡¢L.0ØÇ8ØfÇÇiÒ¨, d·×Ržž0-"ò.¸aSU¸äsˆë^dTõ¨Ó¹¼ÙœøŠ$¾Eó9€œ¨R/Q˜‡÷{ö`€¼’W‚ì©4#mÀDøfòM¼€MkàÇD úÓ5¢FóR #ý7bØäÃh¢Sd°ŽèÔûÿE ™z¬É1b.êÏOGÓ2øþ9éGo81¤½u`Wþ—ÀU¥ÛQ0]Ê"µûü«’Úº@_³¥všD¹^4Ñ\‰ØszAôaÿ2ݾÑÃÍeµë©¯—1Ííç4F4z·³ðîK{%8®øæûï>Ǹ²\b¤ˆÏÐ-ºÂtÚÏy‰ÐäLV­Óˆ ·LÅTgé8šéo*¬¹5ñÁ’]º*8,v osébÐÛdζ?÷ôÇXÒŸ·ëæömNOó ñs“1I6ôýÇãnN“+³jÌÓE„¸«õ©Ð­{JªÎî^x(zhé…!‰:C"²•‘lq¨XúÑ—…òþf¦&¨æ ™_UÝ™#½žå\Æ´Pô1BnÆC-øÒrëflz6å _GŒž#OR.?g{b±M6Lä7¯§Ýƒ"‚ǽÈVšø^ì†OC†Šëªjïàj(käÒ~:.¾‘⡽”=µ|9Àƒr}N¯5yðÌ@ÅNÒ¸Zy5%ö5Žª1Ùé®N T¼l¦$ìwúå)µ%iÒê‹/¨²ýŽ#Ãÿ 0‰Ö·¡ÕYæÎS²-(Iƒo­ZxìeE8ǨasçWAÇÉÂÐ¥÷Pkq<¹ ù3©¢ÎW÷§Jùk5£ögfhžPh2KjÈÀg «#u §1ƒÓ¹u|·E=Þ¸‡Ú  sç)ØàlQ Qf?ËtC±Èï{Ü^ÝB1MN?/0û`á—/ æöýå¹È¤(» [b´U(Š!U!mô9 ˜óúÔÄߌ“šGûxß *ÏÄiéS#­q!o°Ÿ‘n€aÊ*«=ê]—a“C…t7™¼óNp¢c­è¢cÆa”cn'üÅJ¹J£]p),ÍZPÛÅ-â*¬íÄ­Ë KB,‚Çò6ˆ”®NÈeÄ”¯Šß)¢ñîîz8¨Í+k‚C0!ŒHR8hT†3Î̶<àåÑ5è O‰{aÍO½ó²wFY‘7ï»ó~]±p`Þây„Sä{Á’°øDÜO”¡—6qÞˆ8òSû£›,_®ù ð㟠F'¯{Âð¾f]Ë›CÙÒáÚ®#B4ßU".éì¯;ŽÔv¹ÄZ#—ŒaØ—Ü"tÕÜ =ñ™Vÿ<à®Û˜ ÄÚ½¶ÿ³EÚ Ì™\ffÃ7n‰›wBÿ7Ш‹zÈ7Ì‹hÒÈ€qEÀ‹@bü>©¯Br‡ë„¿YŽ g’˜²¥¦¡wœ¨˜á>öUg.´O¸y`Âie…½1žŸ¼/@¬ © 1hrÝôf$t2 Sƒ´}×sð”JeU.U¯«¡Š,r¨¢½qé8±›xNÈhüdlƒ†Ã¢h2K¦W¯·¿=b¦ ¹fDrHÍý¨ÕEUç(;cç‰Çx/)Ý‹í})Ж÷GýsÅ*Iƒ@°%6>‰ «h+Mg¬É¼ ™–ìYÚkx`àvBX%¢r˜GßHÆKFë(œ©ÿ·)Õ-ojvÓ>ÀùƶœÑ¶´§î±dUËLI1cc¢—È€´, ß“|CÅ€FÐJ'EsþùÇ@àeTˆÞ»}ÛÛ@œ—`nÇâ'ôôaˆVmÌ=KÑ}úù™I®0WÝðKØÝŸû¾ZʸX2H!±¯8S̸@TèV+õ ¸&°pêÄ{@|Zlk¤_wê_ÞßM‚´H¾í¿xó*~)¡7YïÖH)êQÀ2§-2À–Y±!áÓ¿*üÔn7ï¾@¡ÞÙ±ë|ú»>ÍOµ}K¨wPÜÒ‰Œ šÿǽڕl¤µº©%ú\çíç'æû»ß¹âUÑÕ–¨«`îzr¨æ0Fé |hG> ²¤=HÖŠñ†|}Á‰Ôï †ÔH‡·T”DÌ6g‚º¯²ĉÍW‹»Í·¬­x‰°mt+rŒ¦<ù©˜ŽÚ«ˆ<´C~.P~;…;é²ü×BºÚåeáž»í6¦¸¯ÎÈž!&T3J9¥“\"©ïá2ß>3gÂ+c07ϨE|Á /|µü31á ZW†{ kN΂ó¨»Ä9ï"ÃÛÁ,Ö¶~ÐCN#(n yÊ8pÁâõ!;^8œ/ÏñþÔ3¿Žª«ÛÚ2™øÞÐQécaRÕÇ FKÏÙ?àÊÌ´ú&©ˆGéQq ´Ì±GWÓºoç3¨u…¸œ‹ªq¿ì²» àXi‰Ì+„C!­pÖ1Ä/Ã7Çëââ¥Øj_±1€'ïåÌXžpYýˆ²!î‘Ö9É42ÆI5!¶.íðfSÁ¼¯ “ûîoX¼ú¨Õ)¦ëÀ0\  L³æ£ï9Ÿ²œ|„ö²†íªc©EÙ~ò‡œZ%¸•üÎA{–œ¥·ô^Iëm?Bh9ÒÜ´«zñ­¨Ü›HIh3÷P³¦ßMÃS½ êiJ þ=̹ƒci:qr;¢J”U+ì8ÐNgDG×3äo2QKQñ­œø±,ý‚zѵâïkgJÎ44¿„ÉêûPæìà ÁOhäpt _\Üþ¡kˆgÛ`ý—ê«ÝuWçf3|ÑÆ.¥Z …§£ÍŽOÖÊ!Š4Â]cî;ù­\âßòT ÝÊú¢Ä5p*¬’Üs/©!/hSø›+óõ òÀx«ÔáÇyoËežå^ÆðÏ 7£Ô¤„}½-øOü$cõ–nƒàüF« €™ûÚA£ÇÏì&µs¿÷,[tÁ=,öʲçÿr3$ Í>µ¬íVû Ì"†V0mÂðM8b\7É]?€kÂm¾Xz”2†\ Ç1{KÂm> c¾F¢chß½Ãû>}•o+W0~Ð --¿W†äÈÕ)±MäÚ|h…AÝŠçfˆwnˆI’I²—þæ[õ'¼í© |Þ’í…éýçGhå7¤Æ¡óUáeÀãÆϲ’ôÞÆ{îtW´¥õßhöŒÜúu~ÿYÆ—ã07Èÿ þ/~8õWË(˜ ÅìšYÒÝbXMÓ~V—Ÿs½eJ7K™5íÓŽÖhÎêâjÝrÌÒª;Ðé3›¦¢¦Ygæµv›a@-jâ¨K3…uRæ`ÛµH.ÝÁ.Ï7OݾïèF×}+mdòRiIåìUÓT‘—&ý² ô?ýä¹Ëý*]—J­<wÃ%uÎ3Í@sÙ}’F4Êïån¨0¼ÅSoϧòkwP ® d4ogá¼Uô³ên" Ŭõ®¾Àó¼}¾©!q´P:İsXdåGi̇o’O_24ñM‰b4 å@¶™C;–Q·þ½&ÊE°–ûL½¤pÞ\ôUéµü&go£ã¦¯åBíÔžÓÖ+}¸KN`(KEú2‡h†ÝeEÞ†GŸ"dê¼úx(§Õ8]—ÈË×g¯•ßô> ;§çqö‚p¿Î]š¿…§wš»UALÛC › ’?o–AûÄÀwUp—bdjFá\¢Ÿö,`‚þóÔ‡ÝÚb#TØœ4à¬ÄúG8Ο¢ô!XYúúËk¶‰‡”é;E‚jÓàõ&>QÇÏ”sD/'cïpôVtêœ4kZäŠ'”úP»@••‘2Z{fHîå7—ŒÂ3Ç©¡vu\-d@§‚ãýßAË“E-á÷T1ù÷Œlsëˆ5{p,YŸ¶sÝð)Òùø%9oùWP_Gù}öÛ`û½£‡8:œ6‹@Ûvçí§‰æ×µ7u#0‡üž’¬^©=…^–2³¯PDEi73ðê…cÎ@‘P¿¤D¯ˆká%£×®6`ËàcrØñuéâéiêòOϦr “¹5 C€Û_RC¥Ž ‹E+;[¦.³-'oÉ}FsÓ &jÄżžã¸ó…A\nÙYrW{l8™k â€ã2 ±jœi‘í¢ü‘抜oödÝ2ª—¨ÐŽrQøÇ¶ŒD†þ#¼Íz(Ùfsr÷É2Š}ø„uÎÑWÂ\¿w¨1D¡¬–É©2QŸÉ•ëœù& á=7=ßéP§"¸ññ]BoO»H'•æqŸ½Mƒ‹>ïCÞîãâv‹*üÞ@("c¥ÖÓÂ\äÂ+ê­÷e(~g³ˆÞñA##‰…¡Î6 tu†6,ž{êŒ ‰=E…w¾«©ëľ ù›$¡P™8.Ié/zØóöÛW2Ø\„ßyíiía3…/Ò^ ¿jì<½•P·ºàšc†øm§âò¨+ùpõÎÿÒêƒ=ê]:‡4È ¡þ&áõð9lÚÕ _ücéøðh¦ÜçûûTòÿh´no2ÐsÒ¥ÄC%éé"·å’Šþ›&c®¶®\ú‚/,ñ¾~‰9Os‚•&]û¬ÙÁË„¦>¥K*1ï㸒ZìŠa®Ôñˆ»+¨Àì¢àŒñmY-3’[®ï] 5wiCŸ-zYn÷„BÑ ;bÛñêpÀ§mG6Š¢ñRŠó¼¿( šXˆ!Ë„Šï2ÈM%†ö5?b?l1ÉÀJh’«61\·Ãj.Pxž;ÚiΆrÞ– 74 j· ¡ê‘®êEY/¿fu³Ü:ítCÆÌ ì=Û¥þ‚wU¥šÑï#c{òÚNº1ÜÊôdŠûȶûtN9Ýfš²ƒœe9D¿¹šX)ø›=´;Þ醕ýë7]KP[Á3±¹"nW[ÉzD3E“ªõÙÞe@X«IÍ•'Ûu1*§Ìw1x&Ư0€9,*è,òDÃKÓ- Òç½ÿ} ™Q–Ú*§ó`ñ)Æ*ìtHçEç¯+™œû¢ý¾¸,Ó+mÑ.ÿY4,=4hÕhÝOkCãc>C‘$0àê(t7òWozc¦Ÿo$;»hwV²/–«ŒçÜ«»üF\YgJÿ@4ÑÎ@…@8Å^Ýx³¢o¤¸¨z•ÊÈ®ëeF¶ƒqËÚˆÜR¡X´0 Iý‘*ARëÓ‘ËwpW`¹j ÕšëÙ³ª(wIfYÇÏún›„ú‘æpr’àc1¿ØÄ1P¾Ju©4b?mËÆ2 ­šÍÍ’h;Tµ…ò¶*¡×†y‚ª¢ÉiDÎ8`[Í—eZQc}šòÙ®b ;¬ä¶àš(¦CìðæÏšhúƒ“dOø`OkæhùYaÛQ!»£z¶-s‰¿GV g™gïQ7¾ÈÂQrtŽÎî;>Žª÷{îMƒìÛ-â¯Ö¤zØ\¾ð±Ÿë³ãošiðÁÏŠĆ)~q€ºÎd‹–ÉÅ^‚圄Ë*»–ȯIT —´Ð[Bÿ4yØôi<½¢Ê›m^Mä…XšOß|/‰Õ»ÔÊ'z¶œÂ[’æ‹IjeH{sÿ÷eë!³¹Í²7HÏ¥Ø'Yºñø­S˜Ifï£õùKplùЦó2¸d*Ïõ)¡/Z´äŸG‘·ÅïZ`Å2‘üE&®S3ºÙD y„IÜ6â¯7Å8œ’nÑ3„‹ Ûêå>º?Ä•µà }5|·®®öžH9k+ô<ÏUmc¶½¼GRðk©¼ÓE0^¬B%ïk§C™l7Nœë%»-­[øð[ðà Å÷ÕUR¬Oþäc'¸HtZzÕ½l$\LßU,`œµ‚üüÐÊýŠˆ€­§E! *ÁÏ0^”8&“J)<;Ìá;YÁ´4Æ' ËLŽjß~?FŽRnúã¥çW…ÓI’ S·zpéRý•Læé DA(²—.â.÷ôõô|íO¡Øø¸Mfil'âšZIKÒ€rTôý •OØÜtñL4ÙEãuŒW˜¡ÃI‘‰8†#Ë–­Ê¸fŠ®t…g?MÝ …GaÊpÀ ú³¬›x¡%Æ]‹(9º½/@ŠýÀÁãN­Üi`ü’…UÊÅ!=>ý>A½®ýŒÔb-t4Ìlsr› ÿØã€ad…›ixøpêͱufY8B%£!„€Xÿœ›©÷ø1›­!^d³³5FѯsÚ•Š&õ©D…ÇuÜQ­Èêÿ¸ «\ôs ÷¦–ŠgC“1:º2ºcdiŽ PU!Ç¿kÜ!¦€B®=â"Kšs+5_i‹ žÁ «.6¢6”†¹ÂB˜zQ çò/­©< ‡Êº˜®úó/ÖáC/§Õ1?ù±·Ÿq òû• ùùÞÉX®V`ÊA¬þ‹Œôµ£æJ] §“Ó‡i¾ÿÛ>S¦”?ö©G’ €äƒ+óص;z|’ÊB1Ⱦ_L4„jûˆ¾CMåögŠZS3Êžý Aͨ®ÙŸƒš±.!áâ~2¯º-á^aŠÕ]RîWÛž$–•ú_9vï™î ¸`m(‚d˜’š€{}ö²ªA˜]z럵ċ\îÞl$q‘u3Mõ_D6‡•æÔo¢G…¯Ü¹[|K­„4ܧpf=Ù¿›üÞ²9W“¥Òoëôâä>¡}™¯RÓÒ/BB?fˆOü¾Y EþŠü)^×w§b]6ü žˆ°žþˆQIgæR Ò“Iª-•Ò1°KeÎMß/#OE·ÂÎ#½~ha1›Wyºx£)lK{g«)mÎöŸ¡'Á¢Mh)N8 š±Ù©¼ôýV¬Ñéðý–d ëj„f5½I±ä„óZì€gL£}btɶ—xñó:»>DØò1QL^ø‰„» í=NÀg4 µž,Ù¤É%PŽ?{øÓzŒ¾T x‹‰Óž×™ @@‘vÃŽµk+,Üõ®99W~ç^<­v—$YpwÔäó{(yÞÈÑi˜ÍÇSgJ «nç¡ÇyÚ<«Íú*îØ”ìc|wŽ´û¿XÊ0L~áyÑ“„Dq⫽œ§¿7‡Jèà– mάú˜t[{–Z>ÈyVŒ“7ˆz©Øãc®Òˆu™Ña£ÝTñ‚¬ª¼ ö5g›'ì´.¶ô•3Äm’«·¯ &G^l¬7¶“Âý†¯–XPÏSé,Jà»åïivÞQ¾”Îç`ö{}Ð æ Í ,"wg‡ÙÅC š>åÝ”Ø “AQ‡ ¨-ƒôM)5ªN¨H_ÔkâŒ( Zn#‹Â3 ¶õ°°ä;H™i³…Ý¢×8õfÁõAM=h Þ´)è.â},ìU§®®+6s ùmkhnŒe?e2‘ŒóFȌĕ&˜LžÉù|—†“V1/A‘p}Ïl¯©¦õQÞ Ëöl`ÂDuq㯌©€×öuUϯ&v©èÙ±@0(+ž'®¯ðPÈ$²›–ÝaÌréNma6$Ô4€z,#UÐÖöÏ*{Ïâ®Û´(‚z 4 Ფ/lL–PèÓì•ÐQüRçv3œ1‡_:°ëmæ ÐŒ¾eÊ¥¹NÍ›ˆ¢yÛ¨{¼„™íí]S*?·.G·ƒÊ§þzÞç'R‚Žüûa|ÔÝà¹ÛÇ—àæÔ:öÔh{bj>á¾ltShfÝãît¹ZÏêAgˆÖî­áýÿWO_ÎÄê7&¤tÚi§ 6õ˜¯ ×j”­˜ë}ëôdßÅoA$å˜Í‹™­bªpDTЉåÈKÁ® ˜h{9ÕG@´8¦£Ù¶Þ²‘ÔüîvùôîËlèYåùý®÷}îklŸ1Áø!_àd5þŠ€ùÂÌÊ\i€%UH ¯ŒTée+.HNW¹àþ3SoŠé<¾äGct ²í)õñhuwÚtr€Ë€ÚŸ¼Buʬ6Z|Ík­Ê© ¹ÊÞö•ª† ŠŠ(Ð ¸Ù/OÛ­ÌM·ÜiÛñ_û’Œ“5È0 ©¹á¦¶7{biØ“¿¢Ë@øåv£ Eâ³üèÏß·›âa‰U3Õ»Ã\`(Aó»¦Ìƒ.ºÞÁúãØ0ßÀWÎÿ1¾åG4¹@‚i›|ÚDf¡nEPqÇRWã{|yª,ðÖ•x¯Ø`D¢¼y¨””o-zzI”ÑWâFO¶~úàm^°íƒ‡7]÷P¾.š‰Õ‹G¿ç :Ìv0BçFl»¸Ç râùíÜ]‹™w‹¨Úy­ô±ŠÅäËg‚+)šGD†j7µ ²AŽ;-À‚¬-²…l1aTäD8¾ÊgJ =ÖÒ¬ó ™Ù‚QKU¡Þ7Ä뼡U^õ®G±&¨—öð’m{¯#Xz¡óaã?d #@hoH¨ÝÂÈê^:#e)àðŸoœ¼ùT~=µ’tÒÙŽGU·M¦ût½¿†$+ËS‘.Lq3²–0&Љ!Pw(¤iïel× YŽî|ì\¨ùLÔ¨O M C[“ 'éL×~HëU‹™ d3ÖJ뎃¾ÍñßâýYôbß'¾AÅ}âýàîlê~éy[ÇUaYJ»ÏNˆ¡é·’ˆI½î8m«¥ÂðlØì“¯‘.aÁå½9=L§o·)/'BeåàÛƒè1w$dÑ·œûJÏì þ QG‚ ¢VôyÞAÇëí˜ éTOomÑ-¼1€ù]ÀÌ'¢á ÁÐ ¯F›f’€þÅz©á˜ØÉ+±&<íÑdä¡i‰ñÉS‚³zI‰È‚ btû«U -Uã©s=#¹ÛZÄ_w` -ÄîãŒòq R2ÌZø‹-]yeÈDÔßuLòÒ–NviM3Îëi7‡î¦þÚ*ÖRZÔ£pˆ]=Þs—UØÀ :‘Ç[J©<²í:ö1©Š B«adÞÊNObR¦YЂUÀޝ°#Ö™PDý g%Ä–4ß±¤àøzŽ?=ÀѬ¶zs$'ÔAø„ sUX¦<ô9[Ì÷ …3]UÐaסФ5; ƒªÞ^w÷µBœ Ùs=l.jí3žÔ€¬Š¥ÌÁåŽÓÀÞ±JéÏ//My¶Ñú/Ü´]¨õŸ@zî@:WUWÈ”¶gµ ÒVä²Vð'd(MÛ”Æþ/t‰».%ˬ³xˆ_çx\.ùKÑ4&bÅÉ‘%}R⣌…1E]·0ÃI ·Y|¸ê.¨”Z>pC§ÐzŠPZ.Œ‡ §4‰‹i­§¬(>vL‘¬¢#²|âË_ÑÒ·ŽWrlÞ‹£u¦ÒÖUj/B²‡ã‘éªQ×a ù{Å“'yZÿ»{H½ó–›¿¯©¹‹4Gy¹2Nó°»û}1 û`æIÇݳOи‰1¼Œ¸¶ëk {Âû † õùrÀ)Ò{/L;òÓ¹Âu[Üoæ/{°„l?BŽýÏJ”ÿßÄòRÇ‘ ½KR–³ èm[Ó B²…ËöóÃªŠ¦g`¯g¾­ýhÞ’ºîHŸPMà(Çå“O6»ûY­ºšœÉ½‡¸êÇæùNÀ…Û´¢òXÅš¬HÇQ8nnýQõU[f}Aµ¦š  žwÎʯµ&è>š±,§ ´ãèó+ºKt€ÊøÖ×´‚áÖ(Wýš‘ŸQ+>òxé+WhÒ&GZÐpx©*óŸX²ÅÊšJ,/5Msõ·°(¦³kbà—¡zǘdÐr(ù]jÁ9¥Z)ÇÑ_ÑgÓrF`Û—Ö‚áv:ïïWÖW’医:Àî×)þŒ¬ò"}²4ñsO M Á¦©ª«U²w&Œ{> Á> ã€:“·kþÂðBüräYFµ[°}°Vk-Ný‰ƒüpVÆÀô÷›FêcUÊmÍwDp\KZÓœ$ï׺¶ñ‘j/ÑÈž-ߤ»,vزÊ*ï8/!ÐAÐ[´²ÜWW¥F·*m¶ƒ@ ¥rx•ÚO tÕ”ã8bQŸ¥ò++å\¦gô4ÎHY¨øMö˜°˜mÀjšÛVs«ÈÊó01P<É3þ&dlʲÏQ±ƒH ª…Gyðõ¶‡sXt =HÎDƒ—M·ú–!ë±ñp'#Ãy…Í<¢Å ©ãaEŶÆ.P·qn{²™Ÿà˜`X›oeíù !2FÖ#™4Ñ:ש…LÌmkØ`´„Ÿ©´„c©å“‡hhz®ó}¤þ˜´£štç4÷– ­³QõhsÙúg,$ ?ŠýÇ2½*jÀ¦Ü‹ÑëØ¦‰½_+ùÐêø'mÏç""ZÌ×/¨JL—E¶G…c4ù4Y'÷IJÁ¥›qä1¡y†iÁ}]<"¹BË^U}ÁÖZýоŠEÈÙÉy?Òe)_{#é ôÞ'qou;Sôod}àtÏwk|epÀå¦ý[€A%é’„Ö¬ ¶,­þÓ©B¥Û8žÊS¾©<üKNˆC9؃Q=¯ñÔ 8QY~rIãVßA|Z,Ð\Lù.HU˜qX·ã T å·»~å@Äýq¡1ú þ>Y‰±¨ ã+»0 ï.‰f*-ˆùï=ÉüN̉ÛS²b7çÙ&rö¯ n”üõu}´9rÍhÅ ‹Gú]2ö¬ˆX´Œ!¹+VÔå$¬7Z(ž‘~SNw“•ª) F¢ nÁZÆ-•’¸Z.ßPm­Sl„7²ø¾.Ùš¡jO¨“¾b¬$'Ž™3XD¢ŽVÿÝÊÉë²€md¤=`8¯þ2’€â,µK¦%¯‡ù÷Fß=Z–]êóV»È™|¸yLu„NÛa%p ÐOÍ^«n“ E?ÚŠñú!„þ 1“—Ÿ `ÊþµüzÂYmÔ£mk}ò9ú9Pÿ7‡ñp翎[7ø#ö“7Çw\¡?!…Åô¼”3 «.XšpŒÑ"ÖC¥„%+ǃˆŒîˆÙ&ùÕ´µ¨Ö'ckíÕdÛ–ð¿ìµ‰¡k|‡;ÝœPÈc:—Pª:u4X•XI¾{(/Ùx¹9tåñ3ŒIQ#U °Ö'‡”]+™Å÷½¿ÆŽ†ë³y‰MÖP˜"¹‹6\Ø£6•º_ˆp…Hž1ò^¡´ÿìfÔtRœ‘à¿8ÏæF!ä‚Þà / å¥Â~’xóÿ¾:¬ù€…STVâˆÑCáyÿ3í =¶»4òÄnŽgÐPVþ,¿çwõ³i3$Í$Nkj[º£º*7$ŠJµmLkþîò-$]~ϯ·ª´e‘Hw±9%õ²°¨HDGÝfÚ:Ê[³È 9 up˜á}(xƒL Š„Þ’X°Œ&%Y‘.÷¿A j„Q•`?@öÃvq[ÀS:§ªvšA´ßAœ­eXãQ—Ýájƒ0áþzg9O~ èc^ô Ò#Ø5„†–ùAê᜷G}ˆ ÌmÀhrøÕ´ùz°š¡¡<úþMQ°‚›ùBvÍ ÓvaæKÕÏÿG5Y7‹áyy?Ú/è›ÿq'Ž=‡Ümç¼M·êšµ†BŸB'hÝXºÈJÛ0;g7½÷‘{£ÏólƒÇÇfîâû=Ç™Îô÷‹­á£MJ$EÖ9uQPoó¦v_ h1d¡h ·ã¤†¿I;Ì æí+”Ý~ Áœ6 x¨£|/ΔÄ€VŒœ ƒÈpr¼ÍëTâ=byEÿü+.•t?+U^9sÉʧE͘þs?Ð\-$B‚Ô5Õòtäb˜óØ¡/E+_Õí¨–ÅV‹l×—«tJ‡N‹3í‹3ùry­«>±•™ò¦FçÇ‚ŠÐ*·óYÒù\aòïÇ-ñèx#Èu§<¤—Z+/Ü!%‰Ñ\Õ©¡„Y5| M©èÜ5€š:Ñ®g¢^Xg T®œˆ~B4'Ù/ÏÄÚiö2*ødZlåÊw)iYõÀ¾P ¿ö`Æ€nP&g‡íRaC¶ÓÉãJ„ FGÈ/$<¼X…»0-”1%¡îúÍÐ{¸ï„M„¬|1©QŸ Ót¢Oß›û1ŒêÅ;Œþ4 -KKQ!­¦=šÊ¢8‘ûh“ ’±€<‹C³ö^Ytx³:)[Ø=ä«—‘_ ÑÆm¤7 •ç/bŽ!ÅSFJTq·Ðøaó5dÛ g`â&²JHò÷ VßàÜ£ÒÎá*oœ'v›Üù¬6‡L6’ÀBZ2å¶8‹pKRµû×D´é[ýí×XÈÜà[Ѭ^…÷Ab×ín„y¯À0øo૦oÀŸvÛÛ¸€²qD‡¯‹×8¸‘Ì¥äMXô„ß¡©=aÁI¬¡ÙÓŽ9ºY¬=2ˆ‚Y–Ás (K¯¢ÍDÆëÓWŠ/«D î©Nø^ôµ`•0ä"M(„û1©âooo²Ï 𠦵5žÀ9ò$‹ó,pü)1ýЖV£tí…¥å¯÷6ËÜ>÷—PT>í<ð‚šç¼ÓöÕ9¤¨¨|4g2<7}ŒÍõÐ}ü°ëgì xIõ<¼dã }."Ñ`|¸¿ˆ\üN^dˆü¤–©\¥å}¶ß\B“²-wû_r‡ÑB½Bèa}° âß«JýÀ´…<1.kútTµRæµ#!èx‹Å?øŠQí¬—$ãÂb퓾“ìó‚5¤:þ¦[ô4*—¶íVõ‰–HúSµèÞŽº`Ù€Ô= è¶ìYa_Ÿ¤¡)g„ïÖBEæp5-×o Éû#&,”æ »Õ20”Ò#kÇ=QŽ'ôà±›ù—P42¨›žð—ö›¦dÚ¦±ÌÔ˜-–a`¿VÌîBÆnƒöm ²\¼h–';í0ï¢ü°ïNEhf ½;ÂJÅNÀe^- HNu òYÓÇÎàñ(€‚nò”Rå,—1±¼ðÜsZ7 Tµz*áï%øq‚DgpÓc‘½O/û³ú§ái‚°Âí“{Ä®°T 82ô¨Ð˜‡nÚþeŸG¢Üaל\ÙÁšf稧VÒÓGp'›M#•Gµuœ~EZv`3^ÿÓRg–"ŒAƒ¸áNàs™mÂʉÎÞd©mij{Y"·äÔõ¯VxZ}÷`:T8Éíê]¼;—•é¹Oý"|a#zÆñÀƒíÝnù‡‚àÌAå„›¨Ž©üXÚ+¢¯¹ún»Ö}†8HèÛCži»­ËbK{4“TÃïc/n´²cRqç¬Óš‘sF-Ô&FÎ~o|EÍ£s§šÍ'·ã}´¶ÑgcÔFà“TæΊùúcg‹Y¡\‰p©:edÉèQ}EÜP­Ö¤+Vh¦c¶ˆê%WÂùد§çÛùPêW•Z2ÑÖtö_‡«-ù†R~ðèìš)œ °¦¦­ŒA뫆Ã8hã¦Ô%Ûþ‘Ò-hŠGÎéÄ|þx8$^ë¿5‡Ίh'áu…`MPtœâã£îž {ñm’=B?¯^¬â'.ˆ‹2@Íy•:Ö,ù²µÇ@láØ™D‡+§’©# .–V˜ø»m)~SÈQ±3õ«½›Ê/<Å<âÝ¥OÒœàyñ$qÄQñŠ"tDίR£ÅâÜØüÒmu£ÐpbòeQ²,®Q6 ø4ñ§Õ¸žÂsÕËy¼Í ;ŸcÛå†÷Ê1ÛãNö)¥ê%¯Âˆ7ÀŒêDrëJÁ¨TqeV1æîßõUB.–‰xËLiy#-KA±9¯}VÁ¥Œò¡X*³¼4ÁñœØg¾~“ÔM›Zè3&‰„é 6fXŠíPõ¤ùælxü^iUZF²i«æÊ²ìTÉ0ÏB…{¥Ã‡äS£à¬ße2øœmAë{!â· äuµ3‘H†î7XM˜ÆÔ¬o ¬€Þwcd‚¾L¡†uƒó-À•Ì•Œ)‡ì _§:ˆãWÅÞ…*ÚDÀ“x·¸ê¨KÐĬ+É߯²\‰A[ð,HhEôϘ¼=éµM$4_uÑ!*~ß_0l"¶DUϤ¾ˆh2ÚV(2Hç­\ˆ½°)EBvõ»Ö/z‚7kù(çt{™›Ä¼šDhùN¶G$nAíöíº Á›?»´ØÉ£Õß?0o½{OuWÒË’u‹ÁJÂ!ŒìŒl³¿UØÏd=ðÊqQ _¹© f¶ú~yrÆF% Uæ:{ÕÊu.5P¡´DtêÇ£÷mÛäC2„܆€ñ1.µ·6JÎ Cs}>­\ï³QUœ[]%D'È+EcÅsº>ú‰ð­f+3hèÞ>W»„A:fh;x‘¦¶–èÁwNäxK•ÀÏe2X'ÎvÆceÏrõ?Á'£°ñý¢0·²>®Í]ÉúüyP]¯Ç·ž \ÄÆôKúâ2©U¢®uÍ›*ÉÖóí„Q}æU B•qUB“ih‹„I ü†‰èjîÿGõDƒãRµî¦Ù`¨mú¶µø>xâC' u3æ~Ü®þô%°¥ëÎ@ÿþk€ 7¦}6°‘,0ÿ!Ü PÛ ç(ÑNw'eÓ¯ì©{f”ؘàÔJl:´. â9ó~‹rNg;b 1B@³ÃRÌýV'4i…?Á‹û/U„޾>|r|b‡áä["*5 šÃWxÏ`>êWcTŒ,V0:ýüŸ»ÙÖ…Á)çpBâùÌ˶l‚:¦ ûs6R:I[„Ø:äG’÷{ö´–!zb¤?âAÓ¹Ó¢»ËÉœ2™!ëéÀ–î^Ýônöó>g¥X²¬E<@gØ/D2L_X`™Óôz—€¾ÕÁŒƒ* ïUXÍâl~'Ï)p$8·{8Z…/Ôé‰kïÎÔ'z sY<¦DÉìÁ2¾n  s`w[=öº;‚oE‘03<± rš©ÐŽ ÓyVš—ãÙo'ù]7ã]L Ïd›GQ¡nWüªÅõöòwùŽK¦¼Ü‰Ú ò\\¸zÛ>}ö ¬U[Ñv´³§}¢ ©Ì ƒT® ¥G*(àÁ H·)à‚¥¹£ß 9}Ï"1ußnô–9þ©¶€:²áö "ýË´øsÐjBcH:ue ŽÓÉXšÑØþÀð‘ODY_;êøð¦µ#Ç : ʆúüžQOÒÍšˆgÑ xã{h3œÁhŸÑ==FtiŽ Jý¶{Þ€I¼xÉ û$~ aÄý)OmãÞßRÚÚÛ÷‘xýY«’dW^‹,ã¾hÖI¼.øþ5ÊQ%è¡X wË„ñ·= ‚gWûJ:ܬRWìçT=Õ–D8( Ú¯A(O:_FSÝ=­UÔGb²N0YÂ{ùÄq=…ñyB !ßâ©Q×·šïº}ØŠ¾ø¯”][È7Íq ôF¤X7 ·áÂq£WøJvI+ŽÕ}vUóÏl±¬ápltpJaç£Î [D΍æX‡à}îfO2ÕS×Õ·9{bÏÿl0<µ»Û×`μ'&py|¢„m2“\†±&ãzòX!l;« ž0ÕœˆZñ²BÕâó™øÒAÓÍL|.Yí ✠³˜tŒ@”ËXçEŸ ñŸuz(ƒ>9@F®DÈl‚WtKföeá‘øv’ʼn'ó)·/†pzÆŠáiú~,¢Â¯¬(Cên§º7®‡ÛE𭡇 LêV‘¦ã0ó¥d^=UŒH~¢3?rZì lRva*å:X“Ol1¹ÎrfslÅIÒy°u*íucÞàvé.e§§Ò¦I2ªRù#å×FB¬—óqdºì©õ‘/ÅÉ'È•BåÀIOˆÿ¢o.sÑ€Úð‰ÈZ!@ÝÁ”²mæô°W±·P¶\A"7fD3ѵRª ¼"b`‰S’šõçÕVÈ~b§¦}ÏGðÕ‘dY›bŸƒ†¨ŒÚæB;bEÜ ›d‹þj%ýZ®ãøöÜmÒé&ÿ©´ð¶’œ˜Ýh±xáÏÙ‰÷/#,¬Ùp¶´[¢GÝq;CqùËz,¡–k6Þ Óêy‚¬ˆ°çôÃÒ>ÇZPu1€:RÉ$C”b¡!†.|+ð:{ÿ2,igö¸$¹&›ÛòWù´P‰¬vU›q’ˆjË5×µšÍÏ,0gõ§ƒí¤<BÅNq bÄoY<ž=D˜ã#aÚ_‡¬/ÙÄÎð1j—ò¯ª¿³¤D¨ÌÛVªÛsyêt9Ýèøzw8u3ñÒÎÃÌ\ëÑ›lÅ\m?¾0=zÙßK%Â?·›×>Ïo­tJ9–;#JǹówUÉ!Oá¾·‹Å$æCš–½ñÊŠ2¼ šé~ofF6häÌiÝýJÛ£"2_ ,t5p¯-1k¡SúÕãEpK!Û*7Ðü’¤ c¼Àíi4hEŽçN«¬i¡ó%°Ì$%¨›'æ2¦¨7ºí~r²•¾f“jH^Ù¶æé­î!~Ñ´‹ÒIaç)•ÅíØwÂ&¦ŒÊÚh7!¿§õ.°BçÊuÈ–^3ƒ“³jë1V›ØË¿M¥¥œ¦ÙTS3••ËïåU[â3ØßuÉOƒ&´>`¯ïŒ:³¦®J¨À} ðjã%u’ —–qÜÒFoú@ÇIüuñΤT‹ŒÒ|Л2x-w å–ü­ãWzp¦ê€TÄÕü‰h—3p–Ÿÿ :$æÐóùœôÿÀÚÊʆ±Uçó + ˆ´ ä?TA¬„&vÍ}œ/°fµŽÐi»†(õfÈ©•yÍ_í7lAasíªeÞ´Ò†±·‹Ä')FI•iƒÏX€YJ¸£s'†yÍè,üÏ–„µQɃò\ÕÔUv '…e& %XìTÓ×6Û^>ü>tðsÌ¡I…ŒØV›Õ0ï¯j)ÕÒ¿iæ½3|í~FÞÜû¹æ§:gú9ÎÀÉP푈„ ëDµÍ¢Ô NÎ ! @þ`ÏJóÑs'ùKIÕĘ>¼OˆQN9ºy/Š ÜrGeÌ5CYf}¯.Û€¶ÔóÚˆË#¹£"$ÈÛ2Ö!H0¶,¿äJ\–ƒP:\!Dç.þ‚÷pÜÚö;:Ó»™ DǽåÃ{Àï”(óËóOæÃ±z\;„Z´U”îy[òÀm®9Ò«·*Ñ¡3q£µþ5¬ˆ²TÙ[ØâˆœÚx9ïáx#舦žÑùÂ¥íKK¶ ïuÏ>˜$5: :á) ¦ÇÂXÇ{G®ù/Ëp ;­p)¶Ìauóõ5鼬-’²’_W7¾È±v«´Kí Dº0&$s~îÑ–üm¾úH“ÄKüÜŸK/¯¨RÖ¹c2t¨s¢Â•+»S‡úI‡¸ÖanOìþ…P±1ÍÔêo®‘ȤS®HñpB¡Ú–‚2IËmX{ì,u@Ç5 ©-îÞ^.ìz·6 ÷ºÝÓÉ—ëP%Ü+ñ2®n>œwWŠP¼öSu¬‰ð὚ q@Ne²B±L ù±Œ5+°Ëž¦q‚ô¢Ë”u1Ê߃ïÎ!5ñƼ R齨#}*C8PL2OçÌ3Ô¦m¥Â÷Àšªán5lH—¡’ÏAð“Cûèõ;p<³ê}<-(äµ6âÐÜ?øh„ Q%oµHØ¿iMÛÁ¨SÃQ¾¦VÛ:µËBc²q'F¥,(sü]rÑÅà*HkRÀü.b@V²£¨?4TŠ òY%È«ÖÙ¤+jµbXÖ£€áTWBúSÙ› > ü—'{tý„‘¥¤l(!¡\ÐHÔkþ¢5‡zÝ>\tcƒU¹ž8äúÁŽ íŠo+¥šßÁ—üžÄ ÅhSiˆI’ÚFNŠ 1‘÷ ÷±³U}¦ÇpØd ~®¶$9éý^S[>‡9ÿYå¬ÛIÝgº¡8Ì_»‡štµ¶0¤ÎcÓðXÇû‹Dsæ¹ÿ\nlÈñĽø½¿hÕ&…Ø—†[H¡Ø9Ó Ã×\7L䊣øZP¶â»A_Jôº%¬¯*œs w%&¡q¥~_—ç¡ëÿ=öœ’­ŠRè¬íí@Ü ò¬'÷Á@ß/ûÔ‹DÑ„1_„‹ðLŸŠ2¥üczP7>‡b ^£’ľ=lG^¨›v¯* Ö.àO--I/V¶Ê¨!ÊÓ»q4,•°æ)—ˆFtè½ÊÛ#åû8,ß Š?t£+GëCªoæñ8L|¯ÏѶ–”`6ìúùu“xÎÎÞ²òv1vì€?ȸØÈšÚämÁ¦ñY»Èò«æ£äJàëš6Ô5Õu9Áx½k" ñ4X{Ÿà&³  ݼ½gÑib˜±ðjØàÎTÖ÷†6p4)ú}z M4„…æ[…ŽÞ5ƒ\´ù”„Š”…ǹ‰Ö›S=¢2±auîøò3È‹ŠìÍ çB YãÏP>³}[í®ÐZ_ ›® æ£n÷}a“ò%Ÿi‘ÒZt¾]“tÚÂK3Õ•jq5.¾Y.}±è¿äYÒ¯Açr10aX6¯aÑý\x‹!ÔŽ!½_¼Ðp9]øwk0ÈVùöÌwø”.Pa0_&^û‚ŒŒôß¼W±ÃÌô`ûƲzÐù7^ÊÓ É„æ‘3Ðs¾=[ª!f‹!,—Ùçs½K5•ÞF)ÄFÃ?SŠ˜ÙÀEx)¤­vÔiëÜÇ´ó\ó…2X4v´,|çÿ Et;?k[Ñb>@¨õVR«è¡˜}ÊÖäf]ÕבÍ4´¨ Ó>´…æð-G@÷ à-.˜Ý­S–úL²]e,÷&5¡gxQ~ú¨ZáÊ´ÕÔIr´‚Ó×_Iµؚ3ªQ8ËÉ}V¢”#úœøEÕ–¼©Ä¥¯Ì!P\—]ÔQ; ʾY0ŒáŠ×m¢î±;Ìé½*9x š?Ì1þ?÷^b¸ ,\_,b@õ‡0ÛèáV« Þ*I?ŽÃ"|Ý ä(8n—ôbžæºÖFÖR<1Tº «N¾pd½Zp«ûè’[™ÇÞ­†¸û„>š‹›¿Rtf×4LÄÛÇw®qFRã^8‹Ä`O1ñVVŸq³ðÄDšÕ{äg£;S̲ós7ÆTý¼¤„¨j åâ~ÝóD_¦…wLq'&ætµ1Àï“}3¶Ãúw;yãG *zЊ¹LÑs{PZú\SÉ8òDƒË råÉe8|M|³’G”Îs T‹Ùý'be7ÁtãoÕ=À¨àf¡¶oh‹tzº7¦°™oéôd›Ù9NóÃuzræòBrž@Þ  çùQáÇñ—,ÚÉú-…ËDǹ{¯ôÉona«ñ@JÏ#®Ê?Q5{¡_Ø•Í$˜TlïuGãr“Ĩö!€¢ø’¼ãyÞQŒaƒe«WËÙ(Ô»}%a¯ ¦ófö‰*+ù [ßë2…ÎÅ] ³PÅ*H‡ ƒfuJ@\ÇÙî_´¸û!öGÁÌ…ð¹õ²m}¥Øp|u»:ú¿ËÛú¥á+ÚH-¨5¹?JŒî7k• d2~Bá'R‰îüT­Ï¾¾Ô}+޼=DlÊØÇo¥Èô3 º÷ÑÛ#oàªù_$%ßL`Õ°Íþ_°x”À®˜Æ•ŸvÀäeÄýªf5Ì…ƒâ’ÒÃ"¬ °ã7¦F*ñ.ÞÜ«7¿hˆÎê?w®ˆ‘zÊ/Nù@ #»›˜…ùÄΑÑQîî#HBþb H44Ò›4µ$9÷€ØãŸ7WËÉêÓÌ2}5£·\_ß··“  îÄb:&q êݤ"‚.w'€îÍuJ±m˜7¥ÁCÄ@åÝ3舳ÐãÏ¥,³µ ÑªJ‘zÐæqïmáÿ•?í4q>ðš±Å˜ÜãNÙŰc¼äúîæ.Çiä¶Y>ËMBMKvpRÐ’\»"ØÁ°ÜQ¦ecÖ(Z-¹‚¯ê(jlå.¤‚ #yQ5>à¦Ø(¤žŽB{.ülqq)³ÖÈä/á¥À°!5žÓßÊÖåŒKÌ7ßÕ𰾪ÌoÒÉÍøR®†±þ{žžHÀ0vÄ€3íV ž¾btðhY¿Ÿ¿JŒ/”«D²rc­7yüшt„3/YpJ§È?¨Œ Ð}Z Çc^âè·cG(„0–=†ñD¬¯öýUžЊ‡Ivƒ—äœÊ•W5P˜ätÎî_Šù>°Á'Hâ4¯DÅ-ãüå(ªhtÚaá^@娃¢l83aC»Lrª¨„>à[êuu †¯*w \6ÚÛ{¬a^r)…¹}¿ŽAÒ.#TÉ¿\:›mB­¡ý$Ê1i¥8Hxšƒ£:GœRà²`…üoÆX+ÿœž…OÃj'ÜSaQ’^Ì^(néû†vå’éH”m¶@z¶Æ·cNËó¦/ ú“VݹƒÇõFàÚ·…‚3ßdi¾w½ÀÄ@÷Ï+ž¸ÝXôW<(,µ+à°ê$½,ûËh,.6\»h(PD/žÓ¸ÌgÉk„2ùiט‹=d‘&9n}£žm}u"†8NÜÁËc‹Kä¼€5&¬ñ»:ü‘ÏË>¦ñQW貓Øçø“q£šŽÁ­‘¯|MÄbÌËæ\|´ÞM‡ MÒdó.H¬Ç°” ÑT„¦Ü!rv½¹‹.©#w¦¦©fÿóòÊ’ª‘ÜëBn^ßZ.Ù²¢Õ­'TI ÀÐx©±+OHDÐ?º0#ìÅáQ®x¬„MãƒI°ÊÞE†ÉУ õG†Õ€nVkrÉÎOE —PÒsíósÍå„ó°ê;_®à+Ýdã ,Ï%7õçev³`|Gy!\“‡Ýý;©£¸“ÑÂiU<¶gZ•ñ~› y• ð³r@4ž× È#jb"îuõ»úßO|ƒÄ—l›oJ} PÿmìHÞºÀ—`†“ýö2þyJ–NtËhñ€ùæ:ÚÑx[vw¸±Åµ¿¥n¹”ÔWaXÞÕ©öߣ ž¶ýü»©¹S äF¸—”nHEu~¯ùãÔ!²*.4B)–ïánüôtñ“ȑӚQÍ% ãln?öÄôjíƒÎØíì4¶@-Ûy8&aUPéÁ~HéS¹ETNÁ‡Ü6ÙÉî´â’käÀ4£‡.úÚ»ž}›”e p¿¢dáéã‚ýL×!Áw䮊Îûµ g/ <2xâ„m[×DË\%U°_ƒxÈWžBgRÉ.<ŒJ (êm£œHƒ8öPèÖ}„émAA%LÚ¸”Ð7Ä|ûŒ6÷·u~'7 ;MÕž\Àô'sŠ# å±Ýàµï 3¦§s±OûNñA¥¸¿‡à·íž#¸n‚Õ³ |á…$ŵ#±ìÚêVÇzÝw"3C¥©¾oCVÛ}]Ñìg0ì,]D¹uNrW\ž`Eù:A£oýí'|%ƒîaçzÐ*¨«LJ+/Êu·\¸™ïã;‚ÄÍ•^OÞ| ;9‰•˜.Õ¡¡3¦X>Є—³2ZÝÙÌ΀?Š, ÇÃaÐÅ×T˜ä^èØ°8Ðõƒöxön»Ì¨†è…çpÞÚ6#¬ZÜ7cãÆDֿݰG¿…ñ=wŸ­ ýk!F0xÜžïz9¼¹–ì_¥q¨“ËÀ¸°ð>€ÇÉ`#çÚo†^wÛ²„ûî^ :É:º‰†ÅËÊÏÓqšÔÂDý“óöö#}¥O‡kú7!Œ ½­‘Û¾€“Ì¥‹$ÕÐågÄòs­àÞ»dkaJ “ý’µSù&®òg×…%°.îSï7ï=d%‘<0¿ÉC÷ ÜÎߨPþl)»Rüb}HÍNZÎíêEt¹€Ü™÷îã@=!Oœp¹þ^¨ýÄâ³E«÷q4â ¥Ó,ám1 ©NuoÅ ¹q¡V ±--³Tà[ èK„O7ùHWÀoɪ·ùª=OXÓf ÍUÿìzÁY{ÉEAMÁÆ6þ„e‰rô; nÐÇxF{>Afl$Y±(ÿJ+²ÚMc»} g9%V^r·Ô$ä‹úÜþª@Üd¤ÅÈ£Û?„¿\ˆOä ªk3©¿‚ ‡úWvE®˜8cÎ<Æ)Ö‰&©  ŽEcŽ¡\`` zŸbªFгRÜ&0Œ‚fË—¥xX…dcaÙz"o&ðÈ)iS j‘|ϯšž+ø+”)ÇìZ ‚¥GV«ùdXÓ>…Ô*½&hi“È“û²‹k4¯"aK¡Hàš$!¨ñìXŠ?÷µ@ŽÇòß"SoÜœý²ÃzðÎ'ó&l[ˆ–]"Œ£„Úæßo‘ý5iE'ùW)„äpd.–J¨ÃÝÔ}wQ»À›zÏÜw‡,ÌS}~}@OÚÊÜä(—‹(5¡z¤£}©â·ðÂé 3ãÖ®%cÛªE½4t'+˜:S~z>E±…jpJ‹k'Žÿ²{^`Øq|9/øêª{Ø¿FC”oæ.kÛ@£GÔ1Q·ÔŒü»#jð¶.áÝI$ ðX´7ž”ù-*s\”Ç["Û¥Iít.FÞk€÷'— 3A‡uË8¸›ãûuZ… «Ú°âW&¨âÛ| ]ºõCî³»Áæ`¬]ÀdœËwÅt Q¥È9øûeÓ“ TæC(ë׃A-ýËÖLQ÷që˜DX ÚÖ,¯û )­o팜úõÚ¯êzöã±¢ >Á¦˜¸”¨£(“óBÀð}”ˆìâHž‰¿sL1ý^Ö¸ËÝâ[õòY)‹‡&õ§·ÛL36f3‚Xš©¸ãj_^ç)Óh èûßpC°5zVÙ:°~þ…>£HÃ^ÃPó<Æs¬S;¸×T6,óäó2Wjµ"<<'ßbóõFŸÄ®n‚“ 6¶†ßí¾‰¯Ÿ´~ˆgÝ’àÕˆôúÏ])ˆ:ù°¡TÇ»° ìÃ]æÆM€€Ìô<OA{´ÿnëäôVt¹ °õw× |ðª Ü29Ÿ0ÀB>Â¥'$ó´Õ›„h3ñ šyS•õµÓjÌËñj7¢ÂsО)®:Š-: âåŽ8€Áض„Žål² M\³¦·e,IV­×~®çì¸Ç\p±Ah•Т¡€/êV”L.¥/îÈ*w§ïã rvZŒ9r÷/?¯)Oç% çsÙ þ¹¼ÓÖ`Ž®ãoÞð bl 6clþ[ZÍc]r÷•q†£™ÛD\¶ø+á?¨éÑÕ8×È@V† B]§_3öpÀJk§Í¼Åt™Ó‡«·Ò^daC¶;üÑ­Œ|( ª&oºq»7W#dÂÝ'&¶þøâiÍ}[DÁ€ÀOJ[Œ+ÛI“OxÇ*$GÙ…IË.3Ó•²;Á·¹)b©&u°>#0’$Ó14„œ2n„ö=˜M¡1×ñ×rÕmu6R=G/ØÑÖï ;¾ ¼:žÔÎu<îÑ—‘&_£^Œ°Öj*øcLaÃÃl ¤XÖ¤9Ó^ÄÁ/}TœbÀµãáõi=l³mî|œ€BÕü‰æˆ¸âKÔÔÊYeF”ÅM®Š‹fÊ·¾œ«Ü N‘EépàÛ€#mî½yë-¥=ˆ,…ûL©Š¶dÛk0«.Ù?Huë¤5B-·>¡Z‡d,™ŠÅ^-!…œ–F ,ê xƒ+N‚ì´32ô{b5ÞãçJº>Ó×—5ï~¦îAoWðôPzÜÝI¢_qwÃÄ.ô[@ì±î ôFÅ5<[2ßó ˆ`K´Ë~£ëƒ¤=Ú´Ÿ:¶C×ã¨Ãkª¶mÖ(^˜[ ¬ký,yoFáïÙ™/·)")¨ØEd¦}ý"¥h¼Ã¾å¨sK~Á•üß:F”äâ)–lK)w†'q@úe4Å¿wËôi2‚3î>¢Ñr2SkñÒØ < o$W-· ËÅý£|±M4cù®]$Ѹ®„rXów,»a{ŠØŸK5› ë2°âÖdm|ЬCE¦^Qsíwìg½«µíF1;cèãÉ#eš‚YÙu«ëwSƒK#™—ÃJ¹X¾ ŒeVQeág{èåÄEýãïPS$ß)4ÙîC×¶*ÑBU¶Ì>²ðÖÉõà"’½ðà€vÝ:Àï.Wà€\@®¡\Ñ-üõI)ro,»0-ö¶ÑÓôÆPCÿQ‡çcá2ñ]°‘P€ªu‹D9ù2ÚN·CG/üµ? a@Ð1³#ú=˜<ºsÿ{Zs ?z€C4:­3ã8Ç=¦f›±ñ <Ñ)½Ã"_h*¶µ!­ïȱë4²¾¢Ñèîá-Á26(gÀ‡µÊ¯ìu° ÞVÐú8‚¬›žÊëõžõYÏóyfiåob‹ª<…ØÁ6C²â”5dQÒK“\ýêSýÖ@§kó’.›CTźä<Õã¦[¿ñÚŸD…躈›"iuÏIâïÿ®¤ñ»X´÷ÒPkƺb‡Ú¹<üB:Ç¿Eþ?SL†Ü6WY=âßÃa½²Ó“¡dBýÄ\nãÖJHkËs þ­&¤^!´Ç` oQ= ¨p„ܨ¸Óä0'õû´º%–ÓECeOa FK>å›°jͲ¢r Ëö—Uç\u© òŒ(gÎ|§w‹Ö™¬›bEðÍ’ȹÞ7rO-w;¿$e—Ô °åWûs¥Ï`ž¹>L¼¢ðà±:“–^ÎQÆ~y¾ÚG«½,ûW EƒçÚ9“,XyO]ƒºÄ:῜—5R–U-ÀFÏ+…åýÜR A¸u¹k±„jHlÄáñ0!­ãÞH*_ûêÁPÕËiÈ)Ã?7°þ8`ŽAìÖ¹¯9óÝþŽäÈ&Œ5Ö¦÷ƒÜž±ZQ0q" wSl?u¾nÊ¡öiÇ„:¦·áßSlTdûà#/¹N‚=™ò 0ᬷ» Ã9=±ˆe‡ýØÖjsD§¯e«Ç§ó³k•“ûõ?ƒf M鉩êN¢ðdÛ4d{·2c²½k*p¥& ãõ_ÂYkEžàªúsÇAÛ€±"}žröèò¹¶]Û:~àâà·A’U Üh&«ŠYA7´ "•µiêùoøsciÄ6-²ü˜S&ñ$̘øæ)aÛâfd‘Ì^M`3lŽXòæ¬åD›… eX˜ÊúÙ‡1üÏ·paYËm’¿L\æ§‹ª>}—4CbdÅEØœãS9{`d×£}OôvðäP8 æ½8æFLï ºAD¢2%®4"Ž,Ó'4€™<Ï*ö>F«z¼£¨éÊì§H(ûzy¨„§ô…¸&4û¨d¹,§1%a¡­“©Cp{ˆÎE¿Ìö$µ2+I.¹>0⟠t+œpCP¦ß~c¯I6DÑ¥K#tB3KÏÂHÿ!Vd+™&HÀÏŸçÈmœÍ´ ïf ðFHÝŠó¡¯õι‘ îBKÓ0ßÙ÷Ð9{¼ìȉe¼oŽÛæÔÃÛ Áγ›Qã+™{шõîþM¦")2¥æR££`ÞçÒ¯Ó ëÙO€ø[m×H‹žBìpQç,äM³Ò‚DHø¼Î,ØšÑÝ¢’Ãpf ÆéñúnrÉ-ý›uê2Ø y!)µÀ[d²JÙµa®—™>ƒ‚n@…­íͼH#)Q‚ôB†” ë.ë¾1WþEʽΪ6˜¾q!ðæù ÄPßÐéIöú|— #f¥*)Šow—=}™ŠØÉO§º3*6DT+m—Püõ¯’PoÓìgº»‰,\ÁÔB0á1+Ke­ G>ý½ã¡UležpZÆÖvV·7Ü\X¶éoÅ}áÿòJiŒ”ûu=2Œ?F?³Ê=HeHC”Œ?(šOATR‡,­úøœ¨Îê&oÁàD²yš¼Ób¤b²¶Ž_$i»s•%ÜY?’Z„6ág™Øu>Œª¥á¡Þªobigeèå§&ö‡{W³=*;ÿ„6ôS6ørÖ÷/!‹æk¥Ð!7]Až8vÏÐñ­ºÂDzáÕëø%y\|‘Ž>()£÷Œ #§GD^FÝߌ*"âwlÍv2ã¼òèÐú ~öo'M#|m¼½pÁÖîYŽ$r·±\S}n…zP®5Y·V£¾òù×Ù©rî8ß™ £Ý”!Ç(4_rƒòª›:™:KÑåmN>Ô\,éS@ã7g«\Fçj§'õã %+Ëü--T° #«K3wÇOp»8£˜‹ù¡ÖsË"ˆ}΢¶<°ä J¥?ÃŽ:x9Y7è¥Å°ôõ)GW’Yˆ?QáÙÀÜóß±rǼ– #{o!8Y¢‚[B/Ñ ×Ýfú™@$—Ø1#®q¿W~•p¯£üÒ"r»q7hoÅGòº—i•U.¥üÅcE†*ê!0Q½w|!MG6'JéŒPΠ,`Cj8èÄñ ÀÐÔ}ÒŒ…#Ê:—\™úC¢†è­Ú#À$ƒÐà{ü!¶·•RÎû áßÞ'? !3q«7è¶vJ· !"ø³Ç™–Â2~V1¡&¦=ÜXV™G†ÇB5¥©MÂ(Ò[zâè›ém: äš*Ò;§vàÁµõØ‘‘+8z¦çLÇk'¯ra‹i§•”ù¹šHUfBið3燡ù%c™ÛI Šû6ïgñ^ÇPÙ•”|;€À°Ý«^šˆ«´ìo'Û¬ÝM¢4†‡5ø ;ˆJ-zѪ»„š ‚”30ÂWüÏTâx–¥\Î$XóÉçQL[%­Bl™bi{;™§G÷^ï·ÇªlzëÇ&[&~°H)\×A•Šœ0Sd(”:t c‡£²r‘ªUóf„ðÉëîIƒ XôÃÎvÁqðžéo_EL{®åLsµTöŸ)YÇìÓÜG£;ý¥¤Â¡'úë³Hµå•sPKyÉSùÆpÆÝ‘ì Ú žÅrØ}¤lÒ÷郿x«ˉd?»ô¼zâ°BIê&¸¼æ}ô1µŒ£úÍm‡½zú{ö |‰ dªeoXl ŒW€½3Y~‘3z‹ž]¤Ž¨ÁÑÅ´è¿•jkˇÓ"Î%‘ð[^©†‘ª‰ÞèºÎÚ2ÉNJ2yø»¿È¥´‡¶õ÷9ãÀnœÁ¸Ãögñ¿Ê ÙG=3çaÆ;ȺÜ9YÛ kD!À(Ûí^¤y–Wz \ »Å…~|¤(S¤Ù —4éAa Çgw³ÛÎêjݰ±Ö«]CƒÒcpF8× aíÝß]УEíòõÖŽDJÌáÐŒb¬Zç§Þï€l5ç­b‰ðŒx”|A´+Ɔ§æ2†aÿ$.@íÕú§@Í ³mÚTt˜ö¶«ºþÃØ’…g"á0B:mÙ`,¹Ø—>¤pžÒñîaL]èší«é&BùSØel!ai¨ŽQ·aZ‚Ÿþ(@¬èå°†`o.›ñÇ—³_^v}AVŒS³3ÉI$Œ Ík¼µ"~·^#z±µ¯Äø$í§G¿Øˆ”·ŸV¥¼‡LÃSÕ‡§mÎ)·hƒôÏz¯êoHSó§ÃßÙxtaWóÕè òþ†Ò©wlé3U¨—„E•ÀHôx‘’ÜOŽÉgêm›ˆ@p³ä÷ŒP=ÒÐÙYº…,ŽÆD' SF¦°}ÉUlqºVÊh¢—¢Ÿ³xþp¥ÛÿÔ”²J¼m¥øŠd%w0œ[ô½É&Nø»/v™·Ä($ÑOØÐº6çˆÉ?õ)cB§ÅE‹—òëÝŽ¦Š*ØC;ÏYN” [‘†VTKÜÅUBúænË“k\Ñ>Zûn¡™™Ù£Ï¶±6ý7^~[éC]gœ¶t)»YÞus¤°HÇŸ/ÜI£©€_*AlmüºˆpŒÓп%/%"¡—êmä ±©”ÍÍÚÊ·šç±úV }rÿŸ9sK…uA—TÆE®¹Ä5Ïeš{¨F;¢ß”v‡YõÏ“Š—‹aT©¾¤hXˆ~‘÷­ÎÅ–¹¿»ØïIṴ̀ù¬]MÁ¨šº¼ññ ]m«û­dRZ5»V ~!9jRã@äL6s,Ï=8ÀJ/Ö(„ƆpÇþ@\çOw{H?äò‰Áe<ŠGÏ>kë÷!ñ]¾v©{g\häGì(_¿y¨3/„ª2Pý}Û *œ·§ý»ÑŸi´æI¶“F^Î ®Þ2K|FM0Áɱ׿d½ÎcZ™t.ÂEzJe&[Õl‡L†óå!ÀW©7GQ¦Çf|-Äm“ ()g@©%MyAGÝÅI{œ ×Õæ^Y² l='Ln ;÷E|ñ’ÓPÉû•Ø€s:I¥ å©=um|RÆ÷ÛŒIãDjI¨Š$o71XA&¸±´mëò0|[øu—äí‹mJ¦3õ4úZ[ˆü˜í³h“—•kaŸwv­N~ö¾ð%dËXÝò½ýèJç-ük6‡Íä|p•'È•áBqŸ«‡O»C‘öÈŽŸvŒPP=[* Àïéîÿ‘y¿E†lÓc±ÿG­«sÓ8f©'"[h—`öà+AÑîáœc =M&‡åëd¼ûÿä°¤1÷…›¤ž‹ÁÜ®ý`ú4M?QA"^kžkš/HÛÃWØ~âÓ²û$€dZ`Ò%!%h¦IF}´”&Ýü¦…£üjåž%©­k‹«üûoÃŽeBàØ®Sºw,6Wb«õ ÑD1TE'š0’ÇpÄɪ‡/ÅI»ùÊxs,†ik¯§ÝW*Lã ;ã–äáÂßÐÕ{^s³«2å“Õ~ž¸5+|’‰Åxiägc0’ùîšwê¾@¦8Pô5_färô¶XÇ7uÖ›è^¾ùꨲ6g[í"­ñÒªˆXZÃ Åø#,úqÝΙÁø¡eØ/<ŽÔ>\:‚ÄÅÆ 8Þ¶Q‚pÂÚVʬŽZAJáÚäÂLÆ€,Ý5aBÐÑ®)F(ƒe%£qîFAÑH­O±*OG Š•³Dã  …k‹ú¸r“¤.¶ñÒÓÐi»ü›8l©KŽÒL¹ÿòp—ô»”<¬ ÇÅÓixTéãÃÉêt×!'ðHÕª\ª,[îÉ~¬[¢{mÎ#b_þkxŒyyOfÇcZ ŠÏÌ—€¡Ret¨Þ[îæÛHͼ4U]ŸPõ6…•SiE›C‰šP•n/:‘"ZRg­^mÕ^‘vMˆÝӃݮ”à§‘Æ]0æÑ¼Š8kìÆQ¤ }¬Žã¿/¦9ÍÄeøÙ¦y(ö("5Î[4à>·Ý­kÉMñ??÷½¶Š[ÉèëðêØMëÀªBÏ‹gº'f‹ˆEFH=¸s‰º¸ï§öúé¾$Èçïfåv­é;Í“³Ÿ“ŒðñÝ1…6½xZÇ5÷hÏqÇßu{|ÎËÆõNÅ<~k‰˜Î+LUJ¿«5Þ­H­Š¬jâçÜ?¬H£köØÙÛƒ«¬ÄnH×îÖ@UU;M:ž…PAp€ßÎR°´Ý„/aT¥æ¿úlØíPvп|ËùD"· RŠ™=SÊQf×ô-Pây*eÍßI¦ß ë–=9ˆê‡¶a'|>ð/YCÑÕSÅ+p M¼QP”“.QâU₌9„íLkþAá„Öá[n×Yh°£x÷’œ²ò3ªZ¥5›A>Z€º4Qú4³JS³¸¢NŠ}øYÌ0!_? Êü¬ÌpqÙ¶õà×ã«Bœ˜}-±%[ª¦"–E âÊô¶I~®©/Ü´¾T•¾™imÇi/Bu´ÏÌìsÅXã%ÍNç˜tä÷é·Ûv¶M;†ñõîY©Béµjà›ÔúÙMKÖ ‰«š$p„Ù&ó0tåVF°J‘„C‹§p&#‰G‰t(™h}Óf‹ÙþÒб)WîÞÚÍXogŸdBbüU(šOLV¾Ï#[£V“œeH}ÍG"WŽKRvû’¡=WñÕÓóé§*úJdêvT€DA¥A Ee0Bb1&›Î̓H¾ç)L(iE™T€ ɱÄ2Gy|nº;ôúž'xŠ ¨k‡æ”+A†±B† YD vBŤÜaETؤi™=Ô2ém êÓˆop[%Ÿ~¯Ávq°õoÌ%båŸoßÙŸä%0`vø.g»‰£I¯r+RuüåmªÇÈÇN¶¡ÊW€Y»,ð´k$î݋ַ ˆreÓ·‹”A®àõ Øe;|ˆ °RæX“u{žågø„;pÀ]æÆ.×–>o6°öÊo)ñOÔÓNV~†ø—&‡~˸á¤tõZ¸$YDµ~8&!C4ƒ60¥â¹½ˆÊ{Dƒ‚çMÐ<ÉGGýAã²>3‡C? •O9 `tWÒv™´¾)»"Ä:‚.c˜døZØLë?°Ã_¢§nšˆ·>í­2ÊM¾)áŽPµqêÍiÐDŸõî1Ù€RÀ‘$êýp¿ì–H‘!Íh½š‹§›·ói¨ E‡y†‚L#­=D"jÉ9¦¦ìž»ï›’áòóDûÚyâ¦GfG{Á-ÚIT3»QxZ¯ar„÷Ô8P…*ÖÜ¢—*…B3ˆàí¡¿ôJzo$ Ê‘èläõv‡|šé}°jeø:c¾?c™œì‚ZŒœ[ù¶ÛH¬šïz?BAÇŸgÖ|œÖ§ ÀGS©¢õ³ÏQðzüŨ<Ë Ô"hˆõ¦DMÞ›oþÏóÒïR±(Š¿ºEãdöº7ú;ؽõÜ15z÷—²ìb&å ¼Ö0Üv|Œ rÆ5ŸV›åÿ‚ÊYÃ&©XjY"™Qõ&K¡u³Ù„· ÂL2ÑíÚuSúŒÓ­¬¤a”°ˆdš kEÿÍP›(X­öǧ*¿ÁS–Zˆ%q+ï_H7'O·ò;gÃÑx¼w°³oÈ_/(€ý&¹ÝŽQ[?v-1ØH& ¨ISƒ"ò†#1Y¹øUç`2¤·$‹õïÊâ úηÆ$‰<Üÿo'"ËCpÕÂ|}Îðç4êžÝ¸‘»Jý•N@“Æ¼Ó ?‚f˜·ËWÐë fñí©¹i¾”™4¹ÚJ-êÜFìd$0½ÏÓÇê‡h£Æ‚2u7A{É¢€þAºý8LX,.!ñ»³íÅû5J`7·'‡0­¦D:;ÒÿM6¸adòX¬x >Òó ó©ƒbUËÙЭÃ_J6뙪ïÁ%­œ'£DîN–ýÈ-VK¿ hñh˜ÚP_­›."ÐfuÔîëý5(@2U£¨ ;êÞ¢cãËJg݇Lñw5Ô Žš8¯<²ƒîϸ²­å¥ÓÒN«¸Øæâ¦¾hXüÐôƒÿ—†ÞÊ—ëI^˜:fÓ¹I‹ßPT—¥Q8ÉÔf+ÚÝA üͳ•&(ú£!hgfm+¹Ò‰8iÅGr1Èýœì3èM]ŒØ üA×(ÂEkËbw€É‡äeˆP]ø`yá¦ò&X[N)“dDª`ÖA9è‹P ¨¦«Á&$žÀ¦·Üޤ¨é*"%|â´œ$‰^¼ ä®ñ8û©X€€æúš"1Õ½ÒÍlh:z±);óûâŸi¶ÝjÄÁA¦>1¢ÎB»'ÊHŸõŽÁl½:Þ»ÓÌhÞÜ6Rý­ïwÌïdM Ë¡ìöáÑéA€Î9FxÁ.AÙ´7[sö], C×2Ÿ†D5C<äÀ_‚èÚ0†.Ü¡F7½w¿¶ žš²è‹¶›ü„V`Ò _­¶ðƒã êAÄÙ°°9é˜töî7ÅP{wí`WLmFg+9Ï€sýXçÂö.ÍqÄkãÙ*Iû>PÆÿ[­–4„—{ÔØ˜T^«ÁÖN”Û±r â"¤¢*ǪO—ï~î°«û³I‚ý—c”8ﲫTpm½“=½;Ø(D¤Ù»Cu%v‡¦?˜˜‡ kÚ‰}ã·P<ûÎ\6x}HYÍírßd¸Ú‡Ԧ E™… ›íúÞ9úè³hhàéªHAûX:Ànî9›õ¢µ{’.ð£ô°ö¹¤Ð#Ë_ZÝ—©iÌ.~:é7,|vYÙŠ€+üÜ$ÓcšþW* ¤ˆŒEo1ŽhÞ¶Oä:cíA½J …l_‹–œ?t ˜ø‹‹ZP‚Îð¿+“? .kä’?^¬âw˜ÝÐØ†è#N9[γ:Æùvõ,ÔÚ¯Ðr~œ¥âe!Ô!whTu)ÉÌõ„ÿ¤+*÷*MAgfJf¾«cºjÌ&º NÚÈ%n»Srô¸ÆÕ;Û†ý©p»¡~;x[0{é4AûKô Ý ü×’Ô‘n²ó£( žcM-ØPpzâ·3ŨEÆÿ¬ôF‡¸+FÕö¬OŒÐ7eºD‰Ê€·ÆÞ²u²Ü°‰ 7õ2ž  ¯)“{“mY ôÇ¥ÚxÀGu× ¬5¢%.Ú¾€ü´¦- >Z+µä-áB™N$áÂ"QËÍTËcÒÓ'ä‹ñæ s³¸ºgb*B/[,úrÓ¸›Õ¿A´2âÝZUQ*xbX~„m¸Ë¡«vV$µVZzs@È™—¿U"`Š1±)òKèßø£+tºño¦sé£v.pÏ&$s_T§ök±Šáùwûjè1p?mÈá%ºZ>àS„òX¸iäá.3jÚ ü7h#Ë}‹õ‚°Õ¢?øE”!©K¶D#­ÚÚ¢› λ ‰êô³ˆ`FpÐÆ±Isšg4Ê5Ÿýxî°Ÿu™ûó|ß•ûr ç,´õ)VÝph†Åzo òæd8õ9bÍk fÖÀ 'lÐ î?CÀÌ£<ýaKéT^£_árt 3Wš£Žc në_[åÚÝ~àýžÙœAÅr‘ò‘¢²”êA6¦;$Å«Û^ñ´Šñ^OJ\–oÏTM —vÄ+“J €®ˆ® ™Œcøâ¸ÑÃ>#Ó·Õé0¦\¹"ß•C‰Pô÷kº>èéÏ ¢Æ·3­öBv TPÉÎ=®LXªXÔ9Î*YGûTî"rj ¡ù¡œ-\QßdU7ø]µq÷å/›ï¸ƒ6‚›{K}È™¼ÈËg{zs™èÕÖ¸?¹ýl¦èð¢çk›_k’ͺkèÄlq fu˜!š‚cû”ƒÍßÈ_(ùáqhÅ›s° qòHÃÁÕ©½i)û@e©eÍy#/µÝEi¸`TéÛqQòB~KÊ?¼Ga®iÿy¤î~}FÊ ½IEy€-ñÉïaûÔÙ0Ï £VÝ]$Vܧæ.Ë_ŽWö‡ ŸßY½º(µJÃN¥Ò]¾´äåÓœóÝ‘øk6úç7.¦n³3MµExµ153Ñæ¢(£ÊqO}®Í\kP‡,³"x†j%²éºÆé pK¢M[²e‰Ä¿ZËãØÆèÚ¤Ü_Ÿ‚Ê›|M™„ÓÆ'káuj|šëY[(¯G³/úŸ ûM.—ÊuÅ÷¥ Ìö6‡žiˆÝÆ1ÙU3m­Q÷å1ˆZ„âáTÍL¤ÉÞ‰¥&éì’gåB¢tUÝ6Úk¹ÒpÔ‹»>¯a±ÅRš[WWß-¦Zúäפó* î±5,.ÐõQ„y] «SöC6ä>ŽÂg‡ž° ¤‘./Vž²íØÂ µN.!«‡X{^#ÿ-Rj N6¦2>¼Û¼ý”Ë]I¹õÍQOI:O¼íådXg$ˆ Ë’Xiïh($@qASeÖþ^›ÛŠPHdÔr+Û”_ó@F8‡MðíúDÓ?ÍZÁµœûa#˜¤¦Óñn`°p+îÉy‚"°põ‡£êÐèÙ²fCQºElîLÕTúç¼m­dH‚dg²Þ™C-QèšÊ,ÎÕI~ÇÇyÓºxr¸¬!§å¦½]м h¤pè“Ú¥º¿°èB›•bÏyn àÊ>ràR#¢À˜ß~°ëšx€mýÛ€}(¢Qº½s…¨CÙ¢ÄÌõ8‘ …c2€Õ3o}Ô£&5T¢ é-ðíÃ䥦Ÿ “Áƒ¹” 6u—è/ÆTI>x2«Øóº,Ϲٴ¼ÚvŠICZò"7²Z`º¬1»CB7Ì“Ž…eUã;ÍèñŸUŒd°Äµ_ þX²]¶Cì.ˆ'CxÅçH)à!ç-Wã §Li?ò zݼî…,î„lê¡WÛ½e¡÷‰Y’\ˆt”¤—š@½‹‰€ãÃ`I×g <îÝ0‰S7Y-'Ÿ@úœ‘zl¡+ ;Ëã‘SD„¿ývPÙK0–ÂWj#¼œƒ»;âVG {Ь•`¶ƒ_ÃFm¸1CùaEÒ c©H*z?îâﵟԅۉŸá@¢º0)!—ß_ ÉtѶŒ¬2—0µcU&ê¯*¬£ÿ75D[jø¥d´4øàNº§€1„ŒFŒTKb…p·@Ì¿ ÿ[ $záÍÒ¦KZÊ=°Ì!z¢:nQ!v&P¼0?é)> ÔJ,8Þ.N=aÛ¨¼ 1怔ääe‰°·Iná»âQœ%68#x ù–T$>äeõ0*¥8Ï„k¾¹2Â+t3¡iæÐ³ Teàa?ÂAÕô±vMs¬©8ú¼~z¹–¥1*÷9^©AáFåbA‚ß2 zÉ8×ï$@V*Ø)GÊs"u›G•Ú™§VQƒ†ùQÓÛû}ø~$^Ó‘*PUi VÂ Ì ¥çñzkÈl €¿¤)&â$Õk¢!O2ŸÊÓØ¶«Jç´¯Ù†t’b´uª3áš –í¨ô ¬^X¾l¡Æu¿‰w¢—µÍiÑ0ºÖµ­üjºzÊy©Ô¡\»',â2ör3ʈjÈÝ€C}ø/LøÑ0õY?1HdùôÚoÀ^wÚ´t¬ÞÈYDe.˜ÅVÑV?È\~PŸQ抰ÄÂFL^Ѳ¸›¯ú¡+¢Ê%„g[c{<áŠ[)y½P^H“Ñ&Í ésV[xú€±~öê¬ÈðydžhЉӲW\x€f´âÓ éXEl©/¿Ú Ѩæ¡ÛÆë%øDaVŠÉç_ÝuÁu.׈ŠI?«=–wˆ'||—á `p&Ëÿý͋ֈõ0ã–ì§­µÇ0†ogêq&Và•†a 5Ùù‰Ší,ÏdÃ4餌qN…ÁœÕ¥?hÆ6‰× ï hV Š(n¸# z#¢§F‚ÎÙÍ4NW”ëHu~Y%fëwÚQƒ†m…9ûmJ¾,¾D| <ùsÅó›~ë.Vs:àçú´oÅì¶g5|dóÔ"0"÷z4‘¡&>Cm×íªq·¶+ø›wIè8t-³Gy’.ý€×ù–{„F\¡¤éx;Y1ÜG,öÏófVmOÙ/cmáÃô ïÁûª¹[”lL/ òä܃ø™<¥ÕF^*% ÜçÒ[Sn悲¯Õvk45‡!^71Ã+€e Î •ÍÇ“ḇUˆ'ç~2Ë'‚˜÷ÙZ4ï¡`ñ>ÁÁfò²ØuCC0Û PÆ#±¤Šˆ³NÁ³ÞÝeÔ ¢M^Ä@!÷ä©(}Îò”ßpKV’ ˪4­wõËÝÄÎôðý×¹Õ­ƒ°7+ay]úËh2¹óiÀÁ6=£¦oò)¯æ÷ÒР”9v°­¤™/AÁXßághvðµÔ.ÑÓîÈ_b›;‹Õˆ”C=—T«—SÚ/¿ÁJ-½Ì9îÒl¤´Äš®ô oQi Å­ï½á`æ °çÝs¶ ¾ÿ¼óÓ“öå!8Ë繸TÒáx÷v=ΘþÙoaŒ›VJcòÈM½¸ÙWÕEê+Üö áuŸ*â‚•‘oKžS_l‘’g½L`˜ì╎N¸‰Üä(À·Ø!EˆDvEÛM1Ýb¸2?™GÜæõˆc‚žjÆ»a¢ô7I½Äx(¢Ç ]t®ÄKp6ž7%š½ÈÛ,CÛîévsÛ‹ Ö›Ÿ©‚U±! „2!ë“ sÁoíûsªReBr² æÉ¤zC‹Î–<*HΡ† pÞ'9h-ï5hDÞuK··€  cÊ¥÷>˜$fsìØI2ý+ã˜ß0µúYß"Ñ©íc›‚æu=‡ë…ø–¹œjï<õNx cIÿzœ. N£µ… ‰´o‡ ü¶ü«¡A€QÇÊr4â6¹¾t~¡`q3°âΗwD¿x—ã$ø ·`ç®^ÝÖ7bE2Tw¢WOtýîWïí¹ é $—ÎÃY-“¯44VÅ)èžJþHöë[ÉUe5õª¬av~õùMõ§ ÓäØÈ4A9£ØŒ‘†£¾!›³èºœjÆšH”—)Ò Ši(â8E±žxºf’¥ l…x;NöI 5n¥ AP -ÍZYîZ½*UoƒYÇRõ³ÊOúŠn?zŽÞðD ]ê|Ÿ’%³Ž“3Ûún¡Z(u'-ë¾#Yh÷xŸ ‡ÿ+ ›"‘b‰ÿôµî¦?:& iÚâhô D¹u‘ËÉ-xzð¸ÍSÉHdH /H°štØ+¤Þ´ZÉåZæl~yvÌﺚýÉñ.G›üéwm@àæˆ˜M¬ð·_èpç8Z+&²ž,œù|~NRØ–¸‘g%›¬QmðêvYþ±¥•FQ3#Áš&Êr4ÒlÀÔM“ºnôMå%MpApè7Éó 7!*o¥‹N„õö¥Ÿþ–A­Hþ€Ô-™¿@>tÐ(óW¸ïV1Öuø©ŒÍö!rä¸üá­Ÿ$ð2= ƒ{ÚA:=U¯0»³Ë’°lxü清_ÊïÝ@÷ê³o•ÜŠu›J¡fÄë@ù¾-ººÅ«,¿®)çÀÃüù} öó0E`-©&ZœBVž†ØçQe¿Å6Éú³;µc±Â²äwfäÍK.Pо*â0“µ ËV ¼Ò=ÃÕP°\$7GÜ⢼sš{5¤I¼ÓBn±¢÷ìósÞ¤'Ÿª Lh' Žz-÷ê¢l`™E2e³ÐuFbR`NÙ{¨ÖËÄ¥àͬbÓpåÞÆú`‘˜*CrÕÖò\l£kõ¯éÃfÍ?Y&íäóXG_Êr£×° ¨i–þØöb¼v;&ZpÐQxE¸@ü{ìèg0¤ù¡ˆçó WcQ'c–vZŸµK:®Þ––ZyF§LkVæÍ£÷‘e9G ñQíD‰‚ck¢|DîÌ}¼‹Kh7x+÷/4®>›ÖJ\†Z­ñÌné¿fC4:Ãú\6j¬ ÎÇ?²0,òY!³H@Üú9Tß©°•@]É×ø~â‚ @7MšMön„Eµ±ãm» BEg›ƒ:yÜû°ó`wM(£SX3bÜñ¿µ¼¼'û¹¨¿zbX‹{ú’|û?´x¥FmÏ!£ù‡)óᤠ´ó²ªD©»!3;Â8Å¿FÃè‘Õ~ÁÛ¤.¹¾!W†vüƒDÔ¨‘šâºDýþ¥OVÈ Ñ¢óî!E"¸ê–‚]$÷”°û}n “oÆ4ãurí¢¶yY­[#mÜ1îøÚ¶Þf °>qj"” »ïÇèüRè˜ú\I•UXÖ†ëì•Ò8ߎ:€îe¸ÿ+Sò“}¼Î¨—XÖ©­ˆBu*~#âí㬨¢}‹’hþèÔÏ*¼UhûyrDÊwþE>áêð±kg¶ÖbˆI¯qG·&B\«C«†Á¤÷4Éàr´"…×HÁ-´c¢Ö7Ú–å\:ŽSË.9îvµTÚ²ûF{¡nµÞŸ7üe(ûýŸãû}™'ŠÑÁÂK@éjµz‘Ý*GÀ¿¥Þa˸î~2Z&þX™að)”R’pBi­ºf;¥W¿ò¦ïH·Õéª._ }LU•^R0š£€Ñ1Î!è˜ãyQXé6¨ñÒ¯éïÐ1+ÖŃaw¨ömeSÂvXµÜoæ¬ÁÄ!1uk R8ÑW7oVÿiùö« <ÁáÆÙ‰€ÞH»7šrdQw$HBRmVçQ<>ƒ[LlW_q†çҽô‡ÝµN'NFeUùœÍÒÄ BÐ{'xÝnì–Þ*ÆU×bGгí…ÈÒ¤~r;~QŒøŠDõÜl©JçïxŒY €(ÙŠƒœß©võ‚3£rtéÙ ô¢©››zè7©£´ ÈtQ•º|†š›Ÿ¿8÷"l¯¨o$ôŠ‹+Sö~ô§3¼vV€îY#öS¬R:hJô“„íçÃ(¹*²Õ¸nûTO@ňc¦´ýðåƧúZ©ÇD¼ÿÉœ™E L…f@Ë[Æý›"!‘¯ù«l»lÒ×!½ˆ"‰2Í¢¡×wòyò(.n%²Ô"@ ƒï¡&:Š?N¸V,—ç‘ÈŽ3öHc†}ìêÙ¬®Ðhs•?šse.‹èt¬Qv™ç÷Òà/g&«Î뢞±K¾r•ÖœJë9: ­O^WŸY§ÖKBÿ½ ôaŸásâØM{°¬9£Qörêdt𲫬N5ù3‡‰ú£ó¿o,"UŒ%ÅMÁd¡žòøgâ«Ý(ˡüYаîÁ« ëù†/ÛíN;w*ƒ–(+åEnfuÕÉßæE„9ƒ²3w°‚Þ¢0¹è9ÚàVº úK"r­çù‰ÚiY–¾UG  ·Í»È੨zôºð¸~É(È'"N*¹Ó*ÖtʕЃKmiÕïÿ¤ x¦*nPcnœ%ŽG𪂚¬ V;RÍ YAç4—ÿ%n7P÷êÛÈÏÚn¡íÓØÿïp鋌É÷`ô‹V7–ôô –›"š‚ÅU¼JÛâIpŸmG••,øzB[¿Ô¦áåíP·q!ÑhÓ`;»Eç`«[?+Þ’†Þ÷ p)u#ñ·‰·“|=¿ÊV´ÌÆÖhƒ4ëæß´ôC¡•S”Ñøïðp~´2eV?ÊUW‡ïi¶0 6Ü@A- úÃÕFBõp)ÀŸšùµ§ÓåÇ1q¤äçì 4>A4'G­@|01äòÔ¶EoÔ¯UAúù+.iùïAÖžbá²®eW]„Å¡dë§<éånŠã õV$GE>œ#˜~×Ū2ü­fÅXîûON°S¯n&©&ŸzaÍø†×þuÞ–ÉÆÏ¶ã;hêÁŽœó뺊Oû¯1¥ß%ÂÒv>¯MÍðˆM¸E£,çÓs¶Ü­ú-c,iă¿ïx6ÚJÀ€\¬ÖðôJvªÐ'FÓH`b ¾Ê«oÌð¸¥˜ŠŠÀPøø4s0çÐÇÌ ½ƒ'6H°=Úe ÅT¶bJ "pôáöÕ£«ô½Ûí¯4ãà™q´‘ì-5šG'GÍÿÎÓãkaÑñÞ«&kš–°kÒÊãA"É>8Û6”³òóªÔt†Þ×;ºA:åð–.ˆŠÇlFû»2_¬?­£ä˜g3¼l9ÿ ÿ7Ý6 iÝë‘/S#d–+‡ØŸþlî“Ú6ÎA°ßEÏ©ð¹d8^ûcÈÕ{ú÷:lõjP½i ;cD$Yâ8÷ÙÓ7¡ 4dÂUÿ¾üãõ†æ[Úd©ûµƒªhNSþ®ଚðƒï´µìâÆÆ;Òâ±|X»;ò0Ée¨ º¦˜È[/,8îžEl@uá¥u¿‘~Ä–×ÓZ@©øÈöz4Åþ_X+ƒmiœ“Æ‚ý3Ñ|Vy¦·óD'+çsoîe €Ï/P»ìÃ×ᬣ-XùÖ Žì®Ñû´I"Þ¼oêºtågã3zÃá ÷»#°I½3gD2nÁ®àUC'ĸ,é&®‘æNÖ“w§¢&Gå'¡Äk.`;ðô.¼è—{í|eòa\„"NeÚêR ŸF(8’2­!qAY|”ˆ±­)\z¦ÀOöŽÎ+EdØËB^=ì|I 4ëuûНŒcÏ{!:”;†âlõ#¯g%3ÇK¤%*÷o3¾GÄ™&'"'t j:Ï46‘P¾óòó§­5*Kº[Y|-!‹]$ ¬ÓžøÏ­Ø¬ÓQ XvrCZV%foŽì™ÆõXÀœèw‚øJ€{HÀø·o°•Üž:Ç5ÀáU;(Ý NÒ¾Êþ[63ìF¼â(‘8Þ¸Z˜Ñ—¸QòÎþÀeWª4ÏÂC?C ®ƒ,•ûbòœÔò;ª1¨äcmow§BúLÏ2,Âç=àe:Àe y³gðU] :-šÀïíf_¨Gª¸Ùoå]@¼!lÉÀ\<•¬mŸVJ:?ï%R#»G‘Ù¥1Æå|÷z#ïËÞÙösÀ2ÏÌ£+ñÊ9ƒl þ¶þ ‘OjvцSER0;#šd1^4ûÆÄüd=¡È ]ïóGØó„“¬xæ@ÎlÁ¾_ÄáÒâtg¨`O\'Ú¥ÛïþÄžpû]yYNݵ¦?¸¬~à.Hæå_Ú Í÷èÚîÍM m.qIÕð ¤WŒt¿øÑ¥´­–ÙC¬ Cj‚HA¸Ã ˜óJDïèapä×7S¿U±7ëdž胑µªd–É€@o˜ç´;!®•‚¹C3®ôIWhÿÄeÞ¿µŠðãưJç½É´öŪ^§Tç‡å§*soR¦ì‡ø¨Lݬ”ò’{˜7ü;CŸJ2ªa£«¹âª¢mç_õñ.öÞ¿å8tMšst8(s¼ÔÐbJç[ª›Á, 2\Õq)ÝRk² r¸ÌC\ç&-;.Âù•‰Ùù׈ÔêÀöøïÜÊ“ßUR{g‡–.‚_fݼÌô¥E‡šVEáÚCXラ¶dåóHZg¸r‘ä–Äów‡J·•&§º­1VP'(ë÷—Üc&x¢H_3Ú,áRÎj7|69Vf{ê¨[Ø@ Sk}g,kI¨"ûa¯_?®šbè4–7ç¹¼N†fxéî­ë¨°ý!Q+.ï2çþÑMFÑúq¨ÃµlŸ>Ý­¦âuÛ 3ÆL⇠¤˜¬£çÊЕJªÞ/2—ŒW Sb¸ïT³*ó²ûÔp‰F9VvP±/Gº‘S:¯O$ÛŸ*Öºq £zÈÓ&·'¦¹ñÞ‚°þ‘¿/¶m¸¸È·ž±ÈBB:.!Ö¦~ͲP×÷´£8©³q­Oaœ½Ð=Œ݈•ol/‚þüœ“§µ+ßg¹xÇ !§tgdiioñs? íåf¦CÑ™Ytþ7¿ÒÿûåÍAÿÓÁLàÞ÷ÆÕ2âÈXžÉÃÈêþwu®bâ­§™: qý0 ¿…_²%¥4Â}:¥LÓœQO侩~ÇKÅþoûìBËþ7JTP𡽾aða­C€ÿs\+ËhsHú™ä+Ë•¶Ôˆi@ Â:Ãý‰$2©À‡¡I9;\Ðc· ˜i„½:Ú4tÀÿd¯¥peý ÿ¬’.ʰ-p¦©{ý¦z'Äù¢øßu‘pyAž²ý˜J“AYR1ó^Ýeh2¼ÅèËýXÒ6+Â×Õ uÕëå÷’Uì–tçÁx£­|f}vá–Ž”]®8 ÓÈ >QP-΀á„ÒÆgÛáµÔ Ón‘áf× i¤ž‚\xÁ:qÏ{YïѼjý€^1(öèø«yøz; ™pRÈqˆâtøsö®„ÓÿõÛÅ(2TF¾d¨GØ" *Ú½îþ¬RÁ(}¤¸Ã|¤”ªz’qõ7òÿ®~ŒÊêá'Üc˜ð<…9ßtÞ|Ëf8¸÷ähøP 9ã²R°<7Èl”Ü36çQ§EDÂúV4D%ì7á#ž8L2 EiQ¦¯¬ášŠ†¬¥º«GŸÐá³ÿ¼\þ[šb Œti1K}tÛþ܇á8.5 TU–?ëÊÙb´ ìIúzÁ„ùÐLß@ @\ºè[_Udp)v×V,B¤‚|fa¸AòÓ‚†sÀ´ŽF4š*©,Ê9¾=õ%J˜@Kéð¬ÈdUI€=w‘Qh!ÕLwŒ;=ˆ¯Ñ¿³Q¸p¼?+¬–H?ÓV6+˜Eù«%ŒíÁjm+ê{øÜÃd¥éË ¨=XÑ>æx68ðÝSƒÔyq²E›Ó?‹Ÿº0þPyy›Nß&4gl¢3 ªµGçûÚlqïG0Ïç9Ooö"ºÂxq|T”Êêå¸@¨`É[|p^–Õ¢–ÿ{ KÌ€sܱ£•Ay½Ž?ñ¤=ĬrÖVN¢ÍL1]}$Ñ‘éáwöõ"_6‰}ˆ¡¹°BCÅÛ] ÿ”Èéõ›¿(gëÉNƒ¥æã¾PQÚ;¢È)mcuêÚýÊ©z¾×`ÀõUQ‡ãÖnBdt­·ö:&>¡!ë\ Ú´Š§RèwÞ£°Âµ='õÌ-Ë4äå¹ñ¶×Åié_xÕ ˜&-%Òí,k&µÙZÿ*Qꚦ-YœâÝÔ$Tߢ¶²ùëP䢨V‚4€9“)—î©É‚y4ROMA[½ÖÍ›¼p8¨Å}Æ~S£=WÉ ûB/ ô”yùÝçñÊäº3o`Ê#&£¬t êˬ³eñ»°Þ3 ÀnlF‹ÈÁXµ4éÕúáç(1âh6*&Kl [ 1×›ØUñ ðSn?6œ®äÝ6峌G  >ÒþD2mتb‰¡µ’²"£_­¤[E5itç ?s5~$B¸äyr˜ìÌνߦ$”úö¸dT›ð$¯]p§i_N(®ú^“YÏ×¼ÔêÅ ø,¼Ç7XÌ Q¯Ö¶5™I¬9%OòUB#äx;™W•_§b—q4 ¤™A£S`On ¨µ!¯ ­~~Êœ3gh¿R‘£¼TñkxžMð‹A\¥ Tò<L›FpÖFŽX_±ÿ;Õ®ñE•”ÅFK+¿XÒó*m‚É#±È$a¶·Tê2¤j½6)é ]ZÄARŸ/`½Fw˜gÚ\yH<ë¬ZÃSËô‚íê5pWW¼Á¹DPbÜ(ƒ:€— 2 ÿør¤l:Óà#mººÞDá»% c®hÖgÒ½P”Y‰ô›Ý Uüù÷þåà/k71¸ vž›ñƒÕX¸óV‘ ¶×­Õ»‡Éá`ÕÓ¶Ê8Ü×µF':ÊŽr#L+:’­ÊÇÀ™µ bv¾w-ž¡·*ÞÊ{U÷þL ‡‚Ãó|ï&1QôB´ayK™¢ò¼Äžßrœo;ÛŸå¹÷ À¸ÂjfØ¥íJSжhÔÚÛŒMGŽCºŠ}ˆs¶s8 ÅãQKY­šbTdéÅgêžÀ‘¡â£f,Ù!÷ø  Í `°oSéOFóGËêV ·áÄNaÕ‚q‰qP--OÖ' E/µ=.\VãeAnº.¹m…nÏ üC¥y¼º¡3’êc]žö¥Xxê@Pâ(«ôBq„Ôµ;Bë>¬ ËÉwNØÒ\ö’uì÷Ókô±<Ác9d·m¾E8ýNd²Î0é/€c^nØOl†ð†äm›2Q¼GJ°þÀs P=)‚ ÆËQ®3s5¥û»ç“´·± b íÎwå¢Mª±.üÔˆ¯u€Ñ£ Ù”ðDzÆf—(K?ϱ1…Õ„— KE—zع?•m ?žÈøšGò‘zpÌq¤ÿI}s b{Ψ±EgÄКŽ¶†"ÄõaŒŠÜ¿©KnØÓ¼G*wÓ.}tã%µÐÎùìw·&ç†f’"ßü¿M#>h ¾d ÌMc¢VLwÀ6@C-,d1Û8Àkí©§kYŒõö&\õ´ˆîÚßX€™· 8ñ›þ‹Âñ ŠøÈ»ƒ¡°ª™¾éà|-@Öí#åÂ3‘ä•á3@N–ÞzYÛtµ™( G¥eÄÕÅH­KӲ༂ÚÊ캪–]˜#Q£ÁäŸ]àHš„ß§s€H“Y´Ó¹ùªà¿"ÚŠkz+´H×)€¾çÊ#ZV*F–bµÇÀ¶·xxfÿž;´&ìÖO|¼dÖÙa% fc•z;fè4p˜°øpåjÜžSéÅéăº)£”ÈQ÷ÜN˜Ñ£¨•,|.qSÝXq ßRj…¹+ž!Ô˜¼â*$W›¢Ä€ëêÁoºò|uììR™ÎÓ n^6g¬ûÓ_´Gƒsj:Y”#j¥sá§1£K;øÈ4*³á–Þê|ÿj猔ÑJ‹¹¤«—Ñz’{hÆÉ4 #û«Þ¡æÏ»i²íæV³ë² øˆk:êû"Züc¬6ú{W,Buo§´J|óƒ7é‚ùÊÈâÓ¸µ­(žT{ÆFqà¤Â_‹ÈXÐ¾é€ –ÄÎsÑ©ßm¶|SÅ‘jðŽ³È‡Ÿ ×ÍŽîù÷üyíûƇv›!ÆŠärRZk@>VJd_ÍÛStt+Ó—îÍ^І”‡]$ ñÊPñò T7r·³Rt¢ò¨3òÝñ:ÅîcDìØ¢Œ›þy»™Y…>l ÉZÝ“úãp4–ÆJžÉã+"­_~ב"“\z#ø0î‡lrS’ü¼n—“‰'ýƒ‰ˆn±ß‚ÒI 6¡bñÃSÜ6ìQ_žŒÏt> 1´™ †K°=«ÆÍ.ÂC¸ÌN¨º{ïñCޏãÃûÁ‰Âf ˆÎm³­ÎjFú±£/Ϩ•×-͉‰é>æ-‘wÌÞ?¸B"ºv:=”¦âÕ!·JhVïôÅC‚>úù—?—ßìWÒTq]XvÈ<èaÒFNg\|ñ‘]gw.þÆtùÕŠA¸G,½ƒÉ:­Š mZ¯V\ê† á¬JhÖûPà+g ‘ðÆTÔìÞƒ-ZÞwKªÆSÜíò•smWÞòÛO%*GC}³*»¿ÈGŸN¦p6<¥Ž²2¿Ÿ ¼: Âq˜A‚Ž%îÿ{wA²QÝà÷)‚¦QIÑãt+‡ø«wWàÒw[s˜§;SªQö ò4@œLB%&ù2JœáÔ¶”¯Eö hå Ð?›†ð-šŽuHKÇ1Ⱦ”^(óݘää%&‡½ðÜÕ{€í³dŸ×n'¢þ”iª»1G_¯ŽìB{ŒM£ HXìß@‚Þu3ri*É%cá˜Ùhë'Ù­© 6n5WAÐ,…ð4о©üpSôÓ5t •÷p®8 ‚$Ͳ¬+D[ϸ.UR»P̳ãÐÓ’÷£&f—Bµó!5‡‡Žôúve=˜Ä¤ó¾îÛ¦GÆŸaŒÍ“Pž¬E„þGT•ªÏ0Þ/H&Bñݶ 7Ç·6ãl= `!E¿”ÔÎIUëú"¶›Ûêøæ‹3ÅE‡jÍñVRð³nhS([_:šc¨¾à¿k€ÅÊÑJÀу‚Ú—;O+Ôi”F¡Œ«03Nð~è宅ƒ)¬úæÞ!W ïŸrïÍR°º·f3+Úú[Qgݨ òèïÊ•˜f‘ÃA”ç:ÇN9ä^¥µÂaîBÁÊnA*Ž€h½¢ÀàŠ`ߦ°Baýn¡hÆ”u€×Eve„n9ä‹–’ô€ÝÔÝ;£×‘¨óÛZ¥@²¬’þO8C˜ úpí%aVæj~KžqýÖÔ Ø-ÎVÀ‰÷÷¸¡åÔíâ5žé:J¸ø«~nä:Rƒñ§?K¬½÷âðhAf[¸ž¶R0s߈-üÿ»µGâöôÅ¡ìAéSwr¨ªzˆ-h ÃDޏÿ[8µxL†Î ®“ÑÍ=Óî1ÌÆ€Š¾Ôè!ªý«Y^ü5Øpb(XGé~÷”TNˆ’¼úƒÄ^˜È:˜UŒ­»'œHUxˆ†ƒÓÒ¦‡7î|w22Wë} ë‹'h=`aw)óF©'Ï kOm£^†BÊûoÍPó1+Xpínƒ|˜Ôž‹ëwšryà=ä`ñΞ°šj54œ’+;ÆØdèûöÕ‚Š4 ºì7ÿE º’Rf-œ,Ôa!WN½ôåX»–F¼3¿rQø'ÌMo>èò$.'¡¢Ü„-Á;ȼ˜ÚûRì‡ÆØtš‚'#ðå„A'0Îþ-;;S#¥pNaTÛÅÄÒ‰)'¹uÜø4 $‘àÜÞ¶nAò»©-`;ž×œ¡òbØíH6;EŽÀo‚¥4î_¨”ËCi¥%íz í^ÜÛ…-dü¤¸»»¾?ﺚÁ9û­eƒ”ÛŸðÞÉ”EÝñÞwÀ zþåW‹Ì@ýØ øÑ|–ð ½zA´`÷ íøa>§°t`€ÓŒ\¿lï”/¤&ä4#šjW IEå…âׇ—‚ É2zUœ”±r¾4¶á'åam Z‹ªš·ùZÈPVwvL‹iÁ>Q»zÔµ´>Ͳû§»ƒë5‚?rFÆ‚ëÛ@X·Øp(”¸ðíÞVÁÕÚëEðw šÈO1Á%[ón­î–+^{ÉœœŽrª‹‰Eÿ³aä :A°{ñjÏW¼î_6a„I¾ÓTf“ϱo(Y½Å¬”lŠÜ27W3óüÒ³­3ÿç4=šÕDé3uÄ„j°¥ÿ¶ës–2IýAF@íÅIU ÒÈ`ž·¤Êà:1Ä,!s!é=YxŠ_ÇYqlÁžvœÎEXŒ0;ap±³0™x¼;‰ôvÍÅ7 ÝùSË "ç 1ÎÉpæ?ÿP™äœlÝ>/#ùêK’i/É¡´‘×ËT8YðÞ¸<^ʺG'bÞ)GX‚’xp¸s©zl„Éd¬–>ŽÏžkÚÌ4»86öýûR€ÜbBÒ’óz!!»wø²çZö>ï7¼; ÔÖ.À…;2ïÍ‹6…ŽtXgB‡ÿ`lã²~t(§ûK«Ÿ/øÒŠü }ýÀxü§Ð!µ?ª€/¨áθ?ó‹»0{‘yIcļ–²cG’TŒÿ–>´7èö©õpŸÚ¿_™ _JO;_áÝBÎ4ÃG `ôUÓŠ“©Õ‰hx Ù}®b—0Xw|ÝÚ6¥T æ)J;ý‡ûiŠyÑ.MÓÍõן^…ª|=U«VaLv@At¿{µ`ÖNÃÇY^ƒ@szd—q¾™ÁðúuõãYciÕåíA›}tH…b“Ò‰U™ 6å_Ç<~:ù~ÉÖÅû!$Ü1`â;O(1¼<"󇬅IÄ\!Ï÷¹¯XNQÍ”Íì©êÇ@nžçåÝÞ¦â„ãEâïôPû[YOµp¾«9Óèßr1|ÓÜõéc°º¤‹žY2 ¿'õ÷âSLEÁno­kk¾û7 6œ:Hs3ƒø@³&…Ì·_Ð'õÕo½1M‚û¡u™YÓT4J?aÂþV}R¶“àî¹™8­%»‘ •×i=ý&aÊÜN’V;³Ç Üý¥bMõãK\ñ‘Ë&åxô|í³Qo܆ýz6/cy\„/a1)û ¨ù~~œA°qëˆÜ÷Gý^ãáwøfjø`‡e¹%Œó”o½$ÿûˆ°ûû»—°ìz;¯z˜JÛ „yu]ãcÞ.Šüʨ¿q=©Ü¯ 6%^}óì€3ƒs=VµŸÌæÐvö@‚‚ÿÊDu‚ãÅøOgàT™Ð„ ~$Q4¡k—”Å*J_rÁfÙK`ÀîsªÝ°V“²ÊŸŸðò?ÐÓÛþüV=ª]ûñÙT‹½ÐZ8 ÕlSleq¤Ê™?¡“ï°5j´©±AH«1Ó¯{|i‡Á–„ôÂ4SÒtóÏX¹eglÕ þÒ!½ »è°¼ÐøiÅ¡:_À¤X,œ‡<ZÑAŒ»¿û!øKS;àQÙr6Ê~ͫʽK9¼„eÀ2]@ôö±¶Í,£@îW'¦ìP.ÔŽb l>QGå×y¾ÝPÃ,WøhYñ¯­¤—:*ƒ› Wð>u­P.dFl±Ê<^„Ë“®šÙÁ*/_$~JSûËh?ô}ŠWëM#eضæ™ÿi·Yò>M½5Gû•ÿzû¬yÙ´àrþý;=¦0$L;Üöò“ˆ qÍ-ÔÒX?<›°f«J Õ Í†êzYQæÚgWˆ?éùN5`¡L˜±T%h7ój›'9s<ê7ˆ2ñÍ7‹öfê>nz«¹xZJ ¤g_Œ–cT(‡kUá_¿µƒ^v‡_žBbm#Û;– ªF, z>Í×Wé7Hé|a!€å+ß§wóÂü§¶®7`ÈšÉ4»ÛC/´©‹œNØbR?«^oÔÍ>çðGxOyŒ½<.Á‡C9æŒÂïÊCÒ{ß®ä:zRfžñ„:»a§ç]½™¶­¹okïOS­3ZìG†÷Co|q^›“´ Æ£kc ¤4¿ ^h‹vO‘rø<`Ryã šù ÊŒazÙª‡àU-Ïñ튅C3O˜H\EPœ¡½œìÈs¶¹½–øÝבëÆgf <†»‡™ÎÇRZ> ÌÚíÒZqN²À¿™F%Iw _èåùÑ)±d×Ú„¾rÜf|µ²)õ8 jñŒ€2ÝZ_~n–-™„úÖî•ýÁ£A÷rÕ‹óÄT°p×£aÑiW&2é$óžË‡Ï w¨éþ·*µÅlX&ÊèO ¡ÏèÐ|`{ìiåŽW_Eª@èbfÕ¡û¬kß,£1 æTß6ÑÓ°%M[”«®ù™¦õÁ½+)FÝcñp 0Þr„‰7M‚Læ«ÎVMÖb¹†0\tSë"7õ¨F*nuëVý‡hçDa!A,Üǹ¬€ Ã>¢ª1ÀO)»if¤o½ÁÄAŒÈð‘ª×8ÞÜrO„„‰’$2xµº&)Ñ›¡QÉ #‡rÏ)U|Q.1" 7ª–¨Vû´bÒíâ4FvÅz¦4Ãí6|Ú+•’^l—j¦dÀ'ó&Í3§2S¦9]ÎÅKpÉqÃø\L`c·®Kuï’Äœüh\1WÍÚ [£¤‡uæHÛ€…ɲD Œž«È‘d̺»µGRNÎÿhì ŸØk“-dcËS…o…¹iG;~|€ƒj³¿^©¼`•YòpÕS[[LÒSÑâ²Hÿû\š#uâ¾Íõ˜œ€×rÍAcç¤Eãb£DðÉö£Þ…·ÔÒÖcгáså¿¥œW·×ÑVáê.`.ß4øP_ïå")Ðr[F!™NÈ(а7½ »= Á§>ÐÒªß-h^ϳVÿûƒHûÙ^ÜŽÛLëùžœÅØ‚iéUŒ7~&ÆïU„*+÷Û§OU¿B‡Ä/pŒï^~ÍvMz‚°VÅàQ¶ÿÞt<;›=t‰8$¤F+`y4w‘m²aâ:[Y…І0¾CTÚÎÛdmfÆ 6w C–Cô4]£‘œ-·„ù£9D¬h eHÓVºÛÏA´C¾Th7‘eóz}P£Ñ”‰™héÚH0¢* NÆ(Hýœ`ÄRW[³²tƒð€:ê´Î}¹LR$/·hÍd'\RÎUðöòOó¨;\˜fëEXîwüò’Èñ/ìÒû©^•÷ŸÒv#[²éF ](ˆÖÊ+N+ÝÞ•ßæåF?G3sH±f~¨w<ËÙ Ù94ia%RÈÐóÆ{æÅÓ•mÞ.ŠR¥¨ Ó‰|ò¼%õ„t¼ [EŽ>1‰­˜70Ù¼€duÌqx2k"èxê²M+GYs¤a25ÝcæýšÀ ýuXnLÑ6Ÿ¾Û£¥fÀ:½:–<&™Œ Åÿÿÿÿÿî óJÙ?KíOš†‘À³jÅ{ Áç8Sÿÿëí*I6tþö·쥪 ‚2¡ åú‰f¶QO(ÉáX¥vâZÜ‹àÌ0±Elu *c¸_úGƒ\}ZËÀ–1:»Ëgˆ­ýÂ&»µBDÇatž®½ÈÚA ¥Lëhu™šŽYUc$ºÒð³UϧÔÁËÄàMÑ t虹1h‹ÓnÄîh0 L0†ÈG=ðð^wBèÒë—*¼•d¬|ôÏ s‹#gî°ZkßáÑX‚Fç>ˆ–«MeÑÓ\Î'¶p¿ÌÂT•Áü2Âb4}uTy+¸R4Hõ ˜e+/¸I^Ì–±w2ãì³;¦|¿Eü=üþvä‹'ZU—;ŸÍ?à½d£u$WvãtfŸDñTV²öÿ›E†Àmáã¹tÀ˜,À¼ ¥¡Yx9•@–ýRM"i´b|OY$Ö‚]©À”of¢°LYÅ‹岺ÅÁ]ºílõ b¦:´J¸(5֮Þ¦ÞhÓ‰}eNÒª­˜&¾Ü#bÀ§.…r©2²óKÅ{lÒ”o]\}»¸‹%RKDDùQ€YÌNq¦øÚÈ×ó•Ä`áqCãË‘òÙúÃi“M*:*Jaö¥ÖE÷W#csÌ‘#ßËKõ—b·]loˆûÝŸfÙ“ÃàVrÃ|=óˆˆÍ¹j§4ûØÁz¸Ú7M¸KĪð“?ÆéIH™!Çßc¸¸«½¡'_Ï“ ¥Ålydâ´žI•)ggƒ­ü.3›zå5BV,ôSê¸Яv‚ïNÇQ>„¢Á·ür$ D2søØçÀ{Ö×èk~ ó‚`Ú,Rƒò‰¥Yƒ˜4ÎZc©¶øëè.C±a”sÜ7ƒ1ˆ àÍAF¢·Z“S#cx §SÙq}{rèŠ3¨Š¤%Ѻ ±c؇Ü| æLW3ý'~mHÂ¥”ŽAQ ж¸I‘N©Šù¡Ý_ï^<³ÔŽ 8l£T8-ѪÃʼn8N!tË_b±LVpæÓ KÚ7 ¼ëK;ùü¯d¸®>ýæ`õÐö É…‘ÐÑ ~a)#³}k,lo@¾²5ðœ× ^[ÂOý Èè_CÛÙÑaÄ#â·-4tÇ®€5>ýK¨Ì‚!Xèlé¨]`sU2)c¦Oåk<E’ðýùâ>¹Æµ jHˆ¤hÁ‡¢>#èñb€3š‰Éozù•|è}:]%Ùygý€)‘íÈJEkágiÈTƒÂfØ(+Çàz•tüB©F¾/ L5ã=À/ù(¾0ÒÎâŒF}V}AÂS‹AÆ®Öê* +½ªvÿŽÐ'¥ ƒÖë¾À±!²þô, ïŠtç¢;óqíZâÏr ÏÑ^ί‰›Øñ*xné&ŽÏžìÕVö_Ë’Ã ,£¬ßePftXÎu[Øa°Y#®ÿ’m¨®~j‰=gü¬ÙTЉŽ(…‡Í¢V™UèÞÒ >“ÐbÇWkô46½Ü¾<´‡# ïIP¸«íöˆîâ=Ònüù£ë—Ó .Ù…7žü¸•bA½è[¯Â€ˆr£ ¹þ|Z+Ý8=Q8Ô†¡Y5Ùv§êPºVh‹6?JEÖñì3° 1'?BfO¦Þ’²Ô ÿ€:4?¸ÅÖ`™-öGܯ#{»æÇŠVÓÒ¼þI,©'@àì7¿Wû7—Uúnµ >""xâË›†‹{’u÷ilõåˆr݇K.ã³ì¦)„azò"=%IÏ{r+Æ9X)âUH¦ ÑÛÛ¾{+ç;9g1ÿë‘î˜ ³œßm£“Î…GñÀiï0Ö#…ÝĶá0ôÎ*sÎmæ:Û-:µéºtôÐ 'n¯ ·h3ÙM•Z·`¢ñÙ·¥ p~{Ç -’Ån¿fv YmCN ”ª¼eÒÊ+(ÆIÔ¦„ø6} Cªkfäí`ú³Ü8¹ ùi;fCéh ’ÔD^Ši~>¶1FÏË3©ŒâÁZqS)'&ûœúÉÑ]ýÝ£t;ŸŸ1 ÏêŽ:Áû;©ÊÔN¶«Ï·vŠÿO%]žöP®l{!«c„ ŽvHHl(dŽ®3¹=ÌÛ}{±uð;Œ?„‘ï·r²f^C³ÎS_-™0Enq0Xx×wÞgYÈ+6´o‚ /C§ÔõyèmÔägöYFÆì?‘±42“p™¥‰ÕB-4Â9 €âV1úi5§+¢ƒy`pC”žûb!ä.õåûœU`X!/þêÉ®¥yí¿ÍO#ŸVÔ­`(]êDôååzl¬Pq&¯g¡µaãØ`®YY¬o‰Ï#xzåë:/JéñòcOSõOâl¤µãþhe°ÇÏú“ŽÕÿ+ÚA÷Àé_í}!³=ÑúН§üÙ€¥ªDo·Ñ‡ŒF&±”¨‹Ä¸h«å@#P@º Ì÷ýjG¨#Ô?t vÖÐ[ཕ¡ßóÚ*Ûå½>éÝMeË4DmF¡ÀÙaì·$ŸEUÊ÷\š³KL?b¾ªc6»ÐL<4œ#®¥ÆEËÝù}ßâQx¿É¼/;ƒJfÐ~ðÔ‚$Ô…Ýk@ W™[û"·¶ŠX‡‹éÕÜO¬á¾,<ˆÄiY ®¨ÖAyIĽø7w‹ãpe e¦d¨óeÎÜiŸ¼ÓgL‹ŸÆtþŠWf»Æòe÷œþ²CI’…2ä(žÓé.B#8§ë$>£–(šs–.ð‚rûÁ½~{€Ž;·Æ¾@¤(§³q&-¯ ÅGÒÜ5‘¨GÃ8JÃó’g¡r5ìäò€p¯rrá)UÇ÷CÌ òRWú ™³=«ÎЗ™Ìih wä½nc¾ë|§{S±cU˜Ã·.ŠÖ* ¢Á2Îë.27¹¾-k~}BH \p…r“Û4£}´Ð\B}ø²^s+øW ½¶.¾-Oáu#ÑX%¤ñöŒwÓš ,sùŽ6Ä$:õÁ -lx š³*jØ”±TSéº %â»vßxãœËUê6­Í.ˆî4,÷ªç)ÓÍ«ó¥”OÊBe—F±0YhûÄ—í¢¯­f^c4)sVpÄo_}ôüúií¬&’Ï £•Óä¬M§€¤ûÈv]e¡çlÊÞ©͘x´¹fê`R€1G^fxÊ1-q9DW†¬Û!gk `_©ûr·xGOzDfíwÎîIšýué Ì>kà`óî´–ü•ؽÂXÕÏivŒÜ´QH•Ö,•R¢WõsÏCFE}ºKïöÅ #ÜŽbüŒ‰$QË.œS6柢7엌Ģ„À'(”iýžæþ}@["K Užh~ü¸ãOö†P­©Í¤Ø™E_á}(ÖŸ´d‚ò¡AýPp»ûD­ÿ–x’{uŽ#œ^P{òœ´æ¿0sÆV¯·HËáWér¨ÚÕv¦dIe„(j(êßÑQ¶)"ç-X¨¬,&/$Še[„H•á-:`ù5œß}1¿¶ËÔaŠU'ˤ_Ó†ù]ôºíEpEûn»Ž;sÄR³c7eëVž>1°›…£ŽNŠÐs¢½3ó}Í´Å+BùˆÜös›áU£i‰u |þ®8 «9>Ñ,u@žU›8ëP`'2Ú{7åË=:Ì#“·²Oç–´Ck,PPj&ÓzlG’À•YN WuK •ØïJ¢óße©º†"•m­ôŽ=ö[wù÷] &¿§µÐP ·ä¾UîO´Ý…ò+vÙ}ø¡&ê¡T^¾14¨&Všþ ¤aîkÚNϸhÃôˆ¾îyfëApÇ·<¬K`XNû bß焘äÛ¡z¸£" XÎèa‹òo°/R¡Š´øq±ÀüqŒ"ñ=ԉķ×nÌÝë:’¿ ¿ïºrÅÜYh—îÖª†8?N"šäzÀ'u Ί±¶Æ‘8{åXk¡.ïôƒ?™?nep›^{çc®€µÌ*»m®8‡%!î¿UzK °)£çD3MѤ;*È”$J[ä¨=¥Jñľ•ïUW:ÛyV±äqrV£©§É>(Ȩ–Yú"Æ>jd6xéo ¾_j,sûVÜùà8¼åñjÄÉýNte©hG`ªYf9$ž‰)x‹Hbé8“I·Hg?濉þ>%™Ñ?¼€R-IŸ·ëa|ÒXÿŸè¥kÕA<5¹BÂô¹wèÃñu4òºHp!"î¸'Tew§øa·Ó}öÝO“N²Q-,XnP]IÔ‡ÓheSº»Üdò¤¡~õkÛ1 UxQÃjDêk †ƒg‰v¦ª¯üûcÆnXÁ+oèX&Æ‹¥5RÚãd=M¿yùä“îôÔ ‚½Lü{>IÑ@0¶´o¸6—ü¹ä³~{Æ€j¡ñÄpeQn$¾àR5,‹R)^²]'î"|5ŽC¬˜{gLŒdòn¨’ßi% t£¸‡Ü¦,ÂVM²y2¨×Tþ²ƒf8;\“þ’lÉmÈ"î(#çk,š¤²ñ"*„É@¹4OÂNƒVcô”ø#êE ÈR#på«ÏOâ äK0$Nð.…¿¥œRa…W ’³šÎì%Th@TzDê·òؤ<É@áÛ[~¿¼gãØ#¾@øœYݳ]ÝXI*—u‚+pëSOd¯h]ZSw¤µs8èP‘Ëo s}ñb¡L繌"UU%´«?J:[‹»×‚lB`ž•%ºÂÐlrà,@М¼…ìöÏ£àk ×…Tã`N剢Ý—!›üTà e‹Ì<ز?Äï´ŠÂ& ßy18ãîQÖ¨™Š*‹ìÍIE:D°Ú£^xnÎþãÊ_Ùé?F¿½¯Û3®Žš{\‡äBw`E ÙD$ õ¶Õé-x2t{ã»;J,ÐúROðЕðñw¶Ûƒ„5!Y÷¡^‘Tp@ùú=ÖMè¡"MÒ6¢¨l½µ[_W#‹¥Q®@ã¿€K]º[5Éè^“y·wRÏœêXxÒÁ›DóÐ?Ì<àf>vÎ=™ïÝ'ðܾ†#ZPgpëÖ\ÁSxã[O«´OíEFŒÛ h¾IYo<ä[í»õVXÇð.û䪎:àŸˆ ð"Qi2åÛçþ:V1ëåÈêÕ7Ççw^8Gí#ÚùNžBû_×HÙ%@³2¶/’d‹êÈ'ÈÝÖfÚ±.ó3ª—竺-Ÿi&wó~KlC¿äeÚ¢IëâfVÍ‹ºâ¿–t¤ìñº5)Í Û”ÀÁs|L_Á ¯ 3 #] ½ RŠ×boxbackup/contrib/windows/installer/tools/Sync.bat0000775000175000017500000000010611060461151023200 0ustar siretartsiretartcontrol.exe sync echo off ping 192.168.254.254 -n 5 -w 1000 > nul boxbackup/contrib/windows/installer/boxbackup.mpi.in0000775000175000017500000031460611073241547023554 0ustar siretartsiretartarray set info { AccountNo 10005005 AllowLanguageSelection No AppName <%BrandName%> ApplicationID E10C6FD9-E524-28BD-B0AB3588F16C ApplicationURL http://www.boxbackup.org/ AutoFileGroups No AutoRefreshFiles Yes BBVersionNo @box_version@ BrandName {Box Backup} BuildFailureAction {Fail (recommended)} CancelledInstallAction {Rollback and Stop} CleanupCancelledInstall Yes CommandLineFailureAction {Fail (recommended)} Company {Tebuco, Inc. and Ben Summers and Contributors} CompressionLevel 6 CompressionMethod zlib ConfigFileName {<%InstallDir%>\bbackupd.conf} ConfigFileTemplate {<%InstallDir%>\templates\template.conf} Copyright {2003-2008 Tebuco, Inc. and Ben Summers and Contributors} CreateDesktopShortcut No CreateQuickLaunchShortcut No DefaultDirectoryLocation {} DefaultLanguage English EncryptedKeyFilePassword Enter_EncryptedKeys_Password_Here Ext .exe ExtractSolidArchivesOnStartup No Icon {} Image @build_dir@/docs/html/images/bblogo.png IncludeDebugging Yes InstallDirSuffix <%ShortAppName%> InstallPassword {} InstallVersion @box_version@ Language,de No Language,en Yes Language,es No Language,fr No Language,hu Yes Language,it Yes Language,nl Yes Language,pl No Language,pt_br No Language,ru Yes LaunchApplication No PackageDescription {<%BrandName%> Backup Service} PackageLicense {} PackageMaintainer {Tebuco, Inc. and Ben Summers and Contributors} PackageName <%ShortAppName%> PackagePackager {Tebuco, Inc. and Ben Summers and Contributors} PackageRelease <%PatchVersion%> PackageSummary {} PackageVersion <%MajorVersion%>.<%MinorVersion%> PreserveFileAttributes Yes PreserveFilePermissions Yes ProjectID 140B9882-3327-FEA8-13415A62FBB2 ProjectVersion 1.2.9.0 SaveOnlyToplevelDirs No ScriptExt .bat ServiceExeName bbackupd.exe ServiceName <%BrandName%> ShortAppName <%BrandName%> SkipUnusedFileGroups Yes SystemLanguage en_us Theme Modern_Wizard ThemeDir Modern_Wizard ThemeVersion 1 UpgradeApplicationID {} UserInfoAcctNo <%AccountNo%> UserInfoCompany {} UserInfoEmail {} UserInfoName {} UserInfoPhone {} Version @box_version@ ViewReadme No WizardHeight 365 WizardWidth 500 } array set ::InstallJammer::InstallCommandLineOptions { D {{} Prefix No No {} {set the value of an option in the installer}} S {InstallMode Switch No No Silent {run the installer in silent mode}} T {Testing Switch Yes No {} {run installer without installing any files}} Y {InstallMode Switch No No Default {accept all defaults and run the installer}} debug {Debugging Switch Yes No {} {run installer in debug mode}} debugconsole {ShowConsole Switch Yes No {} {run installer with a debug console open}} mode {InstallMode Choice No No {Console Default Silent Standard} {set the mode to run the installer in}} prefix {InstallDir String No No {} {set the installation directory}} test {Testing Switch Yes No {} {run installer without installing any files}} } array set ::InstallJammer::UninstallCommandLineOptions { S {InstallMode Switch No No Silent {run the uninstaller in silent mode}} Y {InstallMode Switch No No Default {accept all defaults and run the uninstaller}} debugconsole {ShowConsole Switch Yes No {} {run uninstaller with a debug console open}} mode {UninstallMode Choice No No {Console Silent Standard} {set the mode to run the uninstaller in}} test {Testing Switch Yes No {} {run uninstaller without uninstalling any files}} } FileGroup ::481451CC-F49C-D389-8645076F595B -setup Install -active Yes -platforms {Windows MacOS-X} -name {Program Files} -parent FileGroups File ::B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 -filemethod {Update files with more recent dates} -type dir -directory <%InstallDir%> -name /home/petjal/doc/teb/cli/bu/installer/win/2.2 -location @client_parcel_dir@ -parent 481451CC-F49C-D389-8645076F595B File ::CDDED10B-2747-DD07-5F9D-42A7FD7BB7E6 -name LICENSE.txt -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::D6E262BC-8A84-B6DB-794B-8FDC8AECB079 -name mgwz.dll -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::E56A0360-7D7F-D99E-E9A4-3C20BC4C2B99 -name mingwm10.dll -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::47602DF7-8463-AB89-E13F-11983610CAA2 -type dir -name tools -location @build_dir@/contrib/windows/installer/tools -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::F7F61231-C340-5CD5-686B-01F521994B0C -name InstallService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::68DAE474-165D-81FE-1396-FDD2E6081B41 -name KillBackupProcess.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::2436C940-3332-13AA-7613-8EE67C35CE9B -name ReloadConfig.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::336DDAC3-F3BA-1117-73D4-11DFEF9E98AB -name RemoveService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::0C15AE46-0FF3-3B7F-FC55-D91EF279DBD3 -name RestartService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::58D97EDE-58F2-15D7-7113-BEE3047F0782 -name StartService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::BE7CDB16-D3FE-30FA-2153-7C0509CD5E78 -name StopService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::73BD5859-FB38-71F8-24BD-BDCF871F9FD3 -name Sync.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::67B3838F-4EF7-2C1C-2E86-78DB8ADD6682 -name ShowUsage.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::2646A97C-C0D9-A29C-E145-C5C371F44938 -name QueryOutputAll.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::2F41E3D2-DA4D-2FCB-B3D5-F04032D17A63 -name QueryOutputCurrent.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::C6430BDD-8A07-B80E-FC0C-426C16EB4187 -name RemoteControl.exe -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 -type dir -name .svn -active 0 -parent 47602DF7-8463-AB89-E13F-11983610CAA2 File ::31429CC4-525E-4E30-9328-4774AFA9F619 -name entries -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 File ::A27BEFB6-1421-4030-8F11-F04316BCE57C -name format -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 File ::C99700CE-1035-498C-9A96-B60835652077 -type dir -name prop-base -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 File ::6FF9DFDE-4BB7-4319-AC85-BF1E88731C1C -name InstallService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::6AAE8600-5CFC-4240-A47F-5CFCF4E571C6 -name KillBackupProcess.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::834C33D3-4B53-4B2D-9380-A05420AEE75F -name QueryOutputAll.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::854AD23A-5DDE-44C4-900C-34F5845E6747 -name QueryOutputCurrent.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::9A587DE7-B17C-4CDF-B92C-0D267E30E361 -name ReloadConfig.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::197AAEFA-C62E-4E79-890F-C2E4C8440239 -name RemoteControl.exe.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::912E0F50-53DD-4483-A4F4-CA69944A5959 -name RemoveService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::BDD695BF-9C7E-4F06-BBCE-EC89536DCF27 -name RestartService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::61615EE4-BF66-40E7-B89A-E6A50B92AF93 -name ShowUsage.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::A6F7C6B7-9759-4B86-9388-4A42E6F7C5C3 -name StartService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::45D3E7F5-277B-4E52-81BA-ED6D2BB441D7 -name StopService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::58966972-8387-4D14-A06E-15AA176633A3 -name Sync.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 File ::2A77AF5B-4761-45B5-A543-6328A7F0F39B -type dir -name props -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 File ::BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 -type dir -name text-base -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 File ::D972D6B2-40E5-40B3-BC06-66B8B7F51B04 -name InstallService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::2F14E4F3-5331-4AC5-93F7-C4748970C7F4 -name KillBackupProcess.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::9F3663B2-8BAA-4EAE-B606-53D5C922E703 -name QueryOutputAll.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::BE9BD4FB-DF44-4F4B-BB55-15285A8566BA -name QueryOutputCurrent.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::3B4E0BF4-7FDD-4903-8D43-76C43F38C6C4 -name ReloadConfig.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::038CEEA0-3F21-48F6-B109-BBE1EF7D3E96 -name RemoteControl.exe.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::0C177E04-DF2D-43AB-8A42-9E7A389AB1D1 -name RemoveService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::9BE16F40-9D1D-4C84-843D-955A44069040 -name RestartService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::4E503A3C-EB42-4870-9849-D508A3D9BAB7 -name ShowUsage.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::131B7D8B-1BEB-456C-8F05-386C2EAFBEAE -name StartService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::D3D0AFC1-CB6C-42D4-8C05-21898505DA40 -name StopService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::C89F78B2-25A7-432B-9D92-DC2AB636CFB5 -name Sync.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 File ::90695C82-0000-4F6A-8FE7-0ABDEAA17CAE -type dir -name tmp -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 File ::7DFF0EE4-7298-4C8C-A5BC-56BBDD81CFC8 -type dir -name prop-base -active 0 -parent 90695C82-0000-4F6A-8FE7-0ABDEAA17CAE File ::4C60E473-119E-4B0B-9B01-56240F24D9D5 -type dir -name props -active 0 -parent 90695C82-0000-4F6A-8FE7-0ABDEAA17CAE File ::E1E25ACC-487B-4191-B8CF-9E7C8C88EA09 -type dir -name text-base -active 0 -parent 90695C82-0000-4F6A-8FE7-0ABDEAA17CAE File ::E7258732-3D21-4E89-AA41-24AA8B8EBF29 -name all-wcprops -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 File ::9CEBA2A0-C68B-48BA-944E-2E8EE9A35D97 -name bbackupctl.exe -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::497483A6-7264-4361-86F2-F2703F719908 -name bbackupd-config -active 0 -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::204358FC-610F-47DF-8928-1D0E39921700 -targetfilename templates/original.conf -name bbackupd.conf -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::98C376E2-A6C3-4B6C-BBD4-F70CAC2E6A7B -name bbackupd.exe -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::5C3EAB34-7CD4-4DF3-9DEB-0FC23A6F5812 -name bbackupquery.exe -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::7633DBC3-EACA-4F9B-9A87-AD3AF0EC298E -name installer.iss -active 0 -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::D3CF86E1-CAFF-4342-8730-463F96EACC39 -targetfilename templates/NotifySysAdmin.original.vbs -name NotifySysAdmin.vbs -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 File ::A702625F-29C7-4CA0-A8F8-E50DBF5C541B -name uninstall.exe -active 0 -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 Component ::4A9C852B-647E-EED5-5482FFBCC2AF -setup Install -active Yes -platforms {Windows MacOS-X} -name {Default Component} -parent Components SetupType ::8202CECC-54A0-9B6C-D24D111BA52E -setup Install -active Yes -platforms {Windows MacOS-X} -name Typical -parent SetupTypes InstallComponent AE3BD5B4-35DE-4240-B79914D43E56 -setup Install -type pane -title {Welcome Screen} -component Welcome -active No -parent StandardInstall InstallComponent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8 -setup Install -type pane -conditions 4EE35849-FAD7-170B-0E45-FA30636467B1 -title {Install Password} -component InstallPassword -command insert -active No -parent StandardInstall Condition 4EE35849-FAD7-170B-0E45-FA30636467B1 -active Yes -parent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8 -title {Password Test Condition} -component PasswordTestCondition -TreeObject::id 4EE35849-FAD7-170B-0E45-FA30636467B1 InstallComponent B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D -setup Install -type action -title {Set Install Password} -component SetInstallPassword -active Yes -parent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8 InstallComponent 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E -setup Install -type pane -title {User Information} -component UserInformation -active Yes -parent StandardInstall InstallComponent 9013E862-8E81-5290-64F9-D8BCD13EC7E5 -setup Install -type pane -title {User Information Phone Email} -component UserInformation -active Yes -parent StandardInstall InstallComponent F8FD4BD6-F1DF-3F8D-B857-98310E4B1143 -setup Install -type pane -title {User Information Account No} -component UserInformation -active Yes -parent StandardInstall InstallComponent 58E1119F-639E-17C9-5D3898F385AA -setup Install -type pane -conditions 84DA7F05-9FB7-CC36-9EC98F8A6826 -title {Select Destination} -component SelectDestination -command insert -active Yes -parent StandardInstall Condition 84DA7F05-9FB7-CC36-9EC98F8A6826 -active Yes -parent 58E1119F-639E-17C9-5D3898F385AA -title {File Permission Condition} -component FilePermissionCondition -TreeObject::id 84DA7F05-9FB7-CC36-9EC98F8A6826 InstallComponent 0FDBA082-90AB-808C-478A-A13E7C525336 -setup Install -type action -title BackupLocationNumber -component ExecuteScript -active Yes -parent 58E1119F-639E-17C9-5D3898F385AA InstallComponent 0047FF40-0139-2A59-AAC0-A44D46D6F5CC -setup Install -type action -title BackupLocationName -component ExecuteScript -active No -parent 58E1119F-639E-17C9-5D3898F385AA InstallComponent 2BB06B72-DE53-2319-B1B8-351CDCBA2008 -setup Install -type action -title AddBackupLocation -component ExecuteScript -active Yes -parent 58E1119F-639E-17C9-5D3898F385AA InstallComponent B506E7DA-E7C4-4D42-8C03-FD27BA16D078 -setup Install -type pane -title {License Agreement} -component License -active Yes -parent StandardInstall InstallComponent B93D2216-1DDB-484C-A9AC-D6C18ED7DE23 -setup Install -type action -conditions {6D9D1ABC-7146-443F-9EE9-205D5CA6C830 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C} -title {Modify Widget} -component ModifyWidget -command insert -active Yes -parent B506E7DA-E7C4-4D42-8C03-FD27BA16D078 Condition 6D9D1ABC-7146-443F-9EE9-205D5CA6C830 -active Yes -parent B93D2216-1DDB-484C-A9AC-D6C18ED7DE23 -title {String Is Condition} -component StringIsCondition -TreeObject::id 6D9D1ABC-7146-443F-9EE9-205D5CA6C830 Condition 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C -active Yes -parent B93D2216-1DDB-484C-A9AC-D6C18ED7DE23 -title {String Is Condition} -component StringIsCondition -TreeObject::id 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C InstallComponent 37E627F2-E04B-AEF2-D566C017A4D6 -setup Install -type pane -title {Copying Files} -component CopyFiles -active Yes -parent StandardInstall InstallComponent 3CFFF099-6122-46DD-9CE4-F5819434AC53 -setup Install -type action -title {Stop running service} -component ExecuteExternalProgram -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent FB697A88-2842-468E-9776-85E84B009340 -setup Install -type action -title {Remove installed service} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 41CDE776-2667-5CEB-312A-FC4C33A83E7F -setup Install -type action -title {Backup File} -component BackupFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 0D93323D-779D-44A8-1E0614E5285D -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 5CA3EA16-E37C-AABE-E576C4636EB0 -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent F5F21749-8B3A-49C6-9138-9C4D6D703D26 -setup Install -type action -title {Unpack Keys} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent FDF68FD6-BEA8-4A74-867D-5139F4D9E793 -setup Install -type action -title Wait -component Wait -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent E56ADFF4-C15E-AEDB-A599-C468AF72C4BB -setup Install -type action -title {Copy File NotifySysAdmin} -component CopyFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent D9F88AC1-3D2D-F6DB-871E-3A0E016770B1 -setup Install -type action -title {Copy File config} -component CopyFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B -setup Install -type action -title {Replace Text In File} -component ReplaceTextInFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64 -setup Install -type action -title {Write Text To File} -component WriteTextToFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 28E76C8B-2605-4739-9FFE-9C2880C17E59 -setup Install -type action -title {Edit config file} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 52F0A238-57E1-A578-2CE4DA177B32 -setup Install -type action -title {Move Forward} -component MoveForward -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 InstallComponent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 -setup Install -type pane -title SetBackupLocations -component CustomBlankPane2 -command reorder -active Yes -parent StandardInstall InstallComponent 614C45B2-7515-780C-E444-7F165CF02DD7 -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent A5B32DA1-B2FE-C1FA-6057-FBC3059EF076 -setup Install -type action -title {Execute Script} -component ExecuteScript -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent F9E38720-6ABA-8B99-2471-496902E4CBC2 -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 362B6D6A-11BC-83CE-AFF6-410D8FBCF54D -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 2E2963BD-DDBD-738D-A910-B7F3F04946F9 -setup Install -type action -title ShowAddAnotherValue -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 93AA298C-B64E-5683-14D2-7B86F7DEFD2C -setup Install -type action -title BackupLocationName -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 3FDB57ED-598D-8A4E-CEF7-D90833305558 -setup Install -type action -title {Backup Directory} -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A -setup Install -type action -title BackupLocationShortName -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 855DE408-060E-3D35-08B5-1D9AB05C2865 -setup Install -type action -title Exclusions -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37 -setup Install -type action -title AddAnother -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent C7762473-273F-E3CA-17E3-65789B14CDB0 -setup Install -type action -title {Write Text To File} -component WriteTextToFile -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent D7FBBEBB-2186-5674-BA87-BB7151859D4E -setup Install -type action -title BackupLocationNumber -component ExecuteScript -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 InstallComponent 49E80443-62DB-1C10-392D-1091AEA5ED88 -setup Install -type action -conditions EB532611-5F30-3C24-66EB-F3826D9054FD -title {Move to Pane} -component MoveToPane -command insert -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 Condition EB532611-5F30-3C24-66EB-F3826D9054FD -active Yes -parent 49E80443-62DB-1C10-392D-1091AEA5ED88 -title {String Is Condition} -component StringIsCondition -TreeObject::id EB532611-5F30-3C24-66EB-F3826D9054FD InstallComponent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266 -setup Install -type pane -title {Click Next to Continue} -component CustomBlankPane2 -active Yes -parent StandardInstall InstallComponent DDBBD8A9-13D7-9509-9202-419E989F60A9 -setup Install -type action -title {Add Widget} -component AddWidget -active No -parent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266 InstallComponent 8E095096-F018-A880-429D-A2177A9B70EA -setup Install -type action -title {Add Widget} -component AddWidget -active No -parent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266 InstallComponent 88A50FD5-480F-19A5-DA74-C915EB0A9765 -setup Install -type action -conditions 5EE78EF7-37CA-D440-3DB5-09136CD566B3 -title {Move to Pane} -component MoveToPane -command insert -active No -parent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266 Condition 5EE78EF7-37CA-D440-3DB5-09136CD566B3 -active Yes -parent 88A50FD5-480F-19A5-DA74-C915EB0A9765 -title {String Is Condition} -component StringIsCondition -TreeObject::id 5EE78EF7-37CA-D440-3DB5-09136CD566B3 InstallComponent 908CE221-5A3D-0A78-24A1-E7C91EBE38D4 -setup Install -type pane -title {Next-Build Config} -component CustomBlankPane2 -active No -parent StandardInstall InstallComponent DA33B826-E633-A845-4646-76DFA78B907B -setup Install -type pane -title {Custom Blank Pane 2} -component CustomBlankPane2 -active Yes -parent StandardInstall InstallComponent 6FEE2889-0338-1D49-60BF-1471F465AB26 -setup Install -type action -title {Write Text To File} -component WriteTextToFile -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B InstallComponent 73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8 -setup Install -type action -title {Copy File} -component CopyFile -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B InstallComponent D23DD94C-E517-7F34-FD59-802CB18AB887 -setup Install -type action -title {Adjust Line Feeds} -component AdjustLineFeeds -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B InstallComponent 7D8E1902-2BC4-80D8-2C18771E7C22 -setup Install -type action -title {Installing service} -component ExecuteExternalProgram -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B InstallComponent 1C14291C-0971-4283-92E9-3808401303F5 -setup Install -type action -title {Starting service} -component ExecuteExternalProgram -active No -parent DA33B826-E633-A845-4646-76DFA78B907B InstallComponent 6C323815-B9AB-FA94-4F5D152EBC51 -setup Install -type pane -title {Setup Complete} -component SetupComplete -active Yes -parent StandardInstall InstallComponent 574198A7-7322-2F5E-02EF185D965C -setup Install -type pane -title {Copying Files} -component CopyFiles -active Yes -parent DefaultInstall InstallComponent 8A761DBD-0640-D98C-9B3AD7672A8F -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 574198A7-7322-2F5E-02EF185D965C InstallComponent 6E70FB1F-6A43-6C23-3242E965A0D0 -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 574198A7-7322-2F5E-02EF185D965C InstallComponent 8E1A5944-5AF5-5906-16D395E386D8 -setup Install -type action -title {Move Forward} -component MoveForward -active Yes -parent 574198A7-7322-2F5E-02EF185D965C InstallComponent 1F0926EE-6884-1330-B4A1DB11C1BF -setup Install -type pane -title {Setup Complete} -component SetupComplete -active Yes -parent DefaultInstall InstallComponent 3B6E2E7C-1A26-27F1-D578E383B128 -setup Install -type action -conditions {13BD88FE-CD71-5AC7-E99C10B6CB28 E02368C5-95B5-03A7-3282740037B0} -title {View Readme Checkbutton} -component AddWidget -command insert -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF Condition 13BD88FE-CD71-5AC7-E99C10B6CB28 -active Yes -parent 3B6E2E7C-1A26-27F1-D578E383B128 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 13BD88FE-CD71-5AC7-E99C10B6CB28 Condition E02368C5-95B5-03A7-3282740037B0 -active Yes -parent 3B6E2E7C-1A26-27F1-D578E383B128 -title {String Is Condition} -component StringIsCondition -TreeObject::id E02368C5-95B5-03A7-3282740037B0 InstallComponent CFFA27AF-A641-E41C-B4A0E3BB3CBB -setup Install -type action -conditions {592F46AE-8CEE-01F3-0BA7EBDCA4F4 793D8178-0F51-7F07-BC5886586D3C} -title {Launch Application Checkbutton} -component AddWidget -command insert -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF Condition 592F46AE-8CEE-01F3-0BA7EBDCA4F4 -active Yes -parent CFFA27AF-A641-E41C-B4A0E3BB3CBB -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 592F46AE-8CEE-01F3-0BA7EBDCA4F4 Condition 793D8178-0F51-7F07-BC5886586D3C -active Yes -parent CFFA27AF-A641-E41C-B4A0E3BB3CBB -title {String Is Condition} -component StringIsCondition -TreeObject::id 793D8178-0F51-7F07-BC5886586D3C InstallComponent 16D53E40-546B-54C3-088B1B5E3BBB -setup Install -type action -conditions {4E643D8A-CA31-018D-57D7053C2CE8 B39C0455-D1B6-7DDC-E2717F83463E} -title {Desktop Shortcut Checkbutton} -component AddWidget -command insert -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF Condition 4E643D8A-CA31-018D-57D7053C2CE8 -active Yes -parent 16D53E40-546B-54C3-088B1B5E3BBB -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 4E643D8A-CA31-018D-57D7053C2CE8 Condition B39C0455-D1B6-7DDC-E2717F83463E -active Yes -parent 16D53E40-546B-54C3-088B1B5E3BBB -title {String Is Condition} -component StringIsCondition -TreeObject::id B39C0455-D1B6-7DDC-E2717F83463E InstallComponent 937C3FDD-FB28-98BD-3DAB276E59ED -setup Install -type action -conditions {6B966959-05D9-DB32-8D9C4AD2A3DF 748D673B-DFE6-5F74-329903ACE4DB 3379F80B-36D6-73DC-6FC1D6223A26} -title {Quick Launch Shortcut Checkbutton} -component AddWidget -command insert -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF Condition 6B966959-05D9-DB32-8D9C4AD2A3DF -active Yes -parent 937C3FDD-FB28-98BD-3DAB276E59ED -title {Platform Condition} -component PlatformCondition -TreeObject::id 6B966959-05D9-DB32-8D9C4AD2A3DF Condition 748D673B-DFE6-5F74-329903ACE4DB -active Yes -parent 937C3FDD-FB28-98BD-3DAB276E59ED -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 748D673B-DFE6-5F74-329903ACE4DB Condition 3379F80B-36D6-73DC-6FC1D6223A26 -active Yes -parent 937C3FDD-FB28-98BD-3DAB276E59ED -title {String Is Condition} -component StringIsCondition -TreeObject::id 3379F80B-36D6-73DC-6FC1D6223A26 InstallComponent 3FE82C17-A3E2-4A57-A563-F80818B00B81 -setup Install -type action -title {Console Ask Yes Or No} -component ConsoleAskYesOrNo -active Yes -parent ConsoleInstall InstallComponent 56EE5149-6AA2-4E0C-8841-F66A2EF9276E -setup Install -type action -conditions 241BBFCE-4EB1-432F-94DD-69D444DDB6C0 -title Exit -component Exit -command insert -active Yes -parent ConsoleInstall Condition 241BBFCE-4EB1-432F-94DD-69D444DDB6C0 -active Yes -parent 56EE5149-6AA2-4E0C-8841-F66A2EF9276E -title {String Is Condition} -component StringIsCondition -TreeObject::id 241BBFCE-4EB1-432F-94DD-69D444DDB6C0 InstallComponent 0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5 -setup Install -type action -conditions BC4EA5FD-50BD-4D6E-953F-5E3EDB957360 -title {Console Get User Input} -component ConsoleGetUserInput -command insert -active Yes -parent ConsoleInstall Condition BC4EA5FD-50BD-4D6E-953F-5E3EDB957360 -active Yes -parent 0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5 -title {File Permission Condition} -component FilePermissionCondition -TreeObject::id BC4EA5FD-50BD-4D6E-953F-5E3EDB957360 InstallComponent B002A311-F8E7-41DE-B039-521391924E5B -setup Install -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleInstall InstallComponent D4FC6EB5-DDEE-4E4A-B8E1-D4B588A7928B -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent ConsoleInstall InstallComponent 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6 -setup Install -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleInstall InstallComponent 6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1 -setup Install -type action -title Exit -component Exit -active Yes -parent ConsoleInstall InstallComponent D8F0AA0F-AD79-C566-15CC508F503B -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent SilentInstall InstallComponent 175CBE81-9EBE-1E21-A91479BEEFAE -setup Install -type action -title Exit -component Exit -active Yes -parent SilentInstall InstallComponent A1DD1DC2-85D7-9BC6-998AC3D4A3A9 -setup Install -type actiongroup -title {Startup Actions} -active Yes -parent ActionGroupsInstall InstallComponent 1F9E8CB8-02C1-0416-1F7445B4147F -setup Install -type action -conditions {3D0D1898-4C65-3E66-F82F56581E87 32F5B0AF-EB83-7A03-D8FAE1ECE473} -title Exit -component Exit -command insert -active Yes -parent A1DD1DC2-85D7-9BC6-998AC3D4A3A9 Condition 3D0D1898-4C65-3E66-F82F56581E87 -active Yes -parent 1F9E8CB8-02C1-0416-1F7445B4147F -title {String Is Condition} -component StringIsCondition -TreeObject::id 3D0D1898-4C65-3E66-F82F56581E87 Condition 32F5B0AF-EB83-7A03-D8FAE1ECE473 -active Yes -parent 1F9E8CB8-02C1-0416-1F7445B4147F -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id 32F5B0AF-EB83-7A03-D8FAE1ECE473 InstallComponent 32DC8FB1-A04B-71AA-EC18496D4BD0 -setup Install -type action -title {Create Install Panes} -component CreateInstallPanes -active Yes -parent A1DD1DC2-85D7-9BC6-998AC3D4A3A9 InstallComponent 198905FB-9FAC-23DE-7422D25B8ECA -setup Install -type actiongroup -title {Install Actions} -active Yes -parent ActionGroupsInstall InstallComponent 4D4A7BF0-7CCE-46E6-BDE5222F82D7 -setup Install -type action -title {Install Selected Files} -component InstallSelectedFiles -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 53588803-6B41-D9FC-A385906A5106 -setup Install -type action -title {Install Uninstaller} -component InstallUninstaller -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 73EA65C1-3BE3-B190-55C3E99F6269 -setup Install -type action -conditions 4EF787E3-0643-DE46-15E64BAF0816 -title {Windows Uninstall Registry} -component AddWindowsUninstallEntry -command insert -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA Condition 4EF787E3-0643-DE46-15E64BAF0816 -active Yes -parent 73EA65C1-3BE3-B190-55C3E99F6269 -title {Platform Condition} -component PlatformCondition -TreeObject::id 4EF787E3-0643-DE46-15E64BAF0816 InstallComponent 39B2B666-78D8-75E6-6EA071594D34 -setup Install -type action -conditions 18C00430-D6B1-151F-307762B3A045 -title {Uninstall Shortcut} -component InstallWindowsShortcut -command insert -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA Condition 18C00430-D6B1-151F-307762B3A045 -active Yes -parent 39B2B666-78D8-75E6-6EA071594D34 -title {Platform Condition} -component PlatformCondition -TreeObject::id 18C00430-D6B1-151F-307762B3A045 InstallComponent 6652193C-5D4B-44B6-ABC6-D6E96D89E5DC -setup Install -type action -title {Install Program Folder Shortcut} -component InstallProgramFolderShortcut -active No -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 9D101299-B80C-441B-8685-6E3AC61808E8 -setup Install -type action -title {RemoteControl Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E -setup Install -type action -title {stopservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7 -setup Install -type action -title {Install Backup Service} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 25AA533E-02FC-47D9-9273-25266B8FA1F9 -setup Install -type action -title {Remove Backup Service} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent CDD84DE3-C970-458F-9162-1A3CE0AA716B -setup Install -type action -title {startservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent B5DFEC63-92A9-4686-909E-0CE78A7069D6 -setup Install -type action -title {restartservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent C0452595-F3EB-43AD-BCA2-661437584636 -setup Install -type action -title {editconfig Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 1AF5CD58-65C0-49CB-9A9D-994816CF414E -setup Install -type action -title {QueryUpload Shortcut} -component InstallProgramFolderShortcut -active No -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D -setup Install -type action -title {killbackupprocess Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968 -setup Install -type action -title {reloadconfig Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 6F61CDA8-30C9-454F-82A3-9987E1203079 -setup Install -type action -title {sync Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 9A663209-495B-ED16-09BE-457B61148022 -setup Install -type action -title QueryCurrent -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent C0AF7C05-A31A-8376-BCB9-BA8B3A666252 -setup Install -type action -title SafeQueryAll -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent 32B08FB1-99DF-234E-8BAF-333E80AAC9F5 -setup Install -type action -title Usage -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA InstallComponent FEFD090D-C133-BC95-B3564F693CD3 -setup Install -type actiongroup -title {Finish Actions} -active Yes -parent ActionGroupsInstall InstallComponent DECC120D-6904-7F17-45A49184A5A3 -setup Install -type action -conditions {E44CFF46-6302-C518-B9C30D2E43F7 B0AA6839-AAB6-A602-C0E4ECA2E4FF} -title {Install Desktop Shortcut} -component InstallDesktopShortcut -command insert -active No -parent FEFD090D-C133-BC95-B3564F693CD3 Condition E44CFF46-6302-C518-B9C30D2E43F7 -active Yes -parent DECC120D-6904-7F17-45A49184A5A3 -title {String Is Condition} -component StringIsCondition -TreeObject::id E44CFF46-6302-C518-B9C30D2E43F7 Condition B0AA6839-AAB6-A602-C0E4ECA2E4FF -active Yes -parent DECC120D-6904-7F17-45A49184A5A3 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id B0AA6839-AAB6-A602-C0E4ECA2E4FF InstallComponent 7B770A07-A785-5215-956FA82CF14E -setup Install -type action -conditions {6F94698F-0839-3ABF-0CF2DF05A4C8 738DD098-7E3B-BC89-875CDB93CBE2 8C866252-8760-9B08-FE569C25B60D} -title {Install Quick Launch Shortcut} -component InstallWindowsShortcut -command insert -active No -parent FEFD090D-C133-BC95-B3564F693CD3 Condition 6F94698F-0839-3ABF-0CF2DF05A4C8 -active Yes -parent 7B770A07-A785-5215-956FA82CF14E -title {String Is Condition} -component StringIsCondition -TreeObject::id 6F94698F-0839-3ABF-0CF2DF05A4C8 Condition 738DD098-7E3B-BC89-875CDB93CBE2 -active Yes -parent 7B770A07-A785-5215-956FA82CF14E -title {Platform Condition} -component PlatformCondition -TreeObject::id 738DD098-7E3B-BC89-875CDB93CBE2 Condition 8C866252-8760-9B08-FE569C25B60D -active Yes -parent 7B770A07-A785-5215-956FA82CF14E -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 8C866252-8760-9B08-FE569C25B60D InstallComponent C105AAAE-7C16-2C9E-769FE4535B60 -setup Install -type action -conditions {2583A547-11DE-1C27-B6D04B023CC0 A6E1B027-A1B4-5848-4F868D028D00 0357FAE9-FCFD-26D8-6541D810CD61} -title {View Readme Window} -component TextWindow -command insert -active No -parent FEFD090D-C133-BC95-B3564F693CD3 Condition 2583A547-11DE-1C27-B6D04B023CC0 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {String Is Condition} -component StringIsCondition -TreeObject::id 2583A547-11DE-1C27-B6D04B023CC0 Condition A6E1B027-A1B4-5848-4F868D028D00 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {String Is Condition} -component StringIsCondition -TreeObject::id A6E1B027-A1B4-5848-4F868D028D00 Condition 0357FAE9-FCFD-26D8-6541D810CD61 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 0357FAE9-FCFD-26D8-6541D810CD61 InstallComponent C33D74B2-26FA-16F5-433A10C6A747 -setup Install -type action -conditions {CC4337CC-F3B5-757C-DFCF5D1D365A 795EE61F-6C0D-4A8B-93E02AA3894A 1528F4F0-145C-A48D-A8526DBB6289} -title {Launch Application} -component ExecuteExternalProgram -command insert -active No -parent FEFD090D-C133-BC95-B3564F693CD3 Condition CC4337CC-F3B5-757C-DFCF5D1D365A -active Yes -parent C33D74B2-26FA-16F5-433A10C6A747 -title {String Is Condition} -component StringIsCondition -TreeObject::id CC4337CC-F3B5-757C-DFCF5D1D365A Condition 795EE61F-6C0D-4A8B-93E02AA3894A -active Yes -parent C33D74B2-26FA-16F5-433A10C6A747 -title {String Is Condition} -component StringIsCondition -TreeObject::id 795EE61F-6C0D-4A8B-93E02AA3894A Condition 1528F4F0-145C-A48D-A8526DBB6289 -active Yes -parent C33D74B2-26FA-16F5-433A10C6A747 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 1528F4F0-145C-A48D-A8526DBB6289 InstallComponent E23AC50D-7CFB-800E-A99C6F4068F8 -setup Install -type actiongroup -title {Cancel Actions} -active Yes -parent ActionGroupsInstall InstallComponent 3B8CDC8E-1239-D2E9-DF4CA6B1756D -setup Uninstall -type pane -title Uninstall -component Uninstall -active Yes -parent StandardUninstall InstallComponent 19ADBDDB-1690-4A57-913E32A026C4 -setup Uninstall -type action -title {Modify Widget} -component ModifyWidget -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D InstallComponent 7A983CD8-302C-4942-BE59-525C5B5FA2F2 -setup Uninstall -type action -title {Stop Backup Process} -component ExecuteExternalProgram -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D InstallComponent E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1 -setup Uninstall -type action -title {Stop Service} -component ExecuteExternalProgram -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D InstallComponent B4D31D1E-ADB1-DE8F-18EB7294DDA8 -setup Uninstall -type action -title {Remove Service} -component ExecuteExternalProgram -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D InstallComponent D55BA4AF-E73B-60D1-E26F79175227 -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D InstallComponent 69FD7409-5E2A-143B-DABD1C3B1E67 -setup Uninstall -type action -conditions {96A68CAC-9ED7-806C-086B104720FD E161F216-E597-B340-C1A71C476E2C} -title {Uninstall Leftover Files} -component UninstallLeftoverFiles -command insert -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D Condition 96A68CAC-9ED7-806C-086B104720FD -active Yes -parent 69FD7409-5E2A-143B-DABD1C3B1E67 -title {String Is Condition} -component StringIsCondition -TreeObject::id 96A68CAC-9ED7-806C-086B104720FD Condition E161F216-E597-B340-C1A71C476E2C -active Yes -parent 69FD7409-5E2A-143B-DABD1C3B1E67 -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id E161F216-E597-B340-C1A71C476E2C InstallComponent 05060263-E852-87AB-8D0F2954CAA6 -setup Uninstall -type action -title {Move Forward} -component MoveForward -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D InstallComponent 41D3E165-C263-5F80-0FEEC0AEE47A -setup Uninstall -type pane -conditions EB2B31A1-C111-3582-0C8A5656692A -title {Uninstall Details} -component UninstallDetails -command insert -active Yes -parent StandardUninstall Condition EB2B31A1-C111-3582-0C8A5656692A -active Yes -parent 41D3E165-C263-5F80-0FEEC0AEE47A -title {String Is Condition} -component StringIsCondition -TreeObject::id EB2B31A1-C111-3582-0C8A5656692A InstallComponent 3D33AA8C-0037-204B-39A339FD38BD -setup Uninstall -type pane -title {Uninstall Complete} -component UninstallComplete -active Yes -parent StandardUninstall InstallComponent 49E59F91-27F7-46D1-A1C1-19865C2392D3 -setup Uninstall -type action -title {Console Ask Yes Or No} -component ConsoleAskYesOrNo -active Yes -parent ConsoleUninstall InstallComponent ADA6EB2F-8820-4366-BBEF-ED1335B7F828 -setup Uninstall -type action -conditions 87DE6D78-81E1-495B-A214-B3FF3E7E5614 -title Exit -component Exit -command insert -active Yes -parent ConsoleUninstall Condition 87DE6D78-81E1-495B-A214-B3FF3E7E5614 -active Yes -parent ADA6EB2F-8820-4366-BBEF-ED1335B7F828 -title {String Is Condition} -component StringIsCondition -TreeObject::id 87DE6D78-81E1-495B-A214-B3FF3E7E5614 InstallComponent B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174 -setup Uninstall -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleUninstall InstallComponent 3C7130B3-3206-403D-B09E-59D4A758FBAD -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent ConsoleUninstall InstallComponent 20CBDBEA-2217-457B-8D98-D692C4F591E9 -setup Uninstall -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleUninstall InstallComponent 7F85263E-CAE2-46BA-AAC0-6B89D20FD2DE -setup Uninstall -type action -title Exit -component Exit -active Yes -parent ConsoleUninstall InstallComponent 17D8BA8E-5992-AA5C-F5ECB73A3433 -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent SilentUninstall InstallComponent D3D73C76-D9D3-07DA-63D4163A44BE -setup Uninstall -type action -title Exit -component Exit -active Yes -parent SilentUninstall InstallComponent 848844B5-6103-9343-8B731B0BE4E0 -setup Uninstall -type actiongroup -title {Startup Actions} -active Yes -parent ActionGroupsUninstall InstallComponent 97ACF525-C075-8635-E019202A83D8 -setup Uninstall -type action -conditions {DFFF91A9-2CA5-6ABE-8474D814AF88 4ACB0B47-42B3-2B3A-BFE9AA4EC707} -title Exit -component Exit -command insert -active Yes -parent 848844B5-6103-9343-8B731B0BE4E0 Condition DFFF91A9-2CA5-6ABE-8474D814AF88 -active Yes -parent 97ACF525-C075-8635-E019202A83D8 -title {String Is Condition} -component StringIsCondition -TreeObject::id DFFF91A9-2CA5-6ABE-8474D814AF88 Condition 4ACB0B47-42B3-2B3A-BFE9AA4EC707 -active Yes -parent 97ACF525-C075-8635-E019202A83D8 -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id 4ACB0B47-42B3-2B3A-BFE9AA4EC707 InstallComponent F4024A3E-9A6D-2726-5E0CFFA93054 -setup Uninstall -type actiongroup -title {Uninstall Actions} -active Yes -parent ActionGroupsUninstall InstallComponent 39D7394E-04E9-CA70-0034DB830BFE -setup Uninstall -type action -title {Uninstall Selected Files} -component UninstallSelectedFiles -active Yes -parent F4024A3E-9A6D-2726-5E0CFFA93054 InstallComponent 39270FD8-932E-6132-7EF795ED9B93 -setup Uninstall -type actiongroup -title {Finish Actions} -active Yes -parent ActionGroupsUninstall InstallComponent 905DA2E9-988C-2F27-BB1F5F274AC9 -setup Uninstall -type actiongroup -title {Cancel Actions} -active Yes -parent ActionGroupsUninstall array set Properties { 0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Active No 0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Comment {set BackupLocationName "BackupLocation_${BackupLocationNumber}"} 0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Conditions {0 conditions} 0047FF40-0139-2A59-AAC0-A44D46D6F5CC,ExecuteAction {Before Next Pane is Displayed} 0047FF40-0139-2A59-AAC0-A44D46D6F5CC,ResultVirtualText BackupLocationName 0047FF40-0139-2A59-AAC0-A44D46D6F5CC,TclScript {set BackupLocationName "BackupLocation_${BackupLocationNumber}"} 0357FAE9-FCFD-26D8-6541D810CD61,CheckCondition {Before Action is Executed} 0357FAE9-FCFD-26D8-6541D810CD61,Filename <%ProgramReadme%> 05060263-E852-87AB-8D0F2954CAA6,Conditions {0 conditions} 0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,Prompt <%ConsoleSelectDestinationText%> 0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,VirtualText InstallDir 0D93323D-779D-44A8-1E0614E5285D,Conditions {0 conditions} 0D93323D-779D-44A8-1E0614E5285D,State disabled 0D93323D-779D-44A8-1E0614E5285D,Widget {Back Button;Next Button} 0FDBA082-90AB-808C-478A-A13E7C525336,Conditions {0 conditions} 0FDBA082-90AB-808C-478A-A13E7C525336,ExecuteAction {Before Next Pane is Displayed} 0FDBA082-90AB-808C-478A-A13E7C525336,ResultVirtualText BackupLocationNumber 0FDBA082-90AB-808C-478A-A13E7C525336,TclScript {set BackupLocationNumber 1} 13BD88FE-CD71-5AC7-E99C10B6CB28,CheckCondition {Before Action is Executed} 13BD88FE-CD71-5AC7-E99C10B6CB28,Filename <%ProgramReadme%> 1528F4F0-145C-A48D-A8526DBB6289,CheckCondition {Before Action is Executed} 1528F4F0-145C-A48D-A8526DBB6289,Filename <%ProgramExecutable%> 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,Alias {Stop Backup Windows Process} 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,Conditions {0 conditions} 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,FileName <%ShortAppName%>-program-killbackupprocess 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,ShortcutName {Stop backup process} 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,TargetFileName <%InstallDir%>/tools/KillBackupProcess.bat 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,WorkingDirectory <%InstallDir%> 16D53E40-546B-54C3-088B1B5E3BBB,Background white 16D53E40-546B-54C3-088B1B5E3BBB,Conditions {2 conditions} 16D53E40-546B-54C3-088B1B5E3BBB,Text,subst 1 16D53E40-546B-54C3-088B1B5E3BBB,Type checkbutton 16D53E40-546B-54C3-088B1B5E3BBB,VirtualText CreateDesktopShortcut 16D53E40-546B-54C3-088B1B5E3BBB,X 185 16D53E40-546B-54C3-088B1B5E3BBB,Y 180 175CBE81-9EBE-1E21-A91479BEEFAE,ExitType Finish 17D8BA8E-5992-AA5C-F5ECB73A3433,Action {Uninstall Actions} 17D8BA8E-5992-AA5C-F5ECB73A3433,Conditions {0 conditions} 18C00430-D6B1-151F-307762B3A045,CheckCondition {Before Action is Executed} 18C00430-D6B1-151F-307762B3A045,Platform Windows 198905FB-9FAC-23DE-7422D25B8ECA,Alias {Install Actions} 198905FB-9FAC-23DE-7422D25B8ECA,Conditions {0 conditions} 19ADBDDB-1690-4A57-913E32A026C4,Conditions {0 conditions} 19ADBDDB-1690-4A57-913E32A026C4,State disabled 19ADBDDB-1690-4A57-913E32A026C4,Widget {NextButton; CancelButton} 1AF5CD58-65C0-49CB-9A9D-994816CF414E,Active No 1AF5CD58-65C0-49CB-9A9D-994816CF414E,Alias {Upload File Listing} 1AF5CD58-65C0-49CB-9A9D-994816CF414E,Comment {Upload list of backed up files for Tech Support purposes. May be blocked by firewalls.} 1AF5CD58-65C0-49CB-9A9D-994816CF414E,Conditions {0 conditions} 1AF5CD58-65C0-49CB-9A9D-994816CF414E,FileName <%ShortAppName%>-program-TebucoSafeQuerypload 1AF5CD58-65C0-49CB-9A9D-994816CF414E,ShortcutName {Upload Filelisting to TebucoSafe for review} 1AF5CD58-65C0-49CB-9A9D-994816CF414E,TargetFileName <%InstallDir%>/tools/TebucoSafeQueryUpload.bat 1AF5CD58-65C0-49CB-9A9D-994816CF414E,WorkingDirectory <%InstallDir%> 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Active Yes 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,BackButton,subst 1 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,CancelButton,subst 1 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Caption,subst 1 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,CompanyLabel,subst 0 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Conditions {0 conditions} 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Message,subst 1 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,NextButton,subst 1 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Subtitle,subst 1 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Title,subst 1 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,UserNameLabel,subst 0 1C14291C-0971-4283-92E9-3808401303F5,Active No 1C14291C-0971-4283-92E9-3808401303F5,Comment {Don't start it yet, need to install keys by hand.} 1C14291C-0971-4283-92E9-3808401303F5,Conditions {0 conditions} 1C14291C-0971-4283-92E9-3808401303F5,ProgramCommandLine {net start <%ServiceName%>} 1C14291C-0971-4283-92E9-3808401303F5,WorkingDirectory <%InstallDir%> 1F0926EE-6884-1330-B4A1DB11C1BF,BackButton,subst 1 1F0926EE-6884-1330-B4A1DB11C1BF,CancelButton,subst 1 1F0926EE-6884-1330-B4A1DB11C1BF,Caption,subst 1 1F0926EE-6884-1330-B4A1DB11C1BF,Message,subst 1 1F0926EE-6884-1330-B4A1DB11C1BF,NextButton,subst 1 1F9E8CB8-02C1-0416-1F7445B4147F,Comment {Ask the user if they want to proceed with the install.} 1F9E8CB8-02C1-0416-1F7445B4147F,Conditions {2 conditions} 20CBDBEA-2217-457B-8D98-D692C4F591E9,Message,subst 1 241BBFCE-4EB1-432F-94DD-69D444DDB6C0,CheckCondition {Before Action is Executed} 241BBFCE-4EB1-432F-94DD-69D444DDB6C0,Operator false 241BBFCE-4EB1-432F-94DD-69D444DDB6C0,String <%Answer%> 2583A547-11DE-1C27-B6D04B023CC0,CheckCondition {Before Action is Executed} 2583A547-11DE-1C27-B6D04B023CC0,Operator false 2583A547-11DE-1C27-B6D04B023CC0,String <%SilentMode%> 25AA533E-02FC-47D9-9273-25266B8FA1F9,Alias {Remove Backup Service} 25AA533E-02FC-47D9-9273-25266B8FA1F9,Comment {Remove the Backup Windows Service} 25AA533E-02FC-47D9-9273-25266B8FA1F9,Conditions {0 conditions} 25AA533E-02FC-47D9-9273-25266B8FA1F9,FileName <%ShortAppName%>-program-removeService 25AA533E-02FC-47D9-9273-25266B8FA1F9,ShortcutName {Remove Service} 25AA533E-02FC-47D9-9273-25266B8FA1F9,TargetFileName <%InstallDir%>/tools/RemoveService.bat 25AA533E-02FC-47D9-9273-25266B8FA1F9,WorkingDirectory <%InstallDir%> 28E76C8B-2605-4739-9FFE-9C2880C17E59,Active No 28E76C8B-2605-4739-9FFE-9C2880C17E59,Conditions {0 conditions} 28E76C8B-2605-4739-9FFE-9C2880C17E59,ProgramCommandLine {notepad <%ConfigFileName%>} 28E76C8B-2605-4739-9FFE-9C2880C17E59,WorkingDirectory <%InstallDir%> 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,BackButton,subst 1 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,CancelButton,subst 1 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Caption,subst 1 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Conditions {1 condition} 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Message,subst 1 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,NextButton,subst 1 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Subtitle,subst 1 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Title,subst 1 2BB06B72-DE53-2319-B1B8-351CDCBA2008,Conditions {0 conditions} 2BB06B72-DE53-2319-B1B8-351CDCBA2008,ExecuteAction {Before Next Pane is Displayed} 2BB06B72-DE53-2319-B1B8-351CDCBA2008,ResultVirtualText AddBackupLocation 2BB06B72-DE53-2319-B1B8-351CDCBA2008,TclScript {set AddBackupLocation no} 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message,subst 1 2E2963BD-DDBD-738D-A910-B7F3F04946F9,Conditions {0 conditions} 2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text,subst 1 2E2963BD-DDBD-738D-A910-B7F3F04946F9,Value <%AddBackupLocation%> 2E2963BD-DDBD-738D-A910-B7F3F04946F9,X 400 2E2963BD-DDBD-738D-A910-B7F3F04946F9,Y 70 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,AppendNewline No 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Comment {.conf doesn't exist yet} 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Conditions {0 conditions} 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,FileOpenAction {Append to file} 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Files <%ConfigFileTemplate%> 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite,subst 1 32B08FB1-99DF-234E-8BAF-333E80AAC9F5,Conditions {0 conditions} 32B08FB1-99DF-234E-8BAF-333E80AAC9F5,FileName <%ShortAppName%>-program-Usage 32B08FB1-99DF-234E-8BAF-333E80AAC9F5,ShortcutName Usage 32B08FB1-99DF-234E-8BAF-333E80AAC9F5,TargetFileName <%InstallDir%>/tools/ShowUsage.bat 32B08FB1-99DF-234E-8BAF-333E80AAC9F5,WorkingDirectory <%InstallDir%> 32DC8FB1-A04B-71AA-EC18496D4BD0,Conditions {0 conditions} 32F5B0AF-EB83-7A03-D8FAE1ECE473,CheckCondition {Before Action is Executed} 32F5B0AF-EB83-7A03-D8FAE1ECE473,Message,subst 1 32F5B0AF-EB83-7A03-D8FAE1ECE473,Title,subst 1 32F5B0AF-EB83-7A03-D8FAE1ECE473,TrueValue No 3379F80B-36D6-73DC-6FC1D6223A26,CheckCondition {Before Action is Executed} 3379F80B-36D6-73DC-6FC1D6223A26,Operator false 3379F80B-36D6-73DC-6FC1D6223A26,String <%InstallStopped%> 362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Active No 362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Conditions {0 conditions} 362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,ResultVirtualText BackupLocationExclusions 362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,TclScript {set BackupLocationExclusions ""} 37E627F2-E04B-AEF2-D566C017A4D6,BackButton,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,CancelButton,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,Caption,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,Conditions {0 conditions} 37E627F2-E04B-AEF2-D566C017A4D6,FileLabel,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,Message,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,NextButton,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,ProgressValue,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,Subtitle,subst 1 37E627F2-E04B-AEF2-D566C017A4D6,Title,subst 1 39270FD8-932E-6132-7EF795ED9B93,Alias {Finish Actions} 39270FD8-932E-6132-7EF795ED9B93,Conditions {0 conditions} 39B2B666-78D8-75E6-6EA071594D34,Conditions {1 condition} 39B2B666-78D8-75E6-6EA071594D34,ShortcutName {Uninstall <%BrandName%>} 39B2B666-78D8-75E6-6EA071594D34,TargetFileName <%Uninstaller%> 39B2B666-78D8-75E6-6EA071594D34,WorkingDirectory <%InstallDir%> 39D7394E-04E9-CA70-0034DB830BFE,Conditions {0 conditions} 3B6E2E7C-1A26-27F1-D578E383B128,Background white 3B6E2E7C-1A26-27F1-D578E383B128,Conditions {2 conditions} 3B6E2E7C-1A26-27F1-D578E383B128,Text,subst 1 3B6E2E7C-1A26-27F1-D578E383B128,Type checkbutton 3B6E2E7C-1A26-27F1-D578E383B128,VirtualText ViewReadme 3B6E2E7C-1A26-27F1-D578E383B128,X 185 3B6E2E7C-1A26-27F1-D578E383B128,Y 140 3B8CDC8E-1239-D2E9-DF4CA6B1756D,BackButton,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,CancelButton,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,Caption,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,Conditions {0 conditions} 3B8CDC8E-1239-D2E9-DF4CA6B1756D,FileValue,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,Message,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,NextButton,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,ProgressValue,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,Subtitle,subst 1 3B8CDC8E-1239-D2E9-DF4CA6B1756D,Title,subst 1 3C7130B3-3206-403D-B09E-59D4A758FBAD,Action {Uninstall Actions} 3CFFF099-6122-46DD-9CE4-F5819434AC53,Conditions {0 conditions} 3CFFF099-6122-46DD-9CE4-F5819434AC53,IgnoreErrors Yes 3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgramCommandLine {net stop <%ServiceName%>} 3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgressiveOutputWidget Message 3CFFF099-6122-46DD-9CE4-F5819434AC53,WorkingDirectory <%Temp%> 3D0D1898-4C65-3E66-F82F56581E87,CheckCondition {Before Action is Executed} 3D0D1898-4C65-3E66-F82F56581E87,Operator false 3D0D1898-4C65-3E66-F82F56581E87,String <%SilentMode%> 3D33AA8C-0037-204B-39A339FD38BD,BackButton,subst 1 3D33AA8C-0037-204B-39A339FD38BD,CancelButton,subst 1 3D33AA8C-0037-204B-39A339FD38BD,Caption,subst 1 3D33AA8C-0037-204B-39A339FD38BD,Conditions {0 conditions} 3D33AA8C-0037-204B-39A339FD38BD,Message,subst 1 3D33AA8C-0037-204B-39A339FD38BD,NextButton,subst 1 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Active Yes 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Alias SetBackupLocations 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,BackButton,subst 1 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,CancelButton,subst 1 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Caption,subst 1 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Conditions {0 conditions} 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Message,subst 1 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,NextButton,subst 1 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Subtitle,subst 1 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Title,subst 1 3FDB57ED-598D-8A4E-CEF7-D90833305558,Conditions {0 conditions} 3FDB57ED-598D-8A4E-CEF7-D90833305558,LabelSide left 3FDB57ED-598D-8A4E-CEF7-D90833305558,Text,subst 1 3FDB57ED-598D-8A4E-CEF7-D90833305558,Type {browse entry} 3FDB57ED-598D-8A4E-CEF7-D90833305558,VirtualText BackupLocationPath 3FDB57ED-598D-8A4E-CEF7-D90833305558,Y 70 3FE82C17-A3E2-4A57-A563-F80818B00B81,Default Yes 3FE82C17-A3E2-4A57-A563-F80818B00B81,Prompt <%InstallStartupText%> 41CDE776-2667-5CEB-312A-FC4C33A83E7F,Conditions {0 conditions} 41CDE776-2667-5CEB-312A-FC4C33A83E7F,Files {*/*.conf;*/*.txt;*/*.pem;*/*.raw;*/*.exe;*/*.bat;*/*.dll} 41CDE776-2667-5CEB-312A-FC4C33A83E7F,RenameFiles Yes 41D3E165-C263-5F80-0FEEC0AEE47A,BackButton,subst 1 41D3E165-C263-5F80-0FEEC0AEE47A,CancelButton,subst 1 41D3E165-C263-5F80-0FEEC0AEE47A,Caption,subst 1 41D3E165-C263-5F80-0FEEC0AEE47A,Conditions {1 condition} 41D3E165-C263-5F80-0FEEC0AEE47A,Message,subst 1 41D3E165-C263-5F80-0FEEC0AEE47A,NextButton,subst 1 41D3E165-C263-5F80-0FEEC0AEE47A,Subtitle,subst 1 41D3E165-C263-5F80-0FEEC0AEE47A,Text,subst 1 41D3E165-C263-5F80-0FEEC0AEE47A,Title,subst 1 481451CC-F49C-D389-8645076F595B,Destination <%InstallDir%> 481451CC-F49C-D389-8645076F595B,FileSize 15288767 481451CC-F49C-D389-8645076F595B,Name {Program Files} 49E59F91-27F7-46D1-A1C1-19865C2392D3,Default Yes 49E59F91-27F7-46D1-A1C1-19865C2392D3,Prompt <%UninstallStartupText%> 49E80443-62DB-1C10-392D-1091AEA5ED88,Conditions {1 condition} 49E80443-62DB-1C10-392D-1091AEA5ED88,ExecuteAction {Before Next Pane is Displayed} 49E80443-62DB-1C10-392D-1091AEA5ED88,Pane 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 4A9C852B-647E-EED5-5482FFBCC2AF,Description,subst 1 4A9C852B-647E-EED5-5482FFBCC2AF,DisplayName,subst 1 4A9C852B-647E-EED5-5482FFBCC2AF,FileGroups 481451CC-F49C-D389-8645076F595B 4A9C852B-647E-EED5-5482FFBCC2AF,Name {Default Component} 4A9C852B-647E-EED5-5482FFBCC2AF,RequiredComponent Yes 4ACB0B47-42B3-2B3A-BFE9AA4EC707,CheckCondition {Before Action is Executed} 4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message,subst 1 4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title,subst 1 4ACB0B47-42B3-2B3A-BFE9AA4EC707,TrueValue No 4D4A7BF0-7CCE-46E6-BDE5222F82D7,Conditions {0 conditions} 4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFilePercentage Yes 4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFileText Yes 4E643D8A-CA31-018D-57D7053C2CE8,CheckCondition {Before Action is Executed} 4E643D8A-CA31-018D-57D7053C2CE8,Filename <%ProgramExecutable%> 4EE35849-FAD7-170B-0E45-FA30636467B1,CheckCondition {Before Next Pane is Displayed} 4EE35849-FAD7-170B-0E45-FA30636467B1,EncryptedPassword <%InstallPasswordEncrypted%> 4EE35849-FAD7-170B-0E45-FA30636467B1,FailureFocus {Password Entry} 4EE35849-FAD7-170B-0E45-FA30636467B1,FailureMessage <%PasswordIncorrectText%> 4EE35849-FAD7-170B-0E45-FA30636467B1,UnencryptedPassword <%InstallPassword%> 4EF787E3-0643-DE46-15E64BAF0816,CheckCondition {Before Action is Executed} 4EF787E3-0643-DE46-15E64BAF0816,Platform Windows 52F0A238-57E1-A578-2CE4DA177B32,Conditions {0 conditions} 53588803-6B41-D9FC-A385906A5106,Conditions {0 conditions} 574198A7-7322-2F5E-02EF185D965C,BackButton,subst 1 574198A7-7322-2F5E-02EF185D965C,CancelButton,subst 1 574198A7-7322-2F5E-02EF185D965C,Caption,subst 1 574198A7-7322-2F5E-02EF185D965C,Conditions {0 conditions} 574198A7-7322-2F5E-02EF185D965C,FileLabel,subst 1 574198A7-7322-2F5E-02EF185D965C,Message,subst 1 574198A7-7322-2F5E-02EF185D965C,NextButton,subst 1 574198A7-7322-2F5E-02EF185D965C,ProgressValue,subst 1 574198A7-7322-2F5E-02EF185D965C,Subtitle,subst 1 574198A7-7322-2F5E-02EF185D965C,Title,subst 1 58E1119F-639E-17C9-5D3898F385AA,BackButton,subst 1 58E1119F-639E-17C9-5D3898F385AA,BrowseButton,subst 1 58E1119F-639E-17C9-5D3898F385AA,BrowseText,subst 1 58E1119F-639E-17C9-5D3898F385AA,CancelButton,subst 1 58E1119F-639E-17C9-5D3898F385AA,Caption,subst 1 58E1119F-639E-17C9-5D3898F385AA,Conditions {1 condition} 58E1119F-639E-17C9-5D3898F385AA,DestinationLabel,subst 0 58E1119F-639E-17C9-5D3898F385AA,Message,subst 1 58E1119F-639E-17C9-5D3898F385AA,NextButton,subst 1 58E1119F-639E-17C9-5D3898F385AA,Subtitle,subst 1 58E1119F-639E-17C9-5D3898F385AA,Title,subst 1 592F46AE-8CEE-01F3-0BA7EBDCA4F4,CheckCondition {Before Action is Executed} 592F46AE-8CEE-01F3-0BA7EBDCA4F4,Filename <%ProgramExecutable%> 5CA3EA16-E37C-AABE-E576C4636EB0,Action {Install Actions} 5CA3EA16-E37C-AABE-E576C4636EB0,Conditions {0 conditions} 5EE78EF7-37CA-D440-3DB5-09136CD566B3,CheckCondition {Before Action is Executed} 5EE78EF7-37CA-D440-3DB5-09136CD566B3,String <%AddBackupLocation%> 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,Conditions {0 conditions} 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,Files {<%ConfigFileTemplate%>;*/*.bat;<%InstallDir%>/*.vbs} 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,LineFeed Windows 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,StringMap {"@@CUSTOMERCOMPANY@@" <%UserInfoCompany%> "@@BRANDNAME@@" <%BrandName%> "@@SERVICENAME@@" <%ServiceName%> "@@CUSTOMERUSERNAME@@" <%UserInfoName%> "@@CUSTOMERUSERPHONE@@" <%UserInfoPhone%> "@@CUSTOMERUSEREMAIL@@" <%UserInfoEmail%> "@@ACCTNO@@" <%UserInfoAcctNo%> "@@INSTALLDIR@@" <%InstallDir%>} 614C45B2-7515-780C-E444-7F165CF02DD7,Active No 614C45B2-7515-780C-E444-7F165CF02DD7,Conditions {0 conditions} 614C45B2-7515-780C-E444-7F165CF02DD7,ResultVirtualText BackupLocationShortName 614C45B2-7515-780C-E444-7F165CF02DD7,TclScript {set BackupLocationShortName ""} 6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Active No 6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Comment {PJ removed. Is this the one at the top leve?} 6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Conditions {0 conditions} 69FD7409-5E2A-143B-DABD1C3B1E67,Conditions {2 conditions} 6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1,ExitType Finish 6B966959-05D9-DB32-8D9C4AD2A3DF,CheckCondition {Before Action is Executed} 6B966959-05D9-DB32-8D9C4AD2A3DF,Platform Windows 6C323815-B9AB-FA94-4F5D152EBC51,BackButton,subst 1 6C323815-B9AB-FA94-4F5D152EBC51,CancelButton,subst 1 6C323815-B9AB-FA94-4F5D152EBC51,Caption,subst 1 6C323815-B9AB-FA94-4F5D152EBC51,Conditions {0 conditions} 6C323815-B9AB-FA94-4F5D152EBC51,Message,subst 1 6C323815-B9AB-FA94-4F5D152EBC51,NextButton,subst 1 6D9D1ABC-7146-443F-9EE9-205D5CA6C830,CheckCondition {Before Action is Executed} 6D9D1ABC-7146-443F-9EE9-205D5CA6C830,String {<%Property <%CurrentPane%> UserMustAcceptLicense%>} 6E70FB1F-6A43-6C23-3242E965A0D0,Action {Install Actions} 6E70FB1F-6A43-6C23-3242E965A0D0,Conditions {0 conditions} 6F61CDA8-30C9-454F-82A3-9987E1203079,Alias {Start a sync now.} 6F61CDA8-30C9-454F-82A3-9987E1203079,Conditions {0 conditions} 6F61CDA8-30C9-454F-82A3-9987E1203079,FileName <%ShortAppName%>-program-sync 6F61CDA8-30C9-454F-82A3-9987E1203079,ShortcutName {Sync now} 6F61CDA8-30C9-454F-82A3-9987E1203079,TargetFileName <%InstallDir%>/tools/Sync.bat 6F61CDA8-30C9-454F-82A3-9987E1203079,WorkingDirectory <%InstallDir%> 6F94698F-0839-3ABF-0CF2DF05A4C8,CheckCondition {Before Action is Executed} 6F94698F-0839-3ABF-0CF2DF05A4C8,String <%CreateQuickLaunchShortcut%> 6FEE2889-0338-1D49-60BF-1471F465AB26,AppendNewline No 6FEE2889-0338-1D49-60BF-1471F465AB26,Comment {Closing final BackupLocations bracket} 6FEE2889-0338-1D49-60BF-1471F465AB26,Conditions {0 conditions} 6FEE2889-0338-1D49-60BF-1471F465AB26,FileOpenAction {Append to file} 6FEE2889-0338-1D49-60BF-1471F465AB26,Files <%ConfigFileTemplate%> 6FEE2889-0338-1D49-60BF-1471F465AB26,LineFeed Windows 6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite,subst 1 738DD098-7E3B-BC89-875CDB93CBE2,CheckCondition {Before Action is Executed} 738DD098-7E3B-BC89-875CDB93CBE2,Platform Windows 73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Conditions {0 conditions} 73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Destination <%ConfigFileName%> 73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Source <%ConfigFileTemplate%> 73EA65C1-3BE3-B190-55C3E99F6269,Conditions {1 condition} 748D673B-DFE6-5F74-329903ACE4DB,CheckCondition {Before Action is Executed} 748D673B-DFE6-5F74-329903ACE4DB,Filename <%ProgramExecutable%> 793D8178-0F51-7F07-BC5886586D3C,CheckCondition {Before Action is Executed} 793D8178-0F51-7F07-BC5886586D3C,Operator false 793D8178-0F51-7F07-BC5886586D3C,String <%InstallStopped%> 795EE61F-6C0D-4A8B-93E02AA3894A,CheckCondition {Before Action is Executed} 795EE61F-6C0D-4A8B-93E02AA3894A,String <%LaunchApplication%> 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,CheckCondition {Before Action is Executed} 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,Operator false 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,String <%LicenseAccepted%> 7A983CD8-302C-4942-BE59-525C5B5FA2F2,Conditions {0 conditions} 7A983CD8-302C-4942-BE59-525C5B5FA2F2,ProgramCommandLine {ServiceControl terminate} 7A983CD8-302C-4942-BE59-525C5B5FA2F2,WorkingDirectory <%InstallDir%> 7B770A07-A785-5215-956FA82CF14E,Active No 7B770A07-A785-5215-956FA82CF14E,Conditions {3 conditions} 7B770A07-A785-5215-956FA82CF14E,ShortcutDirectory <%QUICK_LAUNCH%> 7B770A07-A785-5215-956FA82CF14E,ShortcutName <%BrandName%> 7B770A07-A785-5215-956FA82CF14E,TargetFileName <%ProgramExecutable%> 7B770A07-A785-5215-956FA82CF14E,WorkingDirectory <%InstallDir%> 7D8E1902-2BC4-80D8-2C18771E7C22,Conditions {0 conditions} 7D8E1902-2BC4-80D8-2C18771E7C22,ProgramCommandLine {<%ServiceExeName%> -i -S <%ServiceName%> -c "<%ConfigFileName%>"} 7D8E1902-2BC4-80D8-2C18771E7C22,ProgressiveOutputWidget Message 7D8E1902-2BC4-80D8-2C18771E7C22,ShowProgressiveOutput Yes 7D8E1902-2BC4-80D8-2C18771E7C22,WorkingDirectory <%InstallDir%> 7F85263E-CAE2-46BA-AAC0-6B89D20FD2DE,ExitType Finish 8202CECC-54A0-9B6C-D24D111BA52E,Components 4A9C852B-647E-EED5-5482FFBCC2AF 8202CECC-54A0-9B6C-D24D111BA52E,Description,subst 1 8202CECC-54A0-9B6C-D24D111BA52E,DisplayName,subst 1 8202CECC-54A0-9B6C-D24D111BA52E,Name Typical 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Checked No 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Conditions {0 conditions} 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text,subst 1 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Type checkbutton 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Value Yes 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,VirtualText AddBackupLocation 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Y 250 848844B5-6103-9343-8B731B0BE4E0,Alias {Startup Actions} 848844B5-6103-9343-8B731B0BE4E0,Conditions {0 conditions} 84DA7F05-9FB7-CC36-9EC98F8A6826,CheckCondition {Before Next Pane is Displayed} 84DA7F05-9FB7-CC36-9EC98F8A6826,FailureMessage <%DirectoryPermissionText%> 84DA7F05-9FB7-CC36-9EC98F8A6826,Filename <%InstallDir%> 84DA7F05-9FB7-CC36-9EC98F8A6826,Permission {can create} 855DE408-060E-3D35-08B5-1D9AB05C2865,Conditions {0 conditions} 855DE408-060E-3D35-08B5-1D9AB05C2865,Height 100 855DE408-060E-3D35-08B5-1D9AB05C2865,Text,subst 1 855DE408-060E-3D35-08B5-1D9AB05C2865,Type text 855DE408-060E-3D35-08B5-1D9AB05C2865,VirtualText BackupLocationExclusions 855DE408-060E-3D35-08B5-1D9AB05C2865,Y 130 87DE6D78-81E1-495B-A214-B3FF3E7E5614,CheckCondition {Before Action is Executed} 87DE6D78-81E1-495B-A214-B3FF3E7E5614,Operator false 87DE6D78-81E1-495B-A214-B3FF3E7E5614,String <%Answer%> 88A50FD5-480F-19A5-DA74-C915EB0A9765,Active No 88A50FD5-480F-19A5-DA74-C915EB0A9765,Conditions {1 condition} 88A50FD5-480F-19A5-DA74-C915EB0A9765,ExecuteAction {After Pane is Finished} 88A50FD5-480F-19A5-DA74-C915EB0A9765,Pane 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 8A761DBD-0640-D98C-9B3AD7672A8F,Conditions {0 conditions} 8A761DBD-0640-D98C-9B3AD7672A8F,State disabled 8A761DBD-0640-D98C-9B3AD7672A8F,Widget {Back Button;Next Button} 8C866252-8760-9B08-FE569C25B60D,CheckCondition {Before Action is Executed} 8C866252-8760-9B08-FE569C25B60D,Filename <%ProgramExecutable%> 8E095096-F018-A880-429D-A2177A9B70EA,Active No 8E095096-F018-A880-429D-A2177A9B70EA,Conditions {0 conditions} 8E095096-F018-A880-429D-A2177A9B70EA,Text,subst 1 8E095096-F018-A880-429D-A2177A9B70EA,X 50 8E095096-F018-A880-429D-A2177A9B70EA,Y 150 8E1A5944-5AF5-5906-16D395E386D8,Conditions {0 conditions} 9013E862-8E81-5290-64F9-D8BCD13EC7E5,Active Yes 9013E862-8E81-5290-64F9-D8BCD13EC7E5,BackButton,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,CancelButton,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,Caption,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,CompanyLabel,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,Conditions {0 conditions} 9013E862-8E81-5290-64F9-D8BCD13EC7E5,Message,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,NextButton,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,Subtitle,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,Title,subst 1 9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel,subst 1 905DA2E9-988C-2F27-BB1F5F274AC9,Alias {Cancel Actions} 905DA2E9-988C-2F27-BB1F5F274AC9,Conditions {0 conditions} 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,BackButton,subst 1 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,CancelButton,subst 1 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Caption,subst 1 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Conditions {0 conditions} 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Message,subst 1 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,NextButton,subst 1 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Subtitle,subst 1 908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Title,subst 1 937C3FDD-FB28-98BD-3DAB276E59ED,Background white 937C3FDD-FB28-98BD-3DAB276E59ED,Conditions {3 conditions} 937C3FDD-FB28-98BD-3DAB276E59ED,Text,subst 1 937C3FDD-FB28-98BD-3DAB276E59ED,Type checkbutton 937C3FDD-FB28-98BD-3DAB276E59ED,VirtualText CreateQuickLaunchShortcut 937C3FDD-FB28-98BD-3DAB276E59ED,X 185 937C3FDD-FB28-98BD-3DAB276E59ED,Y 200 93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Active No 93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Comment {set BackupLocationName "BackupLocation_${BackupLocationNumber}"} 93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Conditions {0 conditions} 93AA298C-B64E-5683-14D2-7B86F7DEFD2C,ResultVirtualText BackupLocationName 93AA298C-B64E-5683-14D2-7B86F7DEFD2C,TclScript {set BackupLocationName "BackupLocation_${BackupLocationNumber}"} 96A68CAC-9ED7-806C-086B104720FD,CheckCondition {Before Action is Executed} 96A68CAC-9ED7-806C-086B104720FD,String <%ErrorsOccurred%> 97ACF525-C075-8635-E019202A83D8,Comment {Ask the user if they want to proceed with the uninstall.} 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Active No 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Conditions {0 conditions} 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,ResultVirtualText AddBackupLocation 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,TclScript {set AddBackupLocation no} 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Active Yes 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,BackButton,subst 1 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,CancelButton,subst 1 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Caption,subst 1 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Conditions {0 conditions} 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Message,subst 1 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,NextButton,subst 1 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Subtitle,subst 1 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Title,subst 1 9A663209-495B-ED16-09BE-457B61148022,Conditions {0 conditions} 9A663209-495B-ED16-09BE-457B61148022,FileName <%ShortAppName%>-program-QueryCurrent 9A663209-495B-ED16-09BE-457B61148022,ShortcutName {Query Current Only} 9A663209-495B-ED16-09BE-457B61148022,TargetFileName <%InstallDir%>/tools/QueryOutputCurrent.bat 9A663209-495B-ED16-09BE-457B61148022,WorkingDirectory <%InstallDir%> 9D101299-B80C-441B-8685-6E3AC61808E8,Alias {Remote Control} 9D101299-B80C-441B-8685-6E3AC61808E8,Comment {Get tech support via remote control} 9D101299-B80C-441B-8685-6E3AC61808E8,Conditions {0 conditions} 9D101299-B80C-441B-8685-6E3AC61808E8,FileName <%ShortAppName%>-program-RemoteControl 9D101299-B80C-441B-8685-6E3AC61808E8,ShortcutName RemoteControl 9D101299-B80C-441B-8685-6E3AC61808E8,TargetFileName <%InstallDir%>/tools/RemoteControl.exe 9D101299-B80C-441B-8685-6E3AC61808E8,WorkingDirectory <%InstallDir%> A1DD1DC2-85D7-9BC6-998AC3D4A3A9,Alias {Startup Actions} A1DD1DC2-85D7-9BC6-998AC3D4A3A9,Conditions {0 conditions} A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,Conditions {0 conditions} A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,ResultVirtualText AddBackupLocation A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,TclScript {set AddBackupLocation no } A6E1B027-A1B4-5848-4F868D028D00,CheckCondition {Before Action is Executed} A6E1B027-A1B4-5848-4F868D028D00,String <%ViewReadme%> ADA6EB2F-8820-4366-BBEF-ED1335B7F828,Conditions {1 condition} AE3BD5B4-35DE-4240-B79914D43E56,Active No AE3BD5B4-35DE-4240-B79914D43E56,BackButton,subst 1 AE3BD5B4-35DE-4240-B79914D43E56,CancelButton,subst 1 AE3BD5B4-35DE-4240-B79914D43E56,Caption,subst 1 AE3BD5B4-35DE-4240-B79914D43E56,Conditions {0 conditions} AE3BD5B4-35DE-4240-B79914D43E56,Message,subst 1 AE3BD5B4-35DE-4240-B79914D43E56,NextButton,subst 1 AIX-ppc,Active No AIX-ppc,DefaultDirectoryPermission 0755 AIX-ppc,DefaultFilePermission 0755 AIX-ppc,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> AIX-ppc,FallBackToConsole Yes AIX-ppc,InstallDir <%Home%>/<%ShortAppName%> AIX-ppc,InstallMode Standard AIX-ppc,InstallType Typical AIX-ppc,ProgramExecutable {} AIX-ppc,ProgramFolderAllUsers No AIX-ppc,ProgramFolderName <%AppName%> AIX-ppc,ProgramLicense <%InstallDir%>/LICENSE.txt AIX-ppc,ProgramName {} AIX-ppc,ProgramReadme <%InstallDir%>/README.txt AIX-ppc,PromptForRoot Yes AIX-ppc,RequireRoot No AIX-ppc,RootInstallDir /usr/local/<%ShortAppName%> B002A311-F8E7-41DE-B039-521391924E5B,Message,subst 1 B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Alias {Stop Backup Service} B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Comment {Stop the Backup Windows Service} B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Conditions {0 conditions} B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,FileName <%ShortAppName%>-program-stopservice B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,ShortcutName {Stop Service} B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,TargetFileName <%InstallDir%>/tools/StopService.bat B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,WorkingDirectory <%InstallDir%> B0AA6839-AAB6-A602-C0E4ECA2E4FF,CheckCondition {Before Action is Executed} B0AA6839-AAB6-A602-C0E4ECA2E4FF,Filename <%ProgramExecutable%> B39C0455-D1B6-7DDC-E2717F83463E,CheckCondition {Before Action is Executed} B39C0455-D1B6-7DDC-E2717F83463E,Operator false B39C0455-D1B6-7DDC-E2717F83463E,String <%InstallStopped%> B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Conditions {0 conditions} B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,ExecuteAction {Before Next Pane is Displayed} B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Password <%InstallPassword%> B4D31D1E-ADB1-DE8F-18EB7294DDA8,Conditions {0 conditions} B4D31D1E-ADB1-DE8F-18EB7294DDA8,ProgramCommandLine {<%ServiceExeName%> -r -S <%ServiceName%>} B4D31D1E-ADB1-DE8F-18EB7294DDA8,WorkingDirectory <%InstallDir%> B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,AcceptRadiobutton,subst 0 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Active Yes B506E7DA-E7C4-4D42-8C03-FD27BA16D078,BackButton,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,CancelButton,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Caption,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Conditions {0 conditions} B506E7DA-E7C4-4D42-8C03-FD27BA16D078,DeclineRadiobutton,subst 0 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Message,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,NextButton,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Subtitle,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Text,subst 1 B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Title,subst 1 B5DFEC63-92A9-4686-909E-0CE78A7069D6,Alias {Restart Backup Service} B5DFEC63-92A9-4686-909E-0CE78A7069D6,Comment {Stop and restart the Backup Windows Service} B5DFEC63-92A9-4686-909E-0CE78A7069D6,Conditions {0 conditions} B5DFEC63-92A9-4686-909E-0CE78A7069D6,FileName <%ShortAppName%>-program-restartservice B5DFEC63-92A9-4686-909E-0CE78A7069D6,ShortcutName {Restart Service} B5DFEC63-92A9-4686-909E-0CE78A7069D6,TargetFileName {<%InstallDir%>\tools\RestartService.bat} B5DFEC63-92A9-4686-909E-0CE78A7069D6,WorkingDirectory <%InstallDir%> B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Conditions {0 conditions} B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,LabelSide left B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Text,subst 1 B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Type entry B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,VirtualText BackupLocationShortName B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Y 100 B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Conditions {2 conditions} B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,State disabled B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Widget NextButton BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,CheckCondition {Before Next Action is Executed} BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,FailureMessage <%DirectoryPermissionText%> BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Filename <%InstallDir%> BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Permission {can create} C0452595-F3EB-43AD-BCA2-661437584636,Alias {Modify Backup Configuration} C0452595-F3EB-43AD-BCA2-661437584636,Comment {Modify your Backup Configuration} C0452595-F3EB-43AD-BCA2-661437584636,Conditions {0 conditions} C0452595-F3EB-43AD-BCA2-661437584636,FileName <%ShortAppName%>-program-editconfig C0452595-F3EB-43AD-BCA2-661437584636,ShortcutName {Edit Config File} C0452595-F3EB-43AD-BCA2-661437584636,TargetFileName <%InstallDir%>/tools/EditConfig.bat C0452595-F3EB-43AD-BCA2-661437584636,WorkingDirectory <%InstallDir%> C0AF7C05-A31A-8376-BCB9-BA8B3A666252,Conditions {0 conditions} C0AF7C05-A31A-8376-BCB9-BA8B3A666252,FileName <%ShortAppName%>-program-QueryAll C0AF7C05-A31A-8376-BCB9-BA8B3A666252,ShortcutName {Query All} C0AF7C05-A31A-8376-BCB9-BA8B3A666252,TargetFileName <%InstallDir%>/tools/QueryOutputAll.bat C0AF7C05-A31A-8376-BCB9-BA8B3A666252,WorkingDirectory <%InstallDir%> C105AAAE-7C16-2C9E-769FE4535B60,Active No C105AAAE-7C16-2C9E-769FE4535B60,Caption,subst 1 C105AAAE-7C16-2C9E-769FE4535B60,CloseButton,subst 1 C105AAAE-7C16-2C9E-769FE4535B60,Conditions {3 conditions} C105AAAE-7C16-2C9E-769FE4535B60,Message,subst 1 C105AAAE-7C16-2C9E-769FE4535B60,TextFile <%ProgramReadme%> C105AAAE-7C16-2C9E-769FE4535B60,Title,subst 1 C33D74B2-26FA-16F5-433A10C6A747,Active No C33D74B2-26FA-16F5-433A10C6A747,Conditions {3 conditions} C33D74B2-26FA-16F5-433A10C6A747,ProgramCommandLine <%ProgramExecutable%> C33D74B2-26FA-16F5-433A10C6A747,WaitForProgram No C33D74B2-26FA-16F5-433A10C6A747,WorkingDirectory <%InstallDir%> C7762473-273F-E3CA-17E3-65789B14CDB0,Conditions {0 conditions} C7762473-273F-E3CA-17E3-65789B14CDB0,ExecuteAction {Before Next Pane is Displayed} C7762473-273F-E3CA-17E3-65789B14CDB0,FileOpenAction {Append to file} C7762473-273F-E3CA-17E3-65789B14CDB0,Files <%ConfigFileTemplate%> C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite,subst 1 CC4337CC-F3B5-757C-DFCF5D1D365A,CheckCondition {Before Action is Executed} CC4337CC-F3B5-757C-DFCF5D1D365A,Operator false CC4337CC-F3B5-757C-DFCF5D1D365A,String <%SilentMode%> CDD84DE3-C970-458F-9162-1A3CE0AA716B,Alias {Start Backup Service} CDD84DE3-C970-458F-9162-1A3CE0AA716B,Comment {Start Backup Windows Service} CDD84DE3-C970-458F-9162-1A3CE0AA716B,Conditions {0 conditions} CDD84DE3-C970-458F-9162-1A3CE0AA716B,FileName <%ShortAppName%>-program-startservice CDD84DE3-C970-458F-9162-1A3CE0AA716B,ShortcutName {Start Service} CDD84DE3-C970-458F-9162-1A3CE0AA716B,TargetFileName {<%InstallDir%>\tools\StartService.bat} CDD84DE3-C970-458F-9162-1A3CE0AA716B,WorkingDirectory <%InstallDir%> CFFA27AF-A641-E41C-B4A0E3BB3CBB,Background white CFFA27AF-A641-E41C-B4A0E3BB3CBB,Conditions {2 conditions} CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text,subst 1 CFFA27AF-A641-E41C-B4A0E3BB3CBB,Type checkbutton CFFA27AF-A641-E41C-B4A0E3BB3CBB,VirtualText LaunchApplication CFFA27AF-A641-E41C-B4A0E3BB3CBB,X 185 CFFA27AF-A641-E41C-B4A0E3BB3CBB,Y 160 D23DD94C-E517-7F34-FD59-802CB18AB887,Comment {Need to do before starting anything else.} D23DD94C-E517-7F34-FD59-802CB18AB887,Conditions {0 conditions} D23DD94C-E517-7F34-FD59-802CB18AB887,Files {*/*.conf;*/*.txt;*/*.bat} D23DD94C-E517-7F34-FD59-802CB18AB887,LineFeed Windows D3D73C76-D9D3-07DA-63D4163A44BE,Conditions {0 conditions} D3D73C76-D9D3-07DA-63D4163A44BE,ExitType Finish D4FC6EB5-DDEE-4E4A-B8E1-D4B588A7928B,Action {Install Actions} D55BA4AF-E73B-60D1-E26F79175227,Action {Uninstall Actions} D55BA4AF-E73B-60D1-E26F79175227,Conditions {0 conditions} D7FBBEBB-2186-5674-BA87-BB7151859D4E,Conditions {0 conditions} D7FBBEBB-2186-5674-BA87-BB7151859D4E,ResultVirtualText BackupLocationNumber D7FBBEBB-2186-5674-BA87-BB7151859D4E,TclScript {incr BackupLocationNumber} D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,Alias {Reload Configuration File, after editing it.} D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,Conditions {0 conditions} D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,FileName <%ShortAppName%>-program-reloadconfig D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,ShortcutName {Reload configuration file} D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,TargetFileName <%InstallDir%>/tools/ReloadConfig.bat D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,WorkingDirectory <%InstallDir%> D8F0AA0F-AD79-C566-15CC508F503B,Action {Install Actions} D8F0AA0F-AD79-C566-15CC508F503B,Conditions {0 conditions} D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Conditions {0 conditions} D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Destination <%ConfigFileTemplate%> D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Source <%InstallDir%>/templates/original.conf DA33B826-E633-A845-4646-76DFA78B907B,Active Yes DA33B826-E633-A845-4646-76DFA78B907B,BackButton,subst 1 DA33B826-E633-A845-4646-76DFA78B907B,CancelButton,subst 1 DA33B826-E633-A845-4646-76DFA78B907B,Caption,subst 1 DA33B826-E633-A845-4646-76DFA78B907B,Conditions {0 conditions} DA33B826-E633-A845-4646-76DFA78B907B,Message,subst 1 DA33B826-E633-A845-4646-76DFA78B907B,NextButton,subst 1 DA33B826-E633-A845-4646-76DFA78B907B,Subtitle,subst 1 DA33B826-E633-A845-4646-76DFA78B907B,Title,subst 1 DDBBD8A9-13D7-9509-9202-419E989F60A9,Active No DDBBD8A9-13D7-9509-9202-419E989F60A9,Checked No DDBBD8A9-13D7-9509-9202-419E989F60A9,Conditions {0 conditions} DDBBD8A9-13D7-9509-9202-419E989F60A9,Text,subst 1 DDBBD8A9-13D7-9509-9202-419E989F60A9,Type checkbutton DDBBD8A9-13D7-9509-9202-419E989F60A9,VirtualText AddBackupLocation DDBBD8A9-13D7-9509-9202-419E989F60A9,X 50 DDBBD8A9-13D7-9509-9202-419E989F60A9,Y 200 DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Alias {Install Backup Service} DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Comment {Install the Backup Windows Service} DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Conditions {0 conditions} DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,FileName <%ShortAppName%>-program-installService DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,ShortcutName {Install Service} DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,TargetFileName <%InstallDir%>/tools/InstallService.bat DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,WorkingDirectory <%InstallDir%> DECC120D-6904-7F17-45A49184A5A3,Active No DECC120D-6904-7F17-45A49184A5A3,Conditions {2 conditions} DECC120D-6904-7F17-45A49184A5A3,ShortcutName <%AppName%> DECC120D-6904-7F17-45A49184A5A3,TargetFileName <%ProgramExecutable%> DECC120D-6904-7F17-45A49184A5A3,WorkingDirectory <%InstallDir%> DFFF91A9-2CA5-6ABE-8474D814AF88,CheckCondition {Before Action is Executed} DFFF91A9-2CA5-6ABE-8474D814AF88,Operator false DFFF91A9-2CA5-6ABE-8474D814AF88,String <%SilentMode%> E02368C5-95B5-03A7-3282740037B0,CheckCondition {Before Action is Executed} E02368C5-95B5-03A7-3282740037B0,Operator false E02368C5-95B5-03A7-3282740037B0,String <%InstallStopped%> E161F216-E597-B340-C1A71C476E2C,CheckCondition {Before Action is Executed} E161F216-E597-B340-C1A71C476E2C,Message,subst 1 E161F216-E597-B340-C1A71C476E2C,Title,subst 1 E23AC50D-7CFB-800E-A99C6F4068F8,Alias {Cancel Actions} E23AC50D-7CFB-800E-A99C6F4068F8,Conditions {0 conditions} E44CFF46-6302-C518-B9C30D2E43F7,CheckCondition {Before Action is Executed} E44CFF46-6302-C518-B9C30D2E43F7,String <%CreateDesktopShortcut%> E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1,Conditions {0 conditions} E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1,ProgramCommandLine {net stop <%ServiceName%>} E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Conditions {0 conditions} E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Destination {<%InstallDir%>\templates\NotifySysAdmin.original.vbs} E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Source {<%InstallDir%>\templates\NotifySysAdmin.template.vbs} EB2B31A1-C111-3582-0C8A5656692A,String <%ErrorsOccurred%> EB532611-5F30-3C24-66EB-F3826D9054FD,CheckCondition {Before Action is Executed} EB532611-5F30-3C24-66EB-F3826D9054FD,String <%AddBackupLocation%> F4024A3E-9A6D-2726-5E0CFFA93054,Alias {Uninstall Actions} F4024A3E-9A6D-2726-5E0CFFA93054,Conditions {0 conditions} F5F21749-8B3A-49C6-9138-9C4D6D703D26,Active No F5F21749-8B3A-49C6-9138-9C4D6D703D26,Conditions {0 conditions} F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgramCommandLine {cmd /k tools/7za.exe encrypted_keys.exe -p<%InstallPassword%>} F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgressiveOutputWidget Message F5F21749-8B3A-49C6-9138-9C4D6D703D26,ShowProgressiveOutput Yes F5F21749-8B3A-49C6-9138-9C4D6D703D26,WorkingDirectory <%InstallDir%> F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Active Yes F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,BackButton,subst 1 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,CancelButton,subst 1 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Caption,subst 1 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,CompanyLabel,subst 0 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Conditions {0 conditions} F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Message,subst 1 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,NextButton,subst 1 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Subtitle,subst 1 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Title,subst 1 F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,UserNameLabel,subst 1 F9E38720-6ABA-8B99-2471-496902E4CBC2,Active No F9E38720-6ABA-8B99-2471-496902E4CBC2,Conditions {0 conditions} F9E38720-6ABA-8B99-2471-496902E4CBC2,ResultVirtualText BackupLocationPath F9E38720-6ABA-8B99-2471-496902E4CBC2,TclScript {set BackupLocationPath "" } FB697A88-2842-468E-9776-85E84B009340,Active No FB697A88-2842-468E-9776-85E84B009340,Conditions {0 conditions} FB697A88-2842-468E-9776-85E84B009340,IgnoreErrors Yes FB697A88-2842-468E-9776-85E84B009340,ProgramCommandLine {"<%InstallDir%>\<%ServiceExeName%> -r -S <%ServiceName%>} FB697A88-2842-468E-9776-85E84B009340,WorkingDirectory <%Temp%> FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Active No FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Conditions {0 conditions} FDF68FD6-BEA8-4A74-867D-5139F4D9E793,WaitTime 2000 FEFD090D-C133-BC95-B3564F693CD3,Alias {Finish Actions} FEFD090D-C133-BC95-B3564F693CD3,Conditions {0 conditions} FreeBSD-4-x86,Active No FreeBSD-4-x86,DefaultDirectoryPermission 0755 FreeBSD-4-x86,DefaultFilePermission 0755 FreeBSD-4-x86,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> FreeBSD-4-x86,FallBackToConsole Yes FreeBSD-4-x86,InstallDir <%Home%>/<%ShortAppName%> FreeBSD-4-x86,InstallMode Standard FreeBSD-4-x86,InstallType Typical FreeBSD-4-x86,ProgramExecutable {} FreeBSD-4-x86,ProgramFolderAllUsers No FreeBSD-4-x86,ProgramFolderName <%AppName%> FreeBSD-4-x86,ProgramLicense <%InstallDir%>/LICENSE.txt FreeBSD-4-x86,ProgramName {} FreeBSD-4-x86,ProgramReadme <%InstallDir%>/README.txt FreeBSD-4-x86,PromptForRoot Yes FreeBSD-4-x86,RequireRoot No FreeBSD-4-x86,RootInstallDir /usr/local/<%ShortAppName%> FreeBSD-x86,Active No FreeBSD-x86,DefaultDirectoryPermission 0755 FreeBSD-x86,DefaultFilePermission 0755 FreeBSD-x86,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> FreeBSD-x86,FallBackToConsole Yes FreeBSD-x86,InstallDir <%Home%>/<%ShortAppName%> FreeBSD-x86,InstallMode Standard FreeBSD-x86,InstallType Typical FreeBSD-x86,ProgramExecutable {} FreeBSD-x86,ProgramFolderAllUsers No FreeBSD-x86,ProgramFolderName <%AppName%> FreeBSD-x86,ProgramLicense <%InstallDir%>/LICENSE.txt FreeBSD-x86,ProgramName {} FreeBSD-x86,ProgramReadme <%InstallDir%>/README.txt FreeBSD-x86,PromptForRoot Yes FreeBSD-x86,RequireRoot No FreeBSD-x86,RootInstallDir /usr/local/<%ShortAppName%> HPUX-hppa,Active No HPUX-hppa,DefaultDirectoryPermission 0755 HPUX-hppa,DefaultFilePermission 0755 HPUX-hppa,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> HPUX-hppa,FallBackToConsole Yes HPUX-hppa,InstallDir <%Home%>/<%ShortAppName%> HPUX-hppa,InstallMode Standard HPUX-hppa,InstallType Typical HPUX-hppa,ProgramExecutable {} HPUX-hppa,ProgramFolderAllUsers No HPUX-hppa,ProgramFolderName <%AppName%> HPUX-hppa,ProgramLicense <%InstallDir%>/LICENSE.txt HPUX-hppa,ProgramName {} HPUX-hppa,ProgramReadme <%InstallDir%>/README.txt HPUX-hppa,PromptForRoot Yes HPUX-hppa,RequireRoot No HPUX-hppa,RootInstallDir /usr/local/<%ShortAppName%> Linux-x86,Active No Linux-x86,BuildType dynamic Linux-x86,DefaultDirectoryPermission 00755 Linux-x86,DefaultFilePermission 00755 Linux-x86,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> Linux-x86,FallBackToConsole Yes Linux-x86,InstallDir <%Home%>/<%ShortAppName%> Linux-x86,InstallMode Standard Linux-x86,InstallType Typical Linux-x86,ProgramExecutable <%InstallDir%>/TebucoSafe Linux-x86,ProgramFolderAllUsers No Linux-x86,ProgramFolderName <%AppName%> Linux-x86,ProgramLicense <%InstallDir%>/LICENSE.txt Linux-x86,ProgramName {} Linux-x86,ProgramReadme <%InstallDir%>/README.txt Linux-x86,PromptForRoot Yes Linux-x86,RequireRoot No Linux-x86,RootInstallDir /usr/local/<%ShortAppName%> Solaris-sparc,Active No Solaris-sparc,DefaultDirectoryPermission 0755 Solaris-sparc,DefaultFilePermission 0755 Solaris-sparc,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> Solaris-sparc,FallBackToConsole Yes Solaris-sparc,InstallDir <%Home%>/<%ShortAppName%> Solaris-sparc,InstallMode Standard Solaris-sparc,InstallType Typical Solaris-sparc,ProgramExecutable {} Solaris-sparc,ProgramFolderAllUsers No Solaris-sparc,ProgramFolderName <%AppName%> Solaris-sparc,ProgramLicense <%InstallDir%>/LICENSE.txt Solaris-sparc,ProgramName {} Solaris-sparc,ProgramReadme <%InstallDir%>/README.txt Solaris-sparc,PromptForRoot Yes Solaris-sparc,RequireRoot No Solaris-sparc,RootInstallDir /usr/local/<%ShortAppName%> TarArchive,Active No TarArchive,CompressionLevel 6 TarArchive,DefaultDirectoryPermission 0755 TarArchive,DefaultFilePermission 0755 TarArchive,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> TarArchive,FallBackToConsole Yes TarArchive,InstallDir <%Home%>/<%ShortAppName%> TarArchive,InstallMode Standard TarArchive,InstallType Typical TarArchive,OutputFileName <%ShortAppName%>-<%Version%>.tar.gz TarArchive,ProgramExecutable {} TarArchive,ProgramFolderAllUsers No TarArchive,ProgramFolderName <%AppName%> TarArchive,ProgramLicense <%InstallDir%>/LICENSE.txt TarArchive,ProgramName {} TarArchive,ProgramReadme <%InstallDir%>/README.txt TarArchive,PromptForRoot Yes TarArchive,RequireRoot No TarArchive,RootInstallDir /usr/local/<%ShortAppName%> TarArchive,VirtualTextMap {<%InstallDir%> <%ShortAppName%>} Windows,Active Yes Windows,BuildType {} Windows,Executable installer.exe Windows,IncludeTWAPI No Windows,InstallDir {C:\Program Files\<%BrandName%>} Windows,InstallMode Standard Windows,InstallType Typical Windows,ProgramExecutable {} Windows,ProgramFolderAllUsers No Windows,ProgramFolderName <%BrandName%> Windows,ProgramLicense {<%InstallDir%>\LICENSE.txt} Windows,ProgramName {} Windows,ProgramReadme {} Windows,WindowsIcon {} ZipArchive,Active No ZipArchive,CompressionLevel 6 ZipArchive,DefaultDirectoryPermission 0755 ZipArchive,DefaultFilePermission 0755 ZipArchive,Executable <%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> ZipArchive,FallBackToConsole Yes ZipArchive,InstallDir <%Home%>/<%ShortAppName%> ZipArchive,InstallMode Standard ZipArchive,InstallType Typical ZipArchive,OutputFileName <%ShortAppName%>-<%Version%>.zip ZipArchive,ProgramExecutable {} ZipArchive,ProgramFolderAllUsers No ZipArchive,ProgramFolderName <%AppName%> ZipArchive,ProgramLicense <%InstallDir%>/LICENSE.txt ZipArchive,ProgramName {} ZipArchive,ProgramReadme <%InstallDir%>/README.txt ZipArchive,PromptForRoot Yes ZipArchive,RequireRoot No ZipArchive,RootInstallDir /usr/local/<%ShortAppName%> ZipArchive,VirtualTextMap {<%InstallDir%> <%ShortAppName%>} } ::msgcat::mcmset de { 20CBDBEA-2217-457B-8D98-D692C4F591E9,Message <%UninstallCompleteText%> 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message <%InstallationCompleteText%> B002A311-F8E7-41DE-B039-521391924E5B,Message <%InstallingApplicationText%> B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message <%UninstallingApplicationText%> } ::msgcat::mcmset en { 16D53E40-546B-54C3-088B1B5E3BBB,Text <%CreateDesktopShortcutText%> 20CBDBEA-2217-457B-8D98-D692C4F591E9,Message <%UninstallCompleteText%> 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message <%InstallationCompleteText%> 2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text {No:<%BackupLocationNumber%> } 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite BackupLocations\n\{\n 32F5B0AF-EB83-7A03-D8FAE1ECE473,Message <%InstallStartupText%> 32F5B0AF-EB83-7A03-D8FAE1ECE473,Title <%InstallApplicationText%> 36FF8915-8148-0F1F-27D7239CBFA1,Text <%ViewReadmeText%> 3B6E2E7C-1A26-27F1-D578E383B128,Text <%ViewReadmeText%> 3D33AA8C-0037-204B-39A339FD38BD,Message {<%BrandName%> has been removed from your system. Thank you for using the <%BrandName%> Backup Service. If you need further assistance, please contact us at http://<%BrandName%>.com or support@<%BrandName%>.com.} 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Caption {} 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Message {} 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Subtitle {Add a directory to backup, nickname it, and add some exclusions.} 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Title {Backup Locations} 3FDB57ED-598D-8A4E-CEF7-D90833305558,Text {Backup Directory} 4A9C852B-647E-EED5-5482FFBCC2AF,Description <%ProgramFilesDescription%> 4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message <%UninstallStartupText%> 4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title <%UninstallApplicationText%> 58E1119F-639E-17C9-5D3898F385AA,Caption {Setup will install <%ShortAppName%> in the following folder. To install to this folder, click Next. To install to a different folder, click Browse and select another folder.} 58E1119F-639E-17C9-5D3898F385AA,Subtitle {Where should <%ShortAppName%> be installed?} 59395A3B-6116-F93A-84E1-5E079C2CD44B,Caption {Backup Exclusions} 59395A3B-6116-F93A-84E1-5E079C2CD44B,Message {} 59395A3B-6116-F93A-84E1-5E079C2CD44B,Subtitle {Enter your backup exclusions for this Backup Location <%BackupLocationName_ShortName%> here.} 59395A3B-6116-F93A-84E1-5E079C2CD44B,Text {} 59395A3B-6116-F93A-84E1-5E079C2CD44B,Title {Backup Exclusions} 640DA2B2-6CF3-0873-D7AE-ABCDDE39EFCF,Text {Put your custom text here. <%AddBackupLocation%> <%BackupLocationNumber%> <%BackupLocationName%>} 6C323815-B9AB-FA94-4F5D152EBC51,Caption {Installation Wizard Complete} 6C323815-B9AB-FA94-4F5D152EBC51,Message {The Installation Wizard has successfully installed the <%BrandName%> Backup Service. Click Finish to exit the wizard.} 6CFBBE13-6B70-4B7C-B5EF-0677752D95A8,Caption {Installing encryption keys...} 6CFBBE13-6B70-4B7C-B5EF-0677752D95A8,Message {Click next to install encrypted keys and configuration file (bbackupd.conf)... You will be presented with the current configuration file so that you may make any last minute changes...} 6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite \}\n 8202CECC-54A0-9B6C-D24D111BA52E,Description <%TypicalInstallDescription%> 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text {Add another backup location?} 855DE408-060E-3D35-08B5-1D9AB05C2865,Text Exclusions 8E095096-F018-A880-429D-A2177A9B70EA,Text {AddAnother: <%AddBackupLocation%>} 9013E862-8E81-5290-64F9-D8BCD13EC7E5,Caption {Please enter your phone number and email address.} 9013E862-8E81-5290-64F9-D8BCD13EC7E5,CompanyLabel Email: 9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel Phone: 937C3FDD-FB28-98BD-3DAB276E59ED,Text <%CreateQuickLaunchShortcutText%> 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Caption {Click Next to continue...} 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Message {Building configuration file...} 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Subtitle {} 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Title Continue 9BAB328D-414B-D351-CA8D-824DF94B9DCA,Text {Add another backup location after this one?} A18C2977-1409-C1FB-892415711F72,Text <%LaunchApplicationText%> AAF2142A-9FC9-4664-DFF2-13B9EB7BA0E1,CompanyLabel Company: AE3BD5B4-35DE-4240-B79914D43E56,Caption {Welcome to the Installation Wizard for <%BrandName%> Backup Service!} AE3BD5B4-35DE-4240-B79914D43E56,Message {Thank you for installing the <%BrandName%>(SM) Backup Service. If you need any assistance, please contact us at http://<%BrandName%>.com or support@<%BrandName%>.com. This will install <%BrandName%> version <%Version%> on your computer. It is recommended that you close all other applications before continuing. Click Next to continue or Cancel to exit Setup. } B002A311-F8E7-41DE-B039-521391924E5B,Message <%InstallingApplicationText%> B4404713-AF4F-4F4B-670F3115517F,Description <%CustomInstallDescription%> B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message <%UninstallingApplicationText%> B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Text {Box Backup, http://www.fluffy.co.uk/boxbackup Copyright (c) 2003-2007 Ben Summers and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All use of this software and associated advertising materials must display the following acknowledgement: This product includes software developed by Ben Summers and contributors. 4. The names of the Authors may not be used to endorse or promote products derived from this software without specific prior written permission. [Where legally impermissible the Authors do not disclaim liability for direct physical injury or death caused solely by defects in the software unless it is modified by a third party.] THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE} B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Caption {<%BrandName%> will backup the following folder. To backup this folder, click Next. To backup a different folder, click Browse and select another folder.} B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,DestinationLabel {Backup This Folder} B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Message {} B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Subtitle {What directory should <%BrandName%> backup?} B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Title {Choose Backup Location} B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Text {Short Name} B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Caption {Please enter the agreed-upon password for your encrypted key file...} B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,CompanyLabel Password: B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Subtitle {Please enter your encrypted key file password.} C105AAAE-7C16-2C9E-769FE4535B60,Caption <%ApplicationReadmeText%> C105AAAE-7C16-2C9E-769FE4535B60,Message {} C105AAAE-7C16-2C9E-769FE4535B60,Title <%ApplicationReadmeText%> C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite {<%BackupLocationShortName%> { Path = <%BackupLocationPath%> <%BackupLocationExclusions%> } } CB058DBA-C3B7-2F48-D985-BE2F7107A76D,BrowseText {To continue, click Next. If you would like to select a folder to backup, click Browse.} CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Caption {} CB058DBA-C3B7-2F48-D985-BE2F7107A76D,DestinationLabel {Backup This Folder} CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Subtitle {What directories should <%BrandName%> backup?} CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Title {Choose Backup Location} CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text <%LaunchApplicationText%> D4625CA6-9864-D8EF-F252D7B7DC87,Text <%CreateDesktopShortcutText%> D47BE952-79F2-844E-D2E5-8F22044E7A9D,Text {Account Number:} DA33B826-E633-A845-4646-76DFA78B907B,Caption {Click Next to continue...} DA33B826-E633-A845-4646-76DFA78B907B,Message {Completing configuration file...} DA33B826-E633-A845-4646-76DFA78B907B,Subtitle {} DA33B826-E633-A845-4646-76DFA78B907B,Title Continue DDBBD8A9-13D7-9509-9202-419E989F60A9,Text {Add another Backup Location?} E0CADC4E-08A6-E429-3B49-BB8CFB7B097F,Text {Simple name for this Backup Location (short, no spaces or special characters)} E161F216-E597-B340-C1A71C476E2C,Message <%UninstallLeftoverText%> E161F216-E597-B340-C1A71C476E2C,Title {Uninstall <%BrandName%>} EA2C57E8-CCFB-CF3D-92CA-83369EFF1B08,Text {Add (another) Backup Location after this one?} EDE364D6-22C7-5108-D398-26FC24E0A55A,Text {Enter your Backup Exclusions for this Backup Location...} F59AF47D-4136-64F8-82C7-4506BD4327FD,Text {Add another Backup Location after this one?} F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Caption {Please enter your Account Number.} F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,UserNameLabel {Account Number (like 10005004):} F98784B1-1965-0F42-6BB0542AE1A9,Caption {Installing and starting the TebucoSafe Backup Service...} F98784B1-1965-0F42-6BB0542AE1A9,Message {Click Next to install the TebucoSafe Backup Service as an operating system service on your computer (see services.msc), and start up that service. } FC678E76-6823-2E55-204CA01C35EF,Text <%CreateQuickLaunchShortcutText%> FF4F6EEA-F4CC-428E-AF33-EB0E88E2147E,Text { Copyright (c) 2003 - 2006 Ben Summers and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All use of this software and associated advertising materials must display the following acknowledgment: This product includes software developed by Ben Summers. 4. The names of the Authors may not be used to endorse or promote products derived from this software without specific prior written permission. Where legally impermissible the Authors do not disclaim liability for direct physical injury or death caused solely by defects in the software unless it is modified by a third party.] THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } } ::msgcat::mcmset es { 20CBDBEA-2217-457B-8D98-D692C4F591E9,Message <%UninstallCompleteText%> 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message <%InstallationCompleteText%> B002A311-F8E7-41DE-B039-521391924E5B,Message <%InstallingApplicationText%> B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message <%UninstallingApplicationText%> } ::msgcat::mcmset fr { 20CBDBEA-2217-457B-8D98-D692C4F591E9,Message <%UninstallCompleteText%> 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message <%InstallationCompleteText%> B002A311-F8E7-41DE-B039-521391924E5B,Message <%InstallingApplicationText%> B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message <%UninstallingApplicationText%> } ::msgcat::mcmset pl { 20CBDBEA-2217-457B-8D98-D692C4F591E9,Message <%UninstallCompleteText%> 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message <%InstallationCompleteText%> B002A311-F8E7-41DE-B039-521391924E5B,Message <%InstallingApplicationText%> B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message <%UninstallingApplicationText%> } ::msgcat::mcmset pt_br { 20CBDBEA-2217-457B-8D98-D692C4F591E9,Message <%UninstallCompleteText%> 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message <%InstallationCompleteText%> B002A311-F8E7-41DE-B039-521391924E5B,Message <%InstallingApplicationText%> B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message <%UninstallingApplicationText%> } boxbackup/contrib/bbadmin/0000775000175000017500000000000011652362374016357 5ustar siretartsiretartboxbackup/contrib/bbadmin/accounts.cgi0000775000175000017500000003224111106150443020651 0ustar siretartsiretart#!/usr/bin/perl # Box Backup web management interface (c) Chris Wilson, 2008 # # LICENSE: The Box Backup license applies to this code, with the following # additional conditions: # # If you make any changes to this code, except for changes to existing # variables in the Configuration section below, you must publish the changes # under the same license, whether or not you distribute copies of the # changed version. # # If you use any of this code in a derivative work, you must publish the # source code of the derivative work under the same or compatible license, # whether or not you distribute copies of the derivative work. # # The terms of the Box Backup license may be viewed here: # https://www.boxbackup.org/license.html # # If you require access to the code under a different license, this may # be negotiated with the copyright holder. use strict; use warnings; # Variables which you may need to change to match your installation # Changes to existing variables are NOT required to be published. my $box_dir = "/etc/box"; my $bbstored_dir = "$box_dir/bbstored"; my $ca_dir = "/mnt/backup/boxbackup/ca"; # You should not need to change these unless you have a non-standard installation my $bbstored_conf_file = "$box_dir/bbstored.conf"; my $bbstoreaccounts = "/usr/local/sbin/bbstoreaccounts"; my $accounts_db_file = undef; # my $accounts_db_file = "/etc/box/bbstored/accounts.txt"; my $raidfile_conf_file = undef; # my $raidfile_conf_file = "/etc/box/raidfile.conf"; my $sign_period = '5000'; # install Perl module with: # perl -MCPAN -e 'install Config::Scoped' # perl -MCPAN -e 'force install P/PT/PTHOMSEN/BoxBackup/BBConfig-0.03.tar.gz' # perl -MCPAN -e 'install Convert::ASN1' # download http://search.cpan.org/CPAN/authors/id/L/LE/LEO/Convert-X509-0.1.tar.gz, # unpack, and move the Convert folder to /usr/lib/perl5/site_perl/X.X.X # Check that SSL is being used. # DO NOT DISABLE THIS unless you really know what you're doing! die "This script requires an SSL web server" unless $ENV{HTTPS}; # Check that the script is protected by basic authentication. # DO NOT DISABLE THIS unless you really know what you're doing! die "This script requires HTTP Authentication" unless $ENV{REMOTE_USER}; # You should not need to change anything below this line. # If you do, you must publish your changes to comply with the license. use BoxBackup::Config::Accounts; use BoxBackup::Config::DiskSets; use CGI::Carp qw(fatalsToBrowser); use CGI::Pretty; use Config::Scoped; use Convert::X509::Request; use English; use Fcntl; use File::Temp; use URI; use URI::QueryParam; sub check_access($$) { my ($file,$desc) = @_; unless (-r $file) { open FILE, "< $file" and die "should have failed"; die "Failed to access $desc ($file): $!"; } } sub check_executable($$) { my ($file,$desc) = @_; unless (-x $file) { open FILE, "< $file" and die "should have failed"; die "$desc is not executable ($file): $!"; } } my $cgi = new CGI; if (my $download = $cgi->param("download")) { my ($filename, $acct_no); if ($download eq "cert") { $acct_no = $cgi->param("account"); $acct_no =~ tr/0-9a-fA-F//cd; $filename = "$acct_no-cert.pem"; } elsif ($download eq "cacert") { $filename = "serverCA.pem"; } else { die "No such download method $download"; } print $cgi->header(-type => "text/plain", -"content-disposition" => "attachment; filename=$filename"); my $send_file; if ($download eq "cert") { $send_file = "$ca_dir/clients/$filename"; } elsif ($download eq "cacert") { $send_file = "$ca_dir/roots/serverCA.pem"; } die "File does not exist: $send_file" unless -f $send_file; die "File is not readable by user " . getpwuid($UID) . ": $send_file" unless -r $send_file; open SENDFILE, "< $send_file" or die "Failed to open file " . "$send_file: $!"; while (my $line = ) { print $line; } close SENDFILE; exit 0; } print $cgi->header(), $cgi->start_html(-title=>"Box Backup Certificates", -style=>'bb.css'); print $cgi->h1("Box Backup Certificates"); check_access($bbstored_conf_file, "BBStoreD configuration file"); my $bbstored_conf = Config::Scoped->new(file => $bbstored_conf_file)->parse(); $accounts_db_file ||= $bbstored_conf->{'Server'}{'AccountDatabase'}; die "Missing AccountDatabase in $bbstored_conf_file" unless $accounts_db_file; check_access($accounts_db_file, "Accounts Database"); $raidfile_conf_file ||= $bbstored_conf->{'Server'}{'RaidFileConf'}; die "Missing RaidFileConf in $bbstored_conf_file" unless $raidfile_conf_file; check_access($raidfile_conf_file, "RaidFile configuration file"); my $accounts_db = BoxBackup::Config::Accounts->new($accounts_db_file); check_executable($bbstoreaccounts, "bbstoreaccounts program"); sub error($) { my ($message) = @_; unless ($message =~ /^p($message); } print $cgi->div({-class=>"error"}, $message); return 0; } sub url { my $cgi = shift @_; my %params = @_; my $uri = URI->new($cgi->url(-absolute=>1)); foreach my $param (keys %params) { $uri->query_param($param, $params{$param}); } return $uri; } sub create_account($) { my ($cgi) = @_; my $upload = $cgi->upload('req'); unless ($upload) { return error("Please attach a certificate request file."); } my $tempfile = File::Temp->new("bbaccount-certreq-XXXXXX.pem"); my $csr_data = ""; while (my $line = <$upload>) { print $tempfile $line; $csr_data .= $line; } my @accounts = $accounts_db->getAccountIDs(); my $new_acc_no = $cgi->param('account'); if (not $new_acc_no) { return error("Please enter an account number."); } foreach my $account_no (@accounts) { if ($account_no == $new_acc_no) { return error("The account number $new_acc_no " . "already exists, please use one which " . "does not."); } } my $req = Convert::X509::Request->new($csr_data); my $cn; foreach my $part ($req->subject) { if ($part =~ /^cn=(.*)/i) { $cn = $1; last; } } unless ($cn) { return error("The certificate request does not include a " . "common name, which should be BACKUP-$new_acc_no."); } unless ($cn eq "BACKUP-$new_acc_no") { return error("The certificate request includes the wrong " . "common name. Expected " . "BACKUP-$new_acc_no but found " . "$cn."); } my $out_cert_dir = "$ca_dir/clients"; unless (-w $out_cert_dir) { return error("Cannot write to certificate directory " . "$out_cert_dir as user " . "" . getpwuid($UID) . "."); } my $out_cert = "$out_cert_dir/$new_acc_no-cert.pem"; if (-f $out_cert and not -w $out_cert) { return error("The certificate file $out_cert " . "exists and is not writable as user " . "$out_cert_dir as user " . "" . getpwuid($UID) . "."); } my $client_ca_cert_file = "$ca_dir/roots/clientCA.pem"; unless (-r $client_ca_cert_file) { return error("The client CA certificate file " . "$client_ca_cert_file " . "is not readable by user " . "" . getpwuid($UID) . "."); } my $client_ca_key_file = "$ca_dir/keys/clientRootKey.pem"; unless (-r $client_ca_key_file) { return error("The client CA key file " . "$client_ca_key_file " . "is not readable by user " . "" . getpwuid($UID) . "."); } my $serial_file = "$ca_dir/roots/clientCA.srl"; unless (-w $serial_file) { return error("The certificate serial number file " . "$serial_file " . "is not writable by user " . "" . getpwuid($UID) . "."); } my $outputfile = File::Temp->new("bbaccounts-openssl-output-XXXXXX"); if (system("openssl x509 -req -in $tempfile -sha1 " . "-extensions usr_crt " . "-CA $client_ca_cert_file " . "-CAkey $client_ca_key_file " . "-out $out_cert -days $sign_period " . ">$outputfile 2>&1") != 0) { open ERR, "< $outputfile" or die "$outputfile: $!"; my $errors = join("", ); close ERR; return error($cgi->p("Failed to sign certificate:") . $cgi->pre($errors)); } my $cert_uri = url($cgi, download => "cert", account => $new_acc_no); my $ca_uri = url($cgi, download => "cacert"); print $cgi->div({-class=>"success"}, $cgi->p("Account created. Please download the following " . "files:") . $cgi->ul( $cgi->li($cgi->a({href=>$cert_uri}, "Client Certificate")), $cgi->li($cgi->a({href=>$ca_uri}, "CA Certificate")) ) ); return 1; } if ($cgi->param("create")) { print $cgi->h2("Account Creation"); create_account($cgi); } print $cgi->h2("Accounts"); print $cgi->start_table({-border=>0, -class=>"numbers"}); print $cgi->Tr( $cgi->th("Account"), $cgi->th('Used'), $cgi->th('%'), $cgi->th('Old files'), $cgi->th('%'), $cgi->th('Deleted files'), $cgi->th('%'), $cgi->th('Directories'), $cgi->th('%'), $cgi->th('Soft limit'), $cgi->th('%'), $cgi->th('Hard limit'), $cgi->th('Actions') ); sub human_format($) { my ($kb) = @_; die "bad format in value: expected number followed by kB, " . "found '$kb'" unless $kb =~ /^(\d+) (kB)$/; my $value = $1; my $units = $2; if ($value > 1024) { $value /= 1024; $units = "MB"; } if ($value > 1024) { $value /= 1024; $units = "GB"; } $value = sprintf("%.1f", $value); return "$value $units"; } sub bbstoreaccounts_format($) { my ($kb) = @_; die unless $kb =~ /^(\d+) (kB)$/; my $value = $1; my $units = "K"; unless ($value % 1024) { $value /= 1024; $units = "M"; } unless ($value % 1024) { $value /= 1024; $units = "G"; } return "$value$units"; } sub get_account_info($) { my ($account) = @_; open BBSA, "$bbstoreaccounts -c $bbstored_conf_file -m info $account |" or die "Failed to get account info for $account: $!"; my $account_info = {}; while (my $line = ) { unless ($line =~ m/([^:]*): (.*)/) { die "Bad format in bbstoreaccounts info output " . "for account $account: '$line'"; } my $name = $1; my $value = $2; if ($value =~ /(.*), (.*)/) { $account_info->{$name} = [$1, $2]; } else { $account_info->{$name} = $value; } } return $account_info; } sub format_account_info($) { my ($values) = @_; my $kb = $values->[0]; my $pc = $values->[1]; return $cgi->td(human_format($kb)), $cgi->td($values->[1]); } my %account_numbers; my @accounts = $accounts_db->getAccountIDs(); foreach my $i (@accounts) { die "Duplicate account number $i" if $account_numbers{hex($i)}; $account_numbers{hex($i)} = 1; # Find out what account is on what diskset. my $disk = $accounts_db->getDisk($i); # store limits my $account_info = get_account_info($i); print $cgi->Tr( $cgi->td($i), format_account_info($account_info->{'Used'}), format_account_info($account_info->{'Old files'}), format_account_info($account_info->{'Deleted files'}), format_account_info($account_info->{'Directories'}), format_account_info($account_info->{'Soft limit'}), $cgi->td(human_format($account_info->{'Hard limit'}[0])), $cgi->td($cgi->a({-href=>url($cgi, account=>$i)}, "Edit")) ); } print $cgi->end_table(); my $account_no = $cgi->param("account"); $account_no =~ tr/0-9a-fA-F//cd; if (not $cgi->param("showcreate")) { print $cgi->start_form, $cgi->submit(-name=>"showcreate", -value=>"Create Account"), $cgi->end_form(); } if ($account_no) { print $cgi->h2("Edit Account"); my $account_info = get_account_info($account_no); $cgi->param("account", $account_no); $cgi->param("soft_limit", bbstoreaccounts_format($account_info->{'Soft limit'}[0])); $cgi->param("hard_limit", bbstoreaccounts_format($account_info->{'Hard limit'}[0])); } elsif ($cgi->param("showcreate")) { print $cgi->h2("Create Account"); } if ($account_no or $cgi->param("showcreate")) { my $new_account_no = 1; while ($account_numbers{$new_account_no}) { $new_account_no++; } my $disksets_conf = BoxBackup::Config::DiskSets->new($raidfile_conf_file); my @disk_names = $disksets_conf->getListofDisks(); my @disk_numbers; my %disk_labels; foreach my $name (@disk_names) { my $num = $disksets_conf->getParamVal($name, "SetNumber"); push @disk_numbers, $num; $disk_labels{$num} = $name; } print $cgi->start_multipart_form(), $cgi->start_table(); if ($account_no) { print $cgi->Tr( $cgi->th("Account Number"), $cgi->td($account_no . $cgi->hidden("account", $account_no)) ); } else { print $cgi->Tr( $cgi->th("Account Number"), $account_no ? $account_no : $cgi->td($cgi->textfield(-name => "account", -default => sprintf("%x", $new_account_no))), ); } if (not $account_no) { print $cgi->Tr( $cgi->th("Disk Set"), $cgi->td($cgi->popup_menu(-name => "disk_set", -values => \@disk_numbers, -labels => \%disk_labels)) ); } print $cgi->Tr( $cgi->th("Soft Limit"), $cgi->td($cgi->textfield(-name => "soft_limit", -default => "10G")) ), $cgi->Tr( $cgi->th("Hard Limit"), $cgi->td($cgi->textfield(-name => "hard_limit", -default => "20G")) ), $cgi->Tr( $cgi->th("Certificate Request"), $cgi->td($cgi->filefield({ -name => "req", -default => "*.crt" })) ); if ($account_no) { print $cgi->Tr( $cgi->th(), $cgi->td($cgi->submit(-name => "update", -value => "Update Account")) ); } else { print $cgi->Tr( $cgi->th(), $cgi->td($cgi->submit(-name => "create", -value => "Create Account")) ); } print $cgi->end_table(), $cgi->end_form(); } print $cgi->end_html; exit 0; boxbackup/contrib/bbadmin/apache.conf0000664000175000017500000000047711105313051020434 0ustar siretartsiretartAlias /bbadmin /var/www/localhost/bbadmin AuthType basic AuthName "Box Backup Web Management Interface" AuthUserFile /etc/apache2/bbadmin.cgi.htpasswd Require valid-user Allow from all Options ExecCGI AddHandler cgi-script .cgi DirectoryIndex accounts.cgi boxbackup/contrib/bbadmin/bb.css0000664000175000017500000000116011105313051017427 0ustar siretartsiretartbody { background: #edeef3; } table { border-spacing: 0px; } h1, th { background: #e4e6ed; } h1 { border-top: 1px solid #c4c4d5; border-bottom: 1px solid #fff; } td, th { border-top: 1px solid #fff; border-bottom: 1px solid #c4c4d5; margin: 0px; padding: 0.2em 0.5em; } th { text-align: left; } table.numbers td { text-align: right; } div.error, div.success { margin: 1em; } div.error>*, div.success>* { margin: 0.5em; } div.error { background: #fdd; border: 2px solid #c00; } div.success { background: #dfd; border: 2px solid #0c0; } h2, table, p { margin: 0.5em; } form { margin: 0.5em 0; } boxbackup/contrib/bbreporter/0000775000175000017500000000000011652362373017130 5ustar siretartsiretartboxbackup/contrib/bbreporter/LICENSE0000664000175000017500000010451310776352445020146 0ustar siretartsiretart GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . boxbackup/contrib/bbreporter/bbreporter.py0000775000175000017500000005122711074656605021664 0ustar siretartsiretart#!/usr/bin/env python # BoxBackupReporter - Simple script to report on backups that have been # performed using BoxBackup. # # Copyright: (C) 2007 Three A IT Limited # Author: Kenny Millington # # Credit: This script is based on the ideas of BoxReport.pl by Matt Brown of # Three A IT Support Limited. # ################################################################################ # !! Important !! # To make use of this script you need to run the boxbackup client with the -v # commandline option and set LogAllFileAccess = yes in your bbackupd.conf file. # # Notes on lazy mode: # If reporting on lazy mode backups you absolutely must ensure that # logrotate (or similar) rotates the log files at the same rate at # which you run this reporting script or you will report on the same # backup sessions on each execution. # # Notes on --rotate and log rotation in general: # The use-case for --rotate that I imagine is that you'll add a line like the # following into your syslog.conf file:- # # local6.* -/var/log/box # # Then specifying --rotate to this script will make it rotate the logs # each time you report on the backup so that you don't risk a backup session # being spread across two log files (e.g. syslog and syslog.0). # # NB: To do this you'll need to prevent logrotate/syslog from rotating your # /var/log/box file. On Debian based distros you'll need to edit two files. # # First: /etc/cron.daily/sysklogd, find the following line and make the # the required change: # Change: for LOG in `syslogd-listfiles` # To: for LOG in `syslogd-listfiles -s box` # # Second: /etc/cron.weekly/sysklogd, find the following line and make the # the required change: # Change: for LOG in `syslogd-listfiles --weekly` # To: for LOG in `syslogd-listfiles --weekly -s box` # # Alternatively, if suitable just ensure the backups stop before the # /etc/cron.daily/sysklogd file runs (usually 6:25am) and report on it # before the files get rotated. (If going for this option I'd just use # the main syslog file instead of creating a separate log file for box # backup since you know for a fact the syslog will get rotated daily.) # ################################################################################ # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # If sendmail is not in one of these paths, add the path. SENDMAIL_PATHS = ["/usr/sbin/", "/usr/bin/", "/bin/" , "/sbin/"] # The name of the sendmail binary, you probably won't need to change this. SENDMAIL_BIN = "sendmail" # Number of files to rotate around ROTATE_COUNT = 7 # Import the required libraries import sys, os, re, getopt, shutil, gzip class BoxBackupReporter: class BoxBackupReporterError(Exception): pass def __init__(self, config_file="/etc/box/bbackupd.conf", log_file="/var/log/syslog", email_to=None, email_from="report@boxbackup", rotate=False, verbose=False, stats=False, sort=False, debug=False): # Config options self.config_file = config_file self.log_file = log_file self.email_to = email_to self.email_from = email_from self.rotate_log_file = rotate self.verbose_report = verbose self.usage_stats = stats self.sort_files = sort self.debug = debug # Regex's self.re_automatic_backup = re.compile(" *AutomaticBackup *= *no", re.I) self.re_syslog = re.compile("(\S+) +(\S+) +([\d:]+) +(\S+) +([^:]+): +"+ "(?:[A-Z]+:)? *([^:]+): *(.*)") # Initialise report self.reset() def _debug(self, msg): if self.debug: sys.stderr.write("[bbreporter.py Debug]: %s\n" % msg) def reset(self): # Reset report data to default values self.hostname = "" self.patched_files = [] self.synced_files = [] self.uploaded_files = [] self.warnings = [] self.errors = [] self.stats = None self.start_datetime = "Unknown" self.end_datetime = "Unfinished" self.report = "No report generated" def run(self): try: self._determine_operating_mode() if self.lazy_mode: self._debug("Operating in LAZY MODE.") else: self._debug("Operating in SNAPSHOT MODE.") except IOError: raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ "Config file \"%s\" could not be read." % self.config_file) try: self._parse_syslog() except IOError: raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ "Log file \"%s\" could not be read." % self.log_file) self._parse_stats() self._generate_report() def deliver(self): # If we're not e-mailing the report then just dump it to stdout # and return. if self.email_to is None: print self.report # Now that we've delivered the report it's time to rotate the logs # if we're requested to do so. self._rotate_log() return # Locate the sendmail binary sendmail = self._locate_sendmail() if(sendmail is None): raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ "Could not find sendmail binary - Unable to send e-mail!") # Set the subject based on whether we think we failed or not. # (suffice it to say I consider getting an error and backing up # no files a failure or indeed not finding a start time in the logs). subject = "BoxBackup Reporter (%s) - " % self.hostname if self.start_datetime == "Unknown" or\ (len(self.patched_files) == 0 and len(self.synced_files) == 0 and\ len(self.uploaded_files) == 0): subject = subject + "FAILED" else: subject = subject + "SUCCESS" if len(self.errors) > 0: subject = subject + " (with errors)" # Prepare the e-mail message. mail = [] mail.append("To: " + self.email_to) mail.append("From: " + self.email_from) mail.append("Subject: " + subject) mail.append("") mail.append(self.report) # Send the mail. p = os.popen(sendmail + " -t", "w") p.write("\r\n".join(mail)) p.close() # Now that we've delivered the report it's time to rotate the logs # if we're requested to do so. self._rotate_log() def _determine_operating_mode(self): # Scan the config file and determine if we're running in lazy or # snapshot mode. cfh = open(self.config_file) for line in cfh: if not line.startswith("#"): if self.re_automatic_backup.match(line): self.lazy_mode = False cfh.close() return self.lazy_mode = True cfh.close() def _parse_syslog(self): lfh = open(self.log_file) patched_files = {} uploaded_files = {} synced_files = {} for line in lfh: # Only run the regex if we find a box backup entry. if line.find("Box Backup") > -1 or line.find("bbackupd") > -1: raw_data = self.re_syslog.findall(line) try: data = raw_data[0] except IndexError: # If the regex didn't match it's not a message that we're # interested in so move to the next line. continue # Set the hostname, it shouldn't change in a log file self.hostname = data[3] # If we find the backup-start event then set the start_datetime. if data[6].find("backup-start") > -1: # If we're not in lazy mode or the start_datetime hasn't # been set then reset the data and set it. # # If we're in lazy mode and encounter a second backup-start # we don't want to change the start_datetime likewise if # we're not in lazy mode we do want to and we want to reset # so we only capture the most recent session. if not self.lazy_mode or self.start_datetime == "Unknown": self._debug("Reset start dtime with old time: %s." % self.start_datetime) # Reset ourselves self.reset() # Reset our temporary variables which we store # the files in. patched_files = {} uploaded_files = {} synced_files = {} self.start_datetime = data[1]+" "+data[0]+ " "+data[2] self._debug("Reset start dtime with new time %s." % self.start_datetime) # If we find the backup-finish event then set the end_datetime. elif data[6].find("backup-finish") > -1: self.end_datetime = data[1] + " " + data[0] + " " + data[2] self._debug("Set end dtime: %s" % self.end_datetime) # Only log the events if we have our start time. elif self.start_datetime != "Unknown": # We found a patch event, add the file to the patched_files. if data[5] == "Uploading patch to file": patched_files[data[6]] = "" # We found an upload event, add to uploaded files. elif data[5] == "Uploading complete file": uploaded_files[data[6]] = "" # We found another upload event. elif data[5] == "Uploaded file": uploaded_files[data[6]] = "" # We found a sync event, add the file to the synced_files. elif data[5] == "Synchronised file": synced_files[data[6]] = "" # We found a warning, add the warning to the warnings. elif data[5] == "WARNING": self.warnings.append(data[6]) # We found an error, add the error to the errors. elif data[5] == "ERROR": self.errors.append(data[6]) self.patched_files = patched_files.keys() self.uploaded_files = uploaded_files.keys() self.synced_files = synced_files.keys() # There's no point running the sort functions if we're not going # to display the resultant lists. if self.sort_files and self.verbose_report: self.patched_files.sort() self.uploaded_files.sort() lfh.close() def _parse_stats(self): if(not self.usage_stats): return # Grab the stats from bbackupquery sfh = os.popen("bbackupquery usage quit", "r") raw_stats = sfh.read() sfh.close() # Parse the stats stats_re = re.compile("commands.[\n ]*\n(.*)\n+", re.S) stats = stats_re.findall(raw_stats) try: self.stats = stats[0] except IndexError: self.stats = "Unable to retrieve usage information." def _generate_report(self): if self.start_datetime == "Unknown": self.report = "No report data has been found." return total_files = len(self.patched_files) + len(self.uploaded_files) report = [] report.append("--------------------------------------------------") report.append("Report Title : Box Backup - Backup Statistics") report.append("Report Period : %s - %s" % (self.start_datetime, self.end_datetime)) report.append("--------------------------------------------------") report.append("") report.append("This is your box backup report, in summary:") report.append("") report.append("%d file(s) have been backed up." % total_files) report.append("%d file(s) were uploaded." % len(self.uploaded_files)) report.append("%d file(s) were patched." % len(self.patched_files)) report.append("%d file(s) were synchronised." % len(self.synced_files)) report.append("") report.append("%d warning(s) occurred." % len(self.warnings)) report.append("%d error(s) occurred." % len(self.errors)) report.append("") report.append("") # If we asked for the backup stats and they're available # show them. if(self.stats is not None and self.stats != ""): report.append("Your backup usage information follows:") report.append("") report.append(self.stats) report.append("") report.append("") # List the files if we've been asked for a verbose report. if(self.verbose_report): if len(self.uploaded_files) > 0: report.append("Uploaded Files (%d)" % len(self.uploaded_files)) report.append("---------------------") for file in self.uploaded_files: report.append(file) report.append("") report.append("") if len(self.patched_files) > 0: report.append("Patched Files (%d)" % len(self.patched_files)) report.append("---------------------") for file in self.patched_files: report.append(file) report.append("") report.append("") # Always output the warnings/errors. if len(self.warnings) > 0: report.append("Warnings (%d)" % len(self.warnings)) report.append("---------------------") for warning in self.warnings: report.append(warning) report.append("") report.append("") if len(self.errors) > 0: report.append("Errors (%d)" % len(self.errors)) report.append("---------------------") for error in self.errors: report.append(error) report.append("") report.append("") self.report = "\r\n".join(report) def _locate_sendmail(self): for path in SENDMAIL_PATHS: sendmail = os.path.join(path, SENDMAIL_BIN) if os.path.isfile(sendmail): return sendmail return None def _rotate_log(self): # If we're not configured to rotate then abort. if(not self.rotate_log_file): return # So we have these files to possibly account for while we process the # rotation:- # self.log_file, self.log_file.0, self.log_file.1.gz, self.log_file.2.gz # self.log_file.3.gz....self.log_file.(ROTATE_COUNT-1).gz # # Algorithm:- # * Delete last file. # * Work backwards moving 5->6, 4->5, 3->4, etc... but stop at .0 # * For .0 move it to .1 then gzip it. # * Move self.log_file to .0 # * Done. # If it exists, remove the oldest file. if(os.path.isfile(self.log_file + ".%d.gz" % (ROTATE_COUNT - 1))): os.unlink(self.log_file + ".%d.gz" % (ROTATE_COUNT - 1)) # Copy through the other gzipped log files. for i in range(ROTATE_COUNT - 1, 1, -1): src_file = self.log_file + ".%d.gz" % (i - 1) dst_file = self.log_file + ".%d.gz" % i # If the source file exists move/rename it. if(os.path.isfile(src_file)): shutil.move(src_file, dst_file) # Now we need to handle the .0 -> .1.gz case. if(os.path.isfile(self.log_file + ".0")): # Move .0 to .1 shutil.move(self.log_file + ".0", self.log_file + ".1") # gzip the file. fh = open(self.log_file + ".1", "r") zfh = gzip.GzipFile(self.log_file + ".1.gz", "w") zfh.write(fh.read()) zfh.flush() zfh.close() fh.close() # If gzip worked remove the original .1 file. if(os.path.isfile(self.log_file + ".1.gz")): os.unlink(self.log_file + ".1") # Finally move the current logfile to .0 shutil.move(self.log_file, self.log_file + ".0") def stderr(text): sys.stderr.write("%s\n" % text) def usage(): stderr("Usage: %s [OPTIONS]\n" % sys.argv[0]) stderr("Valid Options:-") stderr(" --logfile=LOGFILE\t\t\tSpecify the logfile to process,\n"+\ "\t\t\t\t\tdefault: /var/log/syslog\n") stderr(" --configfile=CONFIGFILE\t\tSpecify the bbackupd config file,\n "+\ "\t\t\t\t\tdefault: /etc/box/bbackupd.conf\n") stderr(" --email-to=user@example.com\t\tSpecify the e-mail address(es)\n"+\ "\t\t\t\t\tto send the report to, default is to\n"+\ "\t\t\t\t\tdisplay the report on the console.\n") stderr(" --email-from=user@example.com\t\tSpecify the e-mail address(es)"+\ "\n\t\t\t\t\tto set the From: address to,\n "+\ "\t\t\t\t\tdefault: report@boxbackup\n") stderr(" --stats\t\t\t\tIncludes the usage stats retrieved from \n"+\ "\t\t\t\t\t'bbackupquery usage' in the report.\n") stderr(" --sort\t\t\t\tSorts the file lists in verbose mode.\n") stderr(" --debug\t\t\t\tEnables debug output.\n") stderr(" --verbose\t\t\t\tList every file that was backed up to\n"+\ "\t\t\t\t\tthe server, default is to just display\n"+\ "\t\t\t\t\tthe summary.\n") stderr(" --rotate\t\t\t\tRotates the log files like logrotate\n"+\ "\t\t\t\t\twould, see the comments for a use-case.\n") def main(): # The defaults logfile = "/var/log/syslog" configfile = "/etc/box/bbackupd.conf" email_to = None email_from = "report@boxbackup" rotate = False verbose = False stats = False sort = False debug = False # Parse the options try: opts, args = getopt.getopt(sys.argv[1:], "dosrvhl:c:t:f:", ["help", "logfile=", "configfile=","email-to=", "email-from=","rotate","verbose","stats","sort", "debug"]) except getopt.GetoptError: usage() return for opt, arg in opts: if(opt in ("--logfile","-l")): logfile = arg elif(opt in ("--configfile", "-c")): configfile = arg elif(opt in ("--email-to", "-t")): email_to = arg elif(opt in ("--email-from", "-f")): email_from = arg elif(opt in ("--rotate", "-r")): rotate = True elif(opt in ("--verbose", "-v")): verbose = True elif(opt in ("--stats", "-s")): stats = True elif(opt in ("--sort", "-o")): sort = True elif(opt in ("--debug", "-d")): debug = True elif(opt in ("--help", "-h")): usage() return # Run the reporter bbr = BoxBackupReporter(configfile, logfile, email_to, email_from, rotate, verbose, stats, sort, debug) try: bbr.run() bbr.deliver() except BoxBackupReporter.BoxBackupReporterError, error_msg: print error_msg if __name__ == "__main__": main() boxbackup/test/0000775000175000017500000000000011652362372014300 5ustar siretartsiretartboxbackup/test/win32/0000775000175000017500000000000011652362372015242 5ustar siretartsiretartboxbackup/test/win32/timezone.cpp0000664000175000017500000000325510374104755017604 0ustar siretartsiretart#include #include typedef int uid_t; typedef int gid_t; typedef int u_int32_t; #include "emu.h" int main(int argc, char** argv) { time_t time_now = time(NULL); char* time_str = strdup(asctime(gmtime(&time_now))); time_str[24] = 0; printf("Time now is %d (%s)\n", time_now, time_str); char testfile[80]; snprintf(testfile, sizeof(testfile), "test.%d", time_now); printf("Test file is: %s\n", testfile); _unlink(testfile); /* int fd = open(testfile, O_RDWR | O_CREAT | O_EXCL); if (fd < 0) { perror("open"); exit(1); } close(fd); */ HANDLE fh = CreateFileA(testfile, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); if (!fh) { fprintf(stderr, "Failed to open file '%s': error %d\n", testfile, GetLastError()); exit(1); } BY_HANDLE_FILE_INFORMATION fi; if (!GetFileInformationByHandle(fh, &fi)) { fprintf(stderr, "Failed to get file information for '%s': " "error %d\n", testfile, GetLastError()); exit(1); } if (!CloseHandle(fh)) { fprintf(stderr, "Failed to close file: error %d\n", GetLastError()); exit(1); } time_t created_time = ConvertFileTimeToTime_t(&fi.ftCreationTime); time_str = strdup(asctime(gmtime(&created_time))); time_str[24] = 0; printf("File created time: %d (%s)\n", created_time, time_str); printf("Difference is: %d\n", created_time - time_now); if (abs(created_time - time_now) > 30) { fprintf(stderr, "Error: time difference too big: " "bug in emu.h?\n"); exit(1); } /* sleep(1); if (_unlink(testfile) != 0) { perror("Failed to delete test file"); exit(1); } */ exit(0); } boxbackup/test/win32/testlibwin32.cpp0000664000175000017500000002001210462244713020266 0ustar siretartsiretart// win32test.cpp : Defines the entry point for the console application. // //#include #include "Box.h" #ifdef WIN32 #include #include #include #include "../../bin/bbackupd/BackupDaemon.h" #include "BoxPortsAndFiles.h" #include "emu.h" int main(int argc, char* argv[]) { // ACL tests char* exename = getenv("WINDIR"); PSID psidOwner; PSID psidGroup; PACL pDacl; PSECURITY_DESCRIPTOR pSecurityDesc; DWORD result = GetNamedSecurityInfo( exename, // pObjectName SE_FILE_OBJECT, // ObjectType DACL_SECURITY_INFORMATION | // SecurityInfo GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, &psidOwner, // ppsidOwner, &psidGroup, // ppsidGroup, &pDacl, // ppDacl, NULL, // ppSacl, &pSecurityDesc // ppSecurityDescriptor ); if (result != ERROR_SUCCESS) { printf("Error getting security info for '%s': error %d", exename, result); } assert(result == ERROR_SUCCESS); char namebuf[1024]; char domainbuf[1024]; SID_NAME_USE nametype; DWORD namelen = sizeof(namebuf); DWORD domainlen = sizeof(domainbuf); assert(LookupAccountSid(NULL, psidOwner, namebuf, &namelen, domainbuf, &domainlen, &nametype)); printf("Owner:\n"); printf("User name: %s\n", namebuf); printf("Domain name: %s\n", domainbuf); printf("Name type: %d\n", nametype); printf("\n"); namelen = sizeof(namebuf); domainlen = sizeof(domainbuf); assert(LookupAccountSid(NULL, psidGroup, namebuf, &namelen, domainbuf, &domainlen, &nametype)); printf("Group:\n"); printf("User name: %s\n", namebuf); printf("Domain name: %s\n", domainbuf); printf("Name type: %d\n", nametype); printf("\n"); ULONG numEntries; PEXPLICIT_ACCESS pEntries; result = GetExplicitEntriesFromAcl ( pDacl, // pAcl &numEntries, // pcCountOfExplicitEntries, &pEntries // pListOfExplicitEntries ); assert(result == ERROR_SUCCESS); printf("Found %lu explicit DACL entries for '%s'\n\n", (unsigned long)numEntries, exename); for (ULONG i = 0; i < numEntries; i++) { EXPLICIT_ACCESS* pEntry = &(pEntries[i]); printf("DACL entry %lu:\n", (unsigned long)i); DWORD perms = pEntry->grfAccessPermissions; printf(" Access permissions: ", perms); #define PRINT_PERM(name) \ if (perms & name) \ { \ printf(#name " "); \ perms &= ~name; \ } PRINT_PERM(FILE_ADD_FILE); PRINT_PERM(FILE_ADD_SUBDIRECTORY); PRINT_PERM(FILE_ALL_ACCESS); PRINT_PERM(FILE_APPEND_DATA); PRINT_PERM(FILE_CREATE_PIPE_INSTANCE); PRINT_PERM(FILE_DELETE_CHILD); PRINT_PERM(FILE_EXECUTE); PRINT_PERM(FILE_LIST_DIRECTORY); PRINT_PERM(FILE_READ_ATTRIBUTES); PRINT_PERM(FILE_READ_DATA); PRINT_PERM(FILE_READ_EA); PRINT_PERM(FILE_TRAVERSE); PRINT_PERM(FILE_WRITE_ATTRIBUTES); PRINT_PERM(FILE_WRITE_DATA); PRINT_PERM(FILE_WRITE_EA); PRINT_PERM(STANDARD_RIGHTS_READ); PRINT_PERM(STANDARD_RIGHTS_WRITE); PRINT_PERM(SYNCHRONIZE); PRINT_PERM(DELETE); PRINT_PERM(READ_CONTROL); PRINT_PERM(WRITE_DAC); PRINT_PERM(WRITE_OWNER); PRINT_PERM(MAXIMUM_ALLOWED); PRINT_PERM(GENERIC_ALL); PRINT_PERM(GENERIC_EXECUTE); PRINT_PERM(GENERIC_WRITE); PRINT_PERM(GENERIC_READ); printf("\n"); if (perms) { printf(" Bits left over: %08x\n", perms); } assert(!perms); printf(" Access mode: "); switch(pEntry->grfAccessMode) { case NOT_USED_ACCESS: printf("NOT_USED_ACCESS\n"); break; case GRANT_ACCESS: printf("GRANT_ACCESS\n"); break; case DENY_ACCESS: printf("DENY_ACCESS\n"); break; case REVOKE_ACCESS: printf("REVOKE_ACCESS\n"); break; case SET_AUDIT_SUCCESS: printf("SET_AUDIT_SUCCESS\n"); break; case SET_AUDIT_FAILURE: printf("SET_AUDIT_FAILURE\n"); break; default: printf("Unknown (%08x)\n", pEntry->grfAccessMode); } printf(" Trustee: "); assert(pEntry->Trustee.pMultipleTrustee == NULL); assert(pEntry->Trustee.MultipleTrusteeOperation == NO_MULTIPLE_TRUSTEE); switch(pEntry->Trustee.TrusteeForm) { case TRUSTEE_IS_SID: { PSID trusteeSid = (PSID)(pEntry->Trustee.ptstrName); namelen = sizeof(namebuf); domainlen = sizeof(domainbuf); assert(LookupAccountSid(NULL, trusteeSid, namebuf, &namelen, domainbuf, &domainlen, &nametype)); printf("SID of %s\\%s (%d)\n", domainbuf, namebuf, nametype); } break; case TRUSTEE_IS_NAME: printf("Name\n"); break; case TRUSTEE_BAD_FORM: printf("Bad form\n"); assert(0); case TRUSTEE_IS_OBJECTS_AND_SID: printf("Objects and SID\n"); break; case TRUSTEE_IS_OBJECTS_AND_NAME: printf("Objects and name\n"); break; default: printf("Unknown form\n"); assert(0); } printf(" Trustee type: "); switch(pEntry->Trustee.TrusteeType) { case TRUSTEE_IS_UNKNOWN: printf("Unknown type.\n"); break; case TRUSTEE_IS_USER: printf("User\n"); break; case TRUSTEE_IS_GROUP: printf("Group\n"); break; case TRUSTEE_IS_DOMAIN: printf("Domain\n"); break; case TRUSTEE_IS_ALIAS: printf("Alias\n"); break; case TRUSTEE_IS_WELL_KNOWN_GROUP: printf("Well-known group\n"); break; case TRUSTEE_IS_DELETED: printf("Deleted account\n"); break; case TRUSTEE_IS_INVALID: printf("Invalid trustee type\n"); break; case TRUSTEE_IS_COMPUTER: printf("Computer\n"); break; default: printf("Unknown type %d\n", pEntry->Trustee.TrusteeType); assert(0); } printf("\n"); } assert(LocalFree((HLOCAL)pEntries) == 0); assert(LocalFree((HLOCAL)pSecurityDesc) == 0); chdir("c:\\tmp"); openfile("test", O_CREAT, 0); struct stat ourfs; //test our opendir, readdir and closedir //functions DIR *ourDir = opendir("C:"); if ( ourDir != NULL ) { struct dirent *info; do { info = readdir(ourDir); if (info) printf("File/Dir name is : %s\r\n", info->d_name); } while (info != NULL); closedir(ourDir); } std::string diry("C:\\Projects\\boxbuild\\testfiles\\"); ourDir = opendir(diry.c_str()); if ( ourDir != NULL ) { struct dirent *info; do { info = readdir(ourDir); if (info == NULL) break; std::string file(diry + info->d_name); stat(file.c_str(), &ourfs); if (info) printf("File/Dir name is : %s\r\n", info->d_name); } while ( info != NULL ); closedir(ourDir); } stat("c:\\windows", &ourfs); stat("c:\\autoexec.bat", &ourfs); printf("Finished dir read\n"); //test our getopt function char * test_argv[] = { "foobar.exe", "-qwc", "-", "-c", "fgfgfg", "-f", "-l", "hello", "-", "force-sync", NULL }; int test_argc; for (test_argc = 0; test_argv[test_argc]; test_argc++) { } const char* opts = "qwc:l:"; assert(getopt(test_argc, test_argv, opts) == 'q'); assert(getopt(test_argc, test_argv, opts) == 'w'); assert(getopt(test_argc, test_argv, opts) == 'c'); assert(strcmp(optarg, "-") == 0); assert(getopt(test_argc, test_argv, opts) == 'c'); assert(strcmp(optarg, "fgfgfg") == 0); assert(getopt(test_argc, test_argv, opts) == '?'); assert(optopt == 'f'); assert(getopt(test_argc, test_argv, opts) == 'l'); assert(strcmp(optarg, "hello") == 0); assert(getopt(test_argc, test_argv, opts) == -1); // assert(optopt == 0); // no more options assert(strcmp(test_argv[optind], "-") == 0); assert(strcmp(test_argv[optind+1], "force-sync") == 0); //end of getopt test //now test our statfs funct stat("c:\\cert.cer", &ourfs); char *timee; timee = ctime(&ourfs.st_mtime); if (S_ISREG(ourfs.st_mode)) { printf("is a normal file\n"); } else { printf("is a directory?\n"); exit(1); } lstat(getenv("WINDIR"), &ourfs); if ( S_ISDIR(ourfs.st_mode)) { printf("is a directory\n"); } else { printf("is a file?\n"); exit(1); } //test the syslog functions openlog("Box Backup", 0,0); //the old ones are the best... syslog(LOG_ERR, "Hello World"); syslog(LOG_ERR, "Value of int is: %i", 6); closelog(); /* //first off get the path name for the default char buf[MAX_PATH]; GetModuleFileName(NULL, buf, sizeof(buf)); std::string buffer(buf); std::string conf("-c " + buffer.substr(0,(buffer.find("win32test.exe"))) + "bbackupd.conf"); //std::string conf( "-c " + buffer.substr(0,(buffer.find("bbackupd.exe"))) + "bbackupd.conf"); */ return 0; } #endif // WIN32 boxbackup/test/win32/Makefile0000664000175000017500000000020510374104755016676 0ustar siretartsiretarttimezone.exe: timezone.cpp Makefile g++ -g -O0 -mno-cygwin -I../../lib/win32 -o timezone.exe timezone.cpp clean: rm timezone.exe boxbackup/test/httpserver/0000775000175000017500000000000011652362372016506 5ustar siretartsiretartboxbackup/test/httpserver/testfiles/0000775000175000017500000000000011652362372020510 5ustar siretartsiretartboxbackup/test/httpserver/testfiles/httpserver.conf0000664000175000017500000000017610476004573023570 0ustar siretartsiretart AddressPrefix = http://localhost:1080 Server { PidFile = testfiles/httpserver.pid ListenAddresses = inet:localhost:1080 } boxbackup/test/httpserver/testfiles/photos/0000775000175000017500000000000011652362372022024 5ustar siretartsiretartboxbackup/test/httpserver/testfiles/photos/puppy.jpg0000664000175000017500000000001411130655017023666 0ustar siretartsiretartomgpuppies! boxbackup/test/httpserver/testfiles/testrequests.pl0000775000175000017500000000665511162175070023630 0ustar siretartsiretart#!/usr/bin/perl use strict; use LWP::UserAgent; my $url_base = 'http://localhost:1080'; my $ua = LWP::UserAgent->new(env_proxy => 0, keep_alive => 1, timeout => 30); print "GET request...\n"; my $response1 = $ua->get("$url_base/test-one/34/341s/234?p1=vOne&p2=vTwo"); die $response1->content unless $response1->is_success(); my $content = $response1->content(); check_url($content, '/test-one/34/341s/234'); check_params($content, 'p1'=>'vOne','p2'=>'vTwo'); print "POST request...\n"; my %post = ('sdfgksjhdfsd'=>'dfvsiufy2e3434','sxciuhwf8723e4'=>'238947829334', '&sfsfsfskfhs'=>'?hdkfjhsjfds','fdsf=sdf2342'=>'3984sajhksda'); my $response2 = $ua->post("$url_base/tdskjhfsjdkhf2943734?p1=vOne&p2=vTwo", \%post); my $content2 = $response2->content(); check_url($content2, '/tdskjhfsjdkhf2943734'); check_params($content2, %post); print "HEAD request...\n"; my $response3 = $ua->head("$url_base/tdskjhfsdfkjhs"); if($response3->content() ne '') { print "Content not zero length\n"; exit(1); } if($response3->code() != 200) { print "Wrong response code\n"; exit(1); } print "Redirected GET request...\n"; my $response4 = $ua->get("$url_base/redirect?key=value"); exit 4 unless $response4->is_success(); my $content4 = $response4->content(); check_url($content4, '/redirected'); check_params($content4); print "Cookie tests...\n"; # from examples in specs test_cookies('CUSTOMER=WILE_E_COYOTE', 'CUSTOMER=WILE_E_COYOTE'); test_cookies('CUSTOMER="WILE_E_COYOTE"; C2="pants"', 'CUSTOMER=WILE_E_COYOTE', 'C2=pants'); test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001'); test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001', 'SHIPPING=FEDEX'); test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"', 'Customer=WILE_E_COYOTE'); test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" ', 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001'); test_cookies(qq!\$Version="1"; Customer="WILE_E_COYOTE"; \$Path="/acme"; Part_Number="Rocket_Launcher_0001"; \$Path="/acme"; Shipping="FedEx"; \t \$Path="/acme"!, 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001', 'Shipping=FedEx'); # test the server setting cookies in the UA require HTTP::Cookies; $ua->cookie_jar(HTTP::Cookies->new()); $ua->get("$url_base/set-cookie"); test_cookies('', 'SetByServer=Value1'); sub test_cookies { my ($c_str, @cookies) = @_; test_cookies2($c_str, @cookies); $c_str =~ s/;/,/g; test_cookies2($c_str, @cookies); } sub test_cookies2 { my ($c_str, @cookies) = @_; my $r; if($c_str ne '') { $r = $ua->get("$url_base/cookie", 'Cookie' => $c_str); } else { $r = $ua->get("$url_base/cookie"); } my $c = $r->content(); for(@cookies) { unless($c =~ m/COOKIE:$_
/) { print "Cookie $_ not found\n"; exit(1); } } } sub check_url { my ($c,$url) = @_; unless($c =~ m~URI: (.+?)

~) { print "URI not found\n"; exit(1); } if($url ne $1) { print "Wrong URI in content\n"; exit(1); } } sub check_params { my ($c,%params) = @_; while($c =~ m/^PARAM:(.+)=(.+?)
/mg) { if($params{$1} ne $2) { print "$1=$2 not found in response\n"; exit(1); } delete $params{$1} } my @k = keys %params; if($#k != -1) { print "Didn't find all params\n"; exit(1); } } boxbackup/test/httpserver/testfiles/s3simulator.conf0000664000175000017500000000035711170703462023643 0ustar siretartsiretartAccessKey = 0PN5J17HBGZHT7JJ3X82 SecretKey = uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o StoreDirectory = testfiles AddressPrefix = http://localhost:1080 Server { PidFile = testfiles/s3simulator.pid ListenAddresses = inet:localhost:1080 } boxbackup/test/httpserver/testhttpserver.cpp0000664000175000017500000003715411175125345022327 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testhttpserver.cpp // Purpose: Test code for HTTP server class // Created: 26/3/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #ifdef HAVE_SIGNAL_H #include #endif #include #include "autogen_HTTPException.h" #include "HTTPRequest.h" #include "HTTPResponse.h" #include "HTTPServer.h" #include "IOStreamGetLine.h" #include "S3Client.h" #include "S3Simulator.h" #include "ServerControl.h" #include "Test.h" #include "decode.h" #include "encode.h" #include "MemLeakFindOn.h" class TestWebServer : public HTTPServer { public: TestWebServer(); ~TestWebServer(); virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); }; // Build a nice HTML response, so this can also be tested neatly in a browser void TestWebServer::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) { // Test redirection mechanism if(rRequest.GetRequestURI() == "/redirect") { rResponse.SetAsRedirect("/redirected"); return; } // Set a cookie? if(rRequest.GetRequestURI() == "/set-cookie") { rResponse.SetCookie("SetByServer", "Value1"); } #define DEFAULT_RESPONSE_1 "\nTEST SERVER RESPONSE\n

Test response

\n

URI: " #define DEFAULT_RESPONSE_3 "

\n

Query string: " #define DEFAULT_RESPONSE_4 "

\n

Method: " #define DEFAULT_RESPONSE_5 "

\n

Decoded query:
" #define DEFAULT_RESPONSE_6 "

\n

Content type: " #define DEFAULT_RESPONSE_7 "

\n

Content length: " #define DEFAULT_RESPONSE_8 "

\n

Cookies:
\n" #define DEFAULT_RESPONSE_2 "

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

Test response

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

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

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

Query string: p1=vOne&p2=vTwo

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

Method: GET

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

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

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

Content type:

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

Content length: -1

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

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

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

Internal Server Error

\n" "

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

" "

Please try again later.

\n" "\n", response_data); } // S3Client tests { S3Simulator simulator; simulator.Configure("testfiles/s3simulator.conf"); S3Client client(&simulator, "johnsmith.s3.amazonaws.com", "0PN5J17HBGZHT7JJ3X82", "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"); HTTPResponse response = client.GetObject("/photos/puppy.jpg"); TEST_EQUAL(200, response.GetResponseCode()); std::string response_data((const char *)response.GetBuffer(), response.GetSize()); TEST_EQUAL("omgpuppies!\n", response_data); // make sure that assigning to HTTPResponse does clear stream response = client.GetObject("/photos/puppy.jpg"); TEST_EQUAL(200, response.GetResponseCode()); response_data = std::string((const char *)response.GetBuffer(), response.GetSize()); TEST_EQUAL("omgpuppies!\n", response_data); response = client.GetObject("/nonexist"); TEST_EQUAL(404, response.GetResponseCode()); FileStream fs("testfiles/testrequests.pl"); response = client.PutObject("/newfile", fs); TEST_EQUAL(200, response.GetResponseCode()); response = client.GetObject("/newfile"); TEST_EQUAL(200, response.GetResponseCode()); TEST_THAT(fs.CompareWith(response)); TEST_EQUAL(0, ::unlink("testfiles/newfile")); } { HTTPRequest request(HTTPRequest::Method_PUT, "/newfile"); request.SetHostName("quotes.s3.amazonaws.com"); request.AddHeader("date", "Wed, 01 Mar 2006 12:00:00 GMT"); request.AddHeader("authorization", "AWS 0PN5J17HBGZHT7JJ3X82:XtMYZf0hdOo4TdPYQknZk0Lz7rw="); request.AddHeader("Content-Type", "text/plain"); FileStream fs("testfiles/testrequests.pl"); fs.CopyStreamTo(request); request.SetForReading(); CollectInBufferStream response_buffer; HTTPResponse response(&response_buffer); S3Simulator simulator; simulator.Configure("testfiles/s3simulator.conf"); simulator.Handle(request, response); TEST_EQUAL(200, response.GetResponseCode()); TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2")); TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); TEST_EQUAL("", response.GetContentType()); TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); TEST_EQUAL(0, response.GetSize()); FileStream f1("testfiles/testrequests.pl"); FileStream f2("testfiles/newfile"); TEST_THAT(f1.CompareWith(f2)); TEST_EQUAL(0, ::unlink("testfiles/newfile")); } // Start the S3Simulator server pid = LaunchServer("./test s3server testfiles/s3simulator.conf", "testfiles/s3simulator.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid <= 0) { return 0; } sock.Close(); sock.Open(Socket::TypeINET, "localhost", 1080); { HTTPRequest request(HTTPRequest::Method_GET, "/nonexist"); request.SetHostName("quotes.s3.amazonaws.com"); request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:0cSX/YPdtXua1aFFpYmH1tc0ajA="); request.SetClientKeepAliveRequested(true); request.Send(sock, IOStream::TimeOutInfinite); HTTPResponse response; response.Receive(sock); std::string value; TEST_EQUAL(404, response.GetResponseCode()); } #ifndef WIN32 // much harder to make files inaccessible on WIN32 // Make file inaccessible, should cause server to return a 403 error, // unless of course the test is run as root :) { TEST_THAT(chmod("testfiles/testrequests.pl", 0) == 0); HTTPRequest request(HTTPRequest::Method_GET, "/testrequests.pl"); request.SetHostName("quotes.s3.amazonaws.com"); request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8="); request.SetClientKeepAliveRequested(true); request.Send(sock, IOStream::TimeOutInfinite); HTTPResponse response; response.Receive(sock); std::string value; TEST_EQUAL(403, response.GetResponseCode()); TEST_THAT(chmod("testfiles/testrequests.pl", 0755) == 0); } #endif { HTTPRequest request(HTTPRequest::Method_GET, "/testrequests.pl"); request.SetHostName("quotes.s3.amazonaws.com"); request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8="); request.SetClientKeepAliveRequested(true); request.Send(sock, IOStream::TimeOutInfinite); HTTPResponse response; response.Receive(sock); std::string value; TEST_EQUAL(200, response.GetResponseCode()); TEST_EQUAL("qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY", response.GetHeaderValue("x-amz-id-2")); TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); TEST_EQUAL("text/plain", response.GetContentType()); TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); FileStream file("testfiles/testrequests.pl"); TEST_THAT(file.CompareWith(response)); } { HTTPRequest request(HTTPRequest::Method_PUT, "/newfile"); request.SetHostName("quotes.s3.amazonaws.com"); request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:kfY1m6V3zTufRy2kj92FpQGKz4M="); request.AddHeader("Content-Type", "text/plain"); FileStream fs("testfiles/testrequests.pl"); HTTPResponse response; request.SendWithStream(sock, IOStream::TimeOutInfinite /* or 10000 milliseconds */, &fs, response); std::string value; TEST_EQUAL(200, response.GetResponseCode()); TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2")); TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); TEST_EQUAL("", response.GetContentType()); TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); TEST_EQUAL(0, response.GetSize()); FileStream f1("testfiles/testrequests.pl"); FileStream f2("testfiles/newfile"); TEST_THAT(f1.CompareWith(f2)); } // Kill it TEST_THAT(KillServer(pid)); #ifdef WIN32 TEST_THAT(unlink("testfiles/s3simulator.pid") == 0); #else TestRemoteProcessMemLeaks("generic-httpserver.memleaks"); #endif return 0; } boxbackup/test/backupstorefix/0000775000175000017500000000000011652362372017331 5ustar siretartsiretartboxbackup/test/backupstorefix/testbackupstorefix.cpp0000664000175000017500000004523511060457116023771 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testbackupstorefix.cpp // Purpose: Test BackupStoreCheck functionality // Created: 23/4/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include "Test.h" #include "BackupStoreConstants.h" #include "BackupStoreDirectory.h" #include "BackupStoreFile.h" #include "FileStream.h" #include "RaidFileController.h" #include "RaidFileWrite.h" #include "RaidFileRead.h" #include "BackupStoreInfo.h" #include "BackupStoreException.h" #include "RaidFileException.h" #include "StoreStructure.h" #include "BackupStoreFileWire.h" #include "ServerControl.h" #include "MemLeakFindOn.h" /* Errors checked: make some BackupDirectoryStore objects, CheckAndFix(), then verify - multiple objects with same ID - wrong order of old flags - all old flags delete store info add spurious file delete directory (should appear again) change container ID of directory delete a file double reference to a file inside a single dir modify the object ID of a directory delete directory, which has no members (will be removed) extra reference to a file in another dir (higher ID to allow consistency -- use something in subti) delete dir + dir2 in dir/dir2/file where nothing in dir2 except file, file should end up in lost+found similarly with a dir, but that should get a dirxxx name corrupt dir corrupt file delete root, copy a file to it instead (equivalent to deleting it too) */ std::string storeRoot("backup/01234567/"); int discSetNum = 0; std::map nameToID; std::map objectIsDir; #define RUN_CHECK \ ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567"); \ ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567 fix"); // Get ID of an object given a filename int32_t getID(const char *name) { std::map::iterator i(nameToID.find(std::string(name))); TEST_THAT(i != nameToID.end()); if(i == nameToID.end()) return -1; return i->second; } // Get the RAID filename of an object std::string getObjectName(int32_t id) { std::string fn; StoreStructure::MakeObjectFilename(id, storeRoot, discSetNum, fn, false); return fn; } // Delete an object void DeleteObject(const char *name) { RaidFileWrite del(discSetNum, getObjectName(getID(name))); del.Delete(); } // Load a directory void LoadDirectory(const char *name, BackupStoreDirectory &rDir) { std::auto_ptr file(RaidFileRead::Open(discSetNum, getObjectName(getID(name)))); rDir.ReadFromStream(*file, IOStream::TimeOutInfinite); } // Save a directory back again void SaveDirectory(const char *name, const BackupStoreDirectory &rDir) { RaidFileWrite d(discSetNum, getObjectName(getID(name))); d.Open(true /* allow overwrite */); rDir.WriteToStream(d); d.Commit(true /* write now! */); } void CorruptObject(const char *name, int start, const char *rubbish) { int rubbish_len = ::strlen(rubbish); std::string fn(getObjectName(getID(name))); std::auto_ptr r(RaidFileRead::Open(discSetNum, fn)); RaidFileWrite w(discSetNum, fn); w.Open(true /* allow overwrite */); // Copy beginning char buf[2048]; r->Read(buf, start, IOStream::TimeOutInfinite); w.Write(buf, start); // Write rubbish r->Seek(rubbish_len, IOStream::SeekType_Relative); w.Write(rubbish, rubbish_len); // Copy rest of file r->CopyStreamTo(w); r->Close(); // Commit w.Commit(true /* convert now */); } BackupStoreFilename fnames[3]; typedef struct { int name; int64_t id; int flags; } dir_en_check; void check_dir(BackupStoreDirectory &dir, dir_en_check *ck) { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en; while((en = i.Next()) != 0) { TEST_THAT(ck->name != -1); if(ck->name == -1) { break; } TEST_THAT(en->GetName() == fnames[ck->name]); TEST_THAT(en->GetObjectID() == ck->id); TEST_THAT(en->GetFlags() == ck->flags); ++ck; } TEST_THAT(en == 0); TEST_THAT(ck->name == -1); } typedef struct { int64_t id, depNewer, depOlder; } checkdepinfoen; void check_dir_dep(BackupStoreDirectory &dir, checkdepinfoen *ck) { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en; while((en = i.Next()) != 0) { TEST_THAT(ck->id != -1); if(ck->id == -1) { break; } TEST_THAT(en->GetObjectID() == ck->id); TEST_THAT(en->GetDependsNewer() == ck->depNewer); TEST_THAT(en->GetDependsOlder() == ck->depOlder); ++ck; } TEST_THAT(en == 0); TEST_THAT(ck->id == -1); } void test_dir_fixing() { { MEMLEAKFINDER_NO_LEAKS; fnames[0].SetAsClearFilename("x1"); fnames[1].SetAsClearFilename("x2"); fnames[2].SetAsClearFilename("x3"); } { BackupStoreDirectory dir; dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2); dir.AddEntry(fnames[1], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2); dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2); dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); dir_en_check ck[] = { {1, 2, BackupStoreDirectory::Entry::Flags_File}, {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, {0, 5, BackupStoreDirectory::Entry::Flags_File}, {-1, 0, 0} }; TEST_THAT(dir.CheckAndFix() == true); TEST_THAT(dir.CheckAndFix() == false); check_dir(dir, ck); } { BackupStoreDirectory dir; dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2); dir.AddEntry(fnames[1], 12, 10 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_OldVersion, 2); dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); dir_en_check ck[] = { {0, 2, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, {1, 10, BackupStoreDirectory::Entry::Flags_Dir}, {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, {0, 5, BackupStoreDirectory::Entry::Flags_File}, {-1, 0, 0} }; TEST_THAT(dir.CheckAndFix() == true); TEST_THAT(dir.CheckAndFix() == false); check_dir(dir, ck); } // Test dependency fixing { BackupStoreDirectory dir; BackupStoreDirectory::Entry *e2 = dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); TEST_THAT(e2 != 0); e2->SetDependsNewer(3); BackupStoreDirectory::Entry *e3 = dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); TEST_THAT(e3 != 0); e3->SetDependsNewer(4); e3->SetDependsOlder(2); BackupStoreDirectory::Entry *e4 = dir.AddEntry(fnames[0], 12, 4 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); TEST_THAT(e4 != 0); e4->SetDependsNewer(5); e4->SetDependsOlder(3); BackupStoreDirectory::Entry *e5 = dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2); TEST_THAT(e5 != 0); e5->SetDependsOlder(4); // This should all be nice and valid TEST_THAT(dir.CheckAndFix() == false); static checkdepinfoen c1[] = {{2, 3, 0}, {3, 4, 2}, {4, 5, 3}, {5, 0, 4}, {-1, 0, 0}}; check_dir_dep(dir, c1); // Check that dependency forwards are restored e4->SetDependsOlder(34343); TEST_THAT(dir.CheckAndFix() == true); TEST_THAT(dir.CheckAndFix() == false); check_dir_dep(dir, c1); // Check that a spurious depends older ref is undone e2->SetDependsOlder(1); TEST_THAT(dir.CheckAndFix() == true); TEST_THAT(dir.CheckAndFix() == false); check_dir_dep(dir, c1); // Now delete an entry, and check it's cleaned up nicely dir.DeleteEntry(3); TEST_THAT(dir.CheckAndFix() == true); TEST_THAT(dir.CheckAndFix() == false); static checkdepinfoen c2[] = {{4, 5, 0}, {5, 0, 4}, {-1, 0, 0}}; check_dir_dep(dir, c2); } } int test(int argc, const char *argv[]) { // Test the backupstore directory fixing test_dir_fixing(); // Initialise the raidfile controller RaidFileController &rcontroller = RaidFileController::GetController(); rcontroller.Initialise("testfiles/raidfile.conf"); // Create an account TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf " "create 01234567 0 10000B 20000B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // Start the bbstored server int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf", "testfiles/bbstored.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { ::sleep(1); TEST_THAT(ServerIsAlive(pid)); // Run the perl script to create the initial directories TEST_THAT_ABORTONFAIL(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl init") == 0); std::string cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; int bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); if(bbackupd_pid > 0) { ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); // Wait 4 more seconds for the files to be old enough // to upload ::safe_sleep(4); // Upload files to create a nice store directory ::sync_and_wait(); // Stop bbackupd #ifdef WIN32 terminate_bbackupd(bbackupd_pid); // implicit check for memory leaks #else TEST_THAT(KillServer(bbackupd_pid)); TestRemoteProcessMemLeaks("bbackupd.memleaks"); #endif } // Generate a list of all the object IDs TEST_THAT_ABORTONFAIL(::system(BBACKUPQUERY " -Wwarning " "-c testfiles/bbackupd.conf \"list -r\" quit " "> testfiles/initial-listing.txt") == 0); // And load it in { FILE *f = ::fopen("testfiles/initial-listing.txt", "r"); TEST_THAT_ABORTONFAIL(f != 0); char line[512]; int32_t id; char flags[32]; char name[256]; while(::fgets(line, sizeof(line), f) != 0) { TEST_THAT(::sscanf(line, "%x %s %s", &id, flags, name) == 3); bool isDir = (::strcmp(flags, "-d---") == 0); //TRACE3("%x,%d,%s\n", id, isDir, name); MEMLEAKFINDER_NO_LEAKS; nameToID[std::string(name)] = id; objectIsDir[id] = isDir; } ::fclose(f); } // ------------------------------------------------------------------------------------------------ ::printf(" === Delete store info, add random file\n"); { // Delete store info RaidFileWrite del(discSetNum, storeRoot + "info"); del.Delete(); } { // Add a spurious file RaidFileWrite random(discSetNum, storeRoot + "randomfile"); random.Open(); random.Write("test", 4); random.Commit(true); } // Fix it RUN_CHECK // Check everything is as it was TEST_THAT(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl check 0") == 0); // Check the random file doesn't exist { TEST_THAT(!RaidFileRead::FileExists(discSetNum, storeRoot + "01/randomfile")); } // ------------------------------------------------------------------------------------------------ ::printf(" === Delete an entry for an object from dir, change that object to be a patch, check it's deleted\n"); { // Open dir and find entry int64_t delID = getID("Test1/cannes/ict/metegoguered/oats"); { BackupStoreDirectory dir; LoadDirectory("Test1/cannes/ict/metegoguered", dir); TEST_THAT(dir.FindEntryByID(delID) != 0); dir.DeleteEntry(delID); SaveDirectory("Test1/cannes/ict/metegoguered", dir); } // Adjust that entry // // IMPORTANT NOTE: There's a special hack in testbackupstorefix.pl to make sure that // the file we're modifiying has at least two blocks so we can modify it and produce a valid file // which will pass the verify checks. // std::string fn(getObjectName(delID)); { std::auto_ptr file(RaidFileRead::Open(discSetNum, fn)); RaidFileWrite f(discSetNum, fn); f.Open(true /* allow overwrite */); // Make a copy of the original file->CopyStreamTo(f); // Move to header in both file->Seek(0, IOStream::SeekType_Absolute); BackupStoreFile::MoveStreamPositionToBlockIndex(*file); f.Seek(file->GetPosition(), IOStream::SeekType_Absolute); // Read header struct { file_BlockIndexHeader hdr; file_BlockIndexEntry e[2]; } h; TEST_THAT(file->Read(&h, sizeof(h)) == sizeof(h)); file->Close(); // Modify TEST_THAT(box_ntoh64(h.hdr.mOtherFileID) == 0); TEST_THAT(box_ntoh64(h.hdr.mNumBlocks) >= 2); h.hdr.mOtherFileID = box_hton64(2345); // don't worry about endianness h.e[0].mEncodedSize = box_hton64((box_ntoh64(h.e[0].mEncodedSize)) + (box_ntoh64(h.e[1].mEncodedSize))); h.e[1].mOtherBlockIndex = box_hton64(static_cast(-2)); // Write to modified file f.Write(&h, sizeof(h)); // Commit new version f.Commit(true /* write now! */); } // Fix it RUN_CHECK // Check TEST_THAT(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl check 1") == 0); // Check the modified file doesn't exist TEST_THAT(!RaidFileRead::FileExists(discSetNum, fn)); } // ------------------------------------------------------------------------------------------------ ::printf(" === Delete directory, change container ID of another, duplicate entry in dir, spurious file size, delete file\n"); { BackupStoreDirectory dir; LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir); dir.SetContainerID(73773); SaveDirectory("Test1/foreomizes/stemptinevidate/ict", dir); } int64_t duplicatedID = 0; int64_t notSpuriousFileSize = 0; { BackupStoreDirectory dir; LoadDirectory("Test1/cannes/ict/peep", dir); // Duplicate the second entry { BackupStoreDirectory::Iterator i(dir); i.Next(); BackupStoreDirectory::Entry *en = i.Next(); TEST_THAT(en != 0); duplicatedID = en->GetObjectID(); dir.AddEntry(*en); } // Adjust file size of first file { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File); TEST_THAT(en != 0); notSpuriousFileSize = en->GetSizeInBlocks(); en->SetSizeInBlocks(3473874); TEST_THAT(en->GetSizeInBlocks() == 3473874); } SaveDirectory("Test1/cannes/ict/peep", dir); } // Delete a directory DeleteObject("Test1/pass/cacted/ming"); // Delete a file DeleteObject("Test1/cannes/ict/scely"); // Fix it RUN_CHECK // Check everything is as it should be TEST_THAT(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl check 2") == 0); { BackupStoreDirectory dir; LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir); TEST_THAT(dir.GetContainerID() == getID("Test1/foreomizes/stemptinevidate")); } { BackupStoreDirectory dir; LoadDirectory("Test1/cannes/ict/peep", dir); BackupStoreDirectory::Iterator i(dir); // Count the number of entries with the ID which was duplicated int count = 0; BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { if(en->GetObjectID() == duplicatedID) { ++count; } } TEST_THAT(count == 1); // Check file size has changed { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File); TEST_THAT(en != 0); TEST_THAT(en->GetSizeInBlocks() == notSpuriousFileSize); } } // ------------------------------------------------------------------------------------------------ ::printf(" === Modify the obj ID of dir, delete dir with no members, add extra reference to a file\n"); // Set bad object ID { BackupStoreDirectory dir; LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir); dir.TESTONLY_SetObjectID(73773); SaveDirectory("Test1/foreomizes/stemptinevidate/ict", dir); } // Delete dir with no members DeleteObject("Test1/dir-no-members"); // Add extra reference { BackupStoreDirectory dir; LoadDirectory("Test1/divel", dir); BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File); TEST_THAT(en != 0); BackupStoreDirectory dir2; LoadDirectory("Test1/divel/torsines/cruishery", dir2); dir2.AddEntry(*en); SaveDirectory("Test1/divel/torsines/cruishery", dir2); } // Fix it RUN_CHECK // Check everything is as it should be TEST_THAT(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl check 3") == 0); { BackupStoreDirectory dir; LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir); TEST_THAT(dir.GetObjectID() == getID("Test1/foreomizes/stemptinevidate/ict")); } // ------------------------------------------------------------------------------------------------ ::printf(" === Orphan files and dirs without being recoverable\n"); DeleteObject("Test1/dir1"); DeleteObject("Test1/dir1/dir2"); // Fix it RUN_CHECK // Check everything is where it is predicted to be TEST_THAT(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl check 4") == 0); // ------------------------------------------------------------------------------------------------ ::printf(" === Corrupt file and dir\n"); // File CorruptObject("Test1/foreomizes/stemptinevidate/algoughtnerge", 33, "34i729834298349283479233472983sdfhasgs"); // Dir CorruptObject("Test1/cannes/imulatrougge/foreomizes",23, "dsf32489sdnadf897fd2hjkesdfmnbsdfcsfoisufio2iofe2hdfkjhsf"); // Fix it RUN_CHECK // Check everything is where it should be TEST_THAT(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl check 5") == 0); // ------------------------------------------------------------------------------------------------ ::printf(" === Overwrite root with a file\n"); { std::auto_ptr r(RaidFileRead::Open(discSetNum, getObjectName(getID("Test1/pass/shuted/brightinats/milamptimaskates")))); RaidFileWrite w(discSetNum, getObjectName(1 /* root */)); w.Open(true /* allow overwrite */); r->CopyStreamTo(w); w.Commit(true /* convert now */); } // Fix it RUN_CHECK // Check everything is where it should be TEST_THAT(::system(PERL_EXECUTABLE " testfiles/testbackupstorefix.pl reroot 6") == 0); // --------------------------------------------------------- // Stop server TEST_THAT(KillServer(pid)); #ifdef WIN32 TEST_THAT(unlink("testfiles/bbstored.pid") == 0); #else TestRemoteProcessMemLeaks("bbstored.memleaks"); #endif } return 0; } boxbackup/test/backupstorefix/testextra0000664000175000017500000000021510347400657021274 0ustar siretartsiretartmkdir testfiles/0_0 mkdir testfiles/0_1 mkdir testfiles/0_2 mkdir testfiles/bbackupd-data cp ../../../test/bbackupd/testfiles/*.* testfiles/ boxbackup/test/backupstorefix/testfiles/0000775000175000017500000000000011652362372021333 5ustar siretartsiretartboxbackup/test/backupstorefix/testfiles/testbackupstorefix.pl.in0000775000175000017500000001246611017275716026242 0ustar siretartsiretart#!@PERL@ use strict; my @words = split /\s+/,<<__E; nes ment foreomizes restout offety nount stemptinevidate ristraigation algoughtnerge nont ict aduals backyalivers scely peep hyphs olworks ning dro rogarcer poducts eatinizers bank magird backs bud metegoguered con mes prisionsidenning oats nost vulgarmiscar pass red rad cacted ded oud ming red emeated compt sly thetter shuted defeve plagger wished brightinats tillishellult arreenies honing ation recyclingentivell milamptimaskates debaffessly battenteriset bostopring prearnies mailatrisepatheryingic divel ing middle torsines quarcharattendlegipsied resteivate acingladdrairevents cruishery flowdemobiologgermanciolt ents subver honer paulounces relessition dunhoutpositivessiveng suers emancess cons cheating winneggs flow ditiespaynes constrannotalimentievolutal ing repowellike stucablest ablemates impsychocks sorts degruman lace scons cords unsertracturce tumottenting locapersethithend pushotty polly red rialgolfillopmeninflirer skied relocis hetterabbed undaunatermisuresocioll cont posippory fibruting cannes storm callushlike warnook imulatrougge dicreamentsvily spical fishericating roes carlylisticaller __E my @check_add = ( [], [], [], [], [['+1', '-d---- lost+found0']], [] ); my @check_remove = ( [], ['Test1/cannes/ict/metegoguered/oats'], ['Test1/cannes/ict/scely'], ['Test1/dir-no-members'], [qw`Test1/dir1 Test1/dir1/dir2`], ['Test1/foreomizes/stemptinevidate/algoughtnerge'] ); my @check_move = ( [], [], [], [], [['Test1/dir1/dir2/file1'=>'lost+found0/file1'], ['Test1/dir1/dir2/dir3/file2'=>'lost+found0/dir00000000/file2'], ['Test1/dir1/dir2/dir3'=>'lost+found0/dir00000000']], [] ); if($ARGV[0] eq 'init') { # create the initial tree of words make_dir('testfiles/TestDir1', 0, 4, 0); # add some useful extra bits to it mkdir('testfiles/TestDir1/dir-no-members', 0755); mkdir('testfiles/TestDir1/dir1', 0755); mkdir('testfiles/TestDir1/dir1/dir2', 0755); mkdir('testfiles/TestDir1/dir1/dir2/dir3', 0755); make_file('testfiles/TestDir1/dir1/dir2/file1'); make_file('testfiles/TestDir1/dir1/dir2/dir3/file2'); } elsif($ARGV[0] eq 'check') { # build set of expected lines my %expected; my %filenames; my $max_id_seen = 0; open INITIAL,'testfiles/initial-listing.txt' or die "Can't open original listing"; while() { chomp; s/\r//; $expected{$_} = 1; m/\A(.+?) .+? (.+)\Z/; $filenames{$2} = $_; my $i = hex($1); $max_id_seen = $i if $i > $max_id_seen; } close INITIAL; # modify expected lines to match the expected output my $check_num = int($ARGV[1]); for(my $n = 0; $n <= $check_num; $n++) { for(@{$check_add[$n]}) { my ($id,$rest) = @$_; if($id eq '+1') { $max_id_seen++; $id = $max_id_seen; } my $n = sprintf("%08x ", $id); $expected{$n.$rest} = 1 } for(@{$check_remove[$n]}) { delete $expected{$filenames{$_}} } for(@{$check_move[$n]}) { my ($from,$to) = @$_; my $orig = $filenames{$from}; delete $expected{$filenames{$from}}; my ($id,$type) = split / /,$orig; $expected{"$id $type $to"} = 1 } } # read in the new listing, and compare open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " . "-c testfiles/bbackupd.conf " . "\"list -r\" quit |" or die "Can't open list utility"; open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt' or die "can't open copy listing file"; my $err = 0; while() { print LISTING_COPY; chomp; s/\r//; s/\[FILENAME NOT ENCRYPTED\]//; if(exists $expected{$_}) { delete $expected{$_} } else { $err = 1; print "Unexpected object $_ in new output\n" } } close LISTING_COPY; close LISTING; # check for anything which didn't appear but should have done for(keys %expected) { $err = 1; print "Expected object $_ not found in new output\n" } exit $err; } elsif($ARGV[0] eq 'reroot') { open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " . "-c testfiles/bbackupd.conf " . "\"list -r\" quit |" or die "Can't open list utility"; open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt' or die "can't open copy listing file"; my $err = 0; my $count = 0; while() { print LISTING_COPY; chomp; s/\[FILENAME NOT ENCRYPTED\]//; my ($id,$type,$name) = split / /; $count++; if($name !~ /\Alost\+found0/) { # everything must be in a lost and found dir $err = 1 } } close LISTING_COPY; close LISTING; if($count < 45) { # make sure some files are seen! $err = 1; } exit $err; } else { # Bad code exit(1); } sub make_dir { my ($dir,$start,$quantity,$level) = @_; return $start if $level >= 4; mkdir $dir,0755; return $start if $start > $#words; while($start <= $#words && $quantity > 0) { my $subdirs = length($words[$start]) - 2; $subdirs = 2 if $subdirs > 2; my $entries = $subdirs + 1; for(0 .. ($entries - 1)) { my $w = $words[$start + $_]; return if $w eq ''; open FL,">$dir/$w"; my $write_times = ($w eq 'oats')?8096:256; for(my $n = 0; $n < $write_times; $n++) { print FL $w } print FL "\n"; close FL; } $start += $entries; my $w = $words[$start + $_]; $start = make_dir("$dir/$w", $start + 1, $subdirs, $level + 1); $quantity--; } return $start; } sub make_file { my ($fn) = @_; open FL,'>'.$fn or die "can't open $fn for writing"; for(0 .. 255) { print FL $fn } close FL; } boxbackup/test/basicserver/0000775000175000017500000000000011652362372016610 5ustar siretartsiretartboxbackup/test/basicserver/TestCommands.cpp0000664000175000017500000000563710462244713021724 0ustar siretartsiretart #include "Box.h" #ifdef HAVE_SYSLOG_H #include #endif #include "autogen_TestProtocolServer.h" #include "CollectInBufferStream.h" #include "MemLeakFindOn.h" std::auto_ptr TestProtocolServerHello::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { if(mNumber32 != 41 || mNumber16 != 87 || mNumber8 != 11 || mText != "pingu") { return std::auto_ptr(new TestProtocolServerError(0, 0)); } return std::auto_ptr(new TestProtocolServerHello(12,89,22,std::string("Hello world!"))); } std::auto_ptr TestProtocolServerLists::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { return std::auto_ptr(new TestProtocolServerListsReply(mLotsOfText.size())); } std::auto_ptr TestProtocolServerQuit::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { return std::auto_ptr(new TestProtocolServerQuit); } std::auto_ptr TestProtocolServerSimple::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { return std::auto_ptr(new TestProtocolServerSimpleReply(mValue+1)); } class UncertainBufferStream : public CollectInBufferStream { public: // make the collect in buffer stream pretend not to know how many bytes are left pos_type BytesLeftToRead() { return IOStream::SizeOfStreamUnknown; } }; std::auto_ptr TestProtocolServerGetStream::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { // make a new stream object CollectInBufferStream *pstream = mUncertainSize?(new UncertainBufferStream):(new CollectInBufferStream); // Data. int values[24273]; int v = mStartingValue; for(int l = 0; l < 3; ++l) { for(int x = 0; x < 24273; ++x) { values[x] = v++; } pstream->Write(values, sizeof(values)); } // Finished pstream->SetForReading(); // Get it to be sent rProtocol.SendStreamAfterCommand(pstream); return std::auto_ptr(new TestProtocolServerGetStream(mStartingValue, mUncertainSize)); } std::auto_ptr TestProtocolServerSendStream::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { if(mValue != 0x73654353298ffLL) { return std::auto_ptr(new TestProtocolServerError(0, 0)); } // Get a stream std::auto_ptr stream(rProtocol.ReceiveStream()); bool uncertain = (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown); // Count how many bytes in it int bytes = 0; char buffer[125]; while(stream->StreamDataLeft()) { bytes += stream->Read(buffer, sizeof(buffer)); } // tell the caller how many bytes there were return std::auto_ptr(new TestProtocolServerGetStream(bytes, uncertain)); } std::auto_ptr TestProtocolServerString::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { return std::auto_ptr(new TestProtocolServerString(mTest)); } boxbackup/test/basicserver/testprotocol.txt0000664000175000017500000000126710347400657022117 0ustar siretartsiretart# test protocol file Name Test IdentString Test-0.00 ServerContextClass TestContext TestContext.h BEGIN_OBJECTS Error 0 IsError(Type,SubType) Reply int32 Type int32 SubType Hello 1 Command(Hello) Reply int32 Number32 int16 Number16 int8 Number8 string Text Lists 2 Command(ListsReply) vector LotsOfText ListsReply 3 Reply int32 NumberOfStrings Quit 4 Command(Quit) Reply EndsConversation Simple 5 Command(SimpleReply) int32 Value SimpleReply 6 Reply int32 ValuePlusOne GetStream 7 Command(GetStream) Reply int32 StartingValue bool UncertainSize SendStream 8 Command(GetStream) StreamWithCommand int64 Value String 9 Command(String) Reply string Test boxbackup/test/basicserver/TestContext.cpp0000664000175000017500000000023410347400657021576 0ustar siretartsiretart #include "Box.h" #include "Test.h" #include "TestContext.h" #include "MemLeakFindOn.h" TestContext::TestContext() { } TestContext::~TestContext() { } boxbackup/test/basicserver/testbasicserver.cpp0000664000175000017500000004171210775523641022534 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testbasicserver.cpp // Purpose: Test basic server classes // Created: 2003/07/29 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include "Test.h" #include "Daemon.h" #include "Configuration.h" #include "ServerStream.h" #include "SocketStream.h" #include "IOStreamGetLine.h" #include "ServerTLS.h" #include "CollectInBufferStream.h" #include "TestContext.h" #include "autogen_TestProtocolClient.h" #include "autogen_TestProtocolServer.h" #include "ServerControl.h" #include "MemLeakFindOn.h" #define SERVER_LISTEN_PORT 2003 // in ms #define COMMS_READ_TIMEOUT 4 #define COMMS_SERVER_WAIT_BEFORE_REPLYING 40 class basicdaemon : public Daemon { public: basicdaemon() {}; ~basicdaemon() {} virtual void Run(); }; void basicdaemon::Run() { // Write a file to check it's done... const Configuration &c(GetConfiguration()); FILE *f = fopen(c.GetKeyValue("TestFile").c_str(), "w"); fclose(f); while(!StopRun()) { ::sleep(10); } } void testservers_pause_before_reply() { #ifdef WIN32 Sleep(COMMS_SERVER_WAIT_BEFORE_REPLYING); #else struct timespec t; t.tv_sec = 0; t.tv_nsec = COMMS_SERVER_WAIT_BEFORE_REPLYING * 1000 * 1000; // convert to ns ::nanosleep(&t, NULL); #endif } #define LARGE_DATA_BLOCK_SIZE 19870 #define LARGE_DATA_SIZE (LARGE_DATA_BLOCK_SIZE*1000) void testservers_connection(SocketStream &rStream) { IOStreamGetLine getline(rStream); if(typeid(rStream) == typeid(SocketStreamTLS)) { // need to wait for some data before sending stuff, otherwise timeout test doesn't work std::string line; while(!getline.GetLine(line)) ; SocketStreamTLS &rtls = (SocketStreamTLS&)rStream; std::string line1("CONNECTED:"); line1 += rtls.GetPeerCommonName(); line1 += '\n'; testservers_pause_before_reply(); rStream.Write(line1.c_str(), line1.size()); } while(!getline.IsEOF()) { std::string line; while(!getline.GetLine(line)) ; if(line == "QUIT") { break; } if(line == "LARGEDATA") { { // Send lots of data char data[LARGE_DATA_BLOCK_SIZE]; for(unsigned int y = 0; y < sizeof(data); y++) { data[y] = y & 0xff; } for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) { rStream.Write(data, sizeof(data)); } } { // Receive lots of data char buf[1024]; int total = 0; int r = 0; while(total < LARGE_DATA_SIZE && (r = rStream.Read(buf, sizeof(buf))) != 0) { total += r; } TEST_THAT(total == LARGE_DATA_SIZE); if (total != LARGE_DATA_SIZE) { BOX_ERROR("Expected " << LARGE_DATA_SIZE << " bytes " << "but was " << total); return; } } { // Send lots of data again char data[LARGE_DATA_BLOCK_SIZE]; for(unsigned int y = 0; y < sizeof(data); y++) { data[y] = y & 0xff; } for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) { rStream.Write(data, sizeof(data)); } } // next! continue; } std::string backwards; for(std::string::const_reverse_iterator i(line.end()); i != std::string::const_reverse_iterator(line.begin()); ++i) { backwards += (*i); } backwards += '\n'; testservers_pause_before_reply(); rStream.Write(backwards.c_str(), backwards.size()); } rStream.Shutdown(); rStream.Close(); } class testserver : public ServerStream { public: testserver() {} ~testserver() {} void Connection(SocketStream &rStream); virtual const char *DaemonName() const { return "test-srv2"; } const ConfigurationVerify *GetConfigVerify() const; }; const ConfigurationVerify *testserver::GetConfigVerify() const { static ConfigurationVerifyKey verifyserverkeys[] = { SERVERSTREAM_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses }; static ConfigurationVerify verifyserver[] = { { "Server", 0, verifyserverkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; static ConfigurationVerify verify = { "root", verifyserver, 0, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; return &verify; } void testserver::Connection(SocketStream &rStream) { testservers_connection(rStream); } class testProtocolServer : public testserver { public: testProtocolServer() {} ~testProtocolServer() {} void Connection(SocketStream &rStream); virtual const char *DaemonName() const { return "test-srv4"; } }; void testProtocolServer::Connection(SocketStream &rStream) { TestProtocolServer server(rStream); TestContext context; server.DoServer(context); } class testTLSserver : public ServerTLS { public: testTLSserver() {} ~testTLSserver() {} void Connection(SocketStreamTLS &rStream); virtual const char *DaemonName() const { return "test-srv3"; } const ConfigurationVerify *GetConfigVerify() const; }; const ConfigurationVerify *testTLSserver::GetConfigVerify() const { static ConfigurationVerifyKey verifyserverkeys[] = { SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses }; static ConfigurationVerify verifyserver[] = { { "Server", 0, verifyserverkeys, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; static ConfigurationVerify verify = { "root", verifyserver, 0, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; return &verify; } void testTLSserver::Connection(SocketStreamTLS &rStream) { testservers_connection(rStream); } void Srv2TestConversations(const std::vector &conns) { const static char *tosend[] = { "test 1\n", "carrots\n", "pineapples\n", "booo!\n", 0 }; const static char *recieve[] = { "1 tset", "storrac", "selppaenip", "!ooob", 0 }; IOStreamGetLine **getline = new IOStreamGetLine*[conns.size()]; for(unsigned int c = 0; c < conns.size(); ++c) { getline[c] = new IOStreamGetLine(*conns[c]); bool hadTimeout = false; if(typeid(*conns[c]) == typeid(SocketStreamTLS)) { SocketStreamTLS *ptls = (SocketStreamTLS *)conns[c]; printf("Connected to '%s'\n", ptls->GetPeerCommonName().c_str()); // Send some data, any data, to get the first response. conns[c]->Write("Hello\n", 6); std::string line1; while(!getline[c]->GetLine(line1, false, COMMS_READ_TIMEOUT)) hadTimeout = true; TEST_THAT(line1 == "CONNECTED:CLIENT"); TEST_THAT(hadTimeout) } } for(int q = 0; tosend[q] != 0; ++q) { for(unsigned int c = 0; c < conns.size(); ++c) { //printf("%d: %s", c, tosend[q]); conns[c]->Write(tosend[q], strlen(tosend[q])); std::string rep; bool hadTimeout = false; while(!getline[c]->GetLine(rep, false, COMMS_READ_TIMEOUT)) hadTimeout = true; TEST_THAT(rep == recieve[q]); TEST_THAT(hadTimeout) } } for(unsigned int c = 0; c < conns.size(); ++c) { conns[c]->Write("LARGEDATA\n", 10); } for(unsigned int c = 0; c < conns.size(); ++c) { // Receive lots of data char buf[1024]; int total = 0; int r = 0; while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0) { total += r; } TEST_THAT(total == LARGE_DATA_SIZE); } for(unsigned int c = 0; c < conns.size(); ++c) { // Send lots of data char data[LARGE_DATA_BLOCK_SIZE]; for(unsigned int y = 0; y < sizeof(data); y++) { data[y] = y & 0xff; } for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) { conns[c]->Write(data, sizeof(data)); } } for(unsigned int c = 0; c < conns.size(); ++c) { // Receive lots of data again char buf[1024]; int total = 0; int r = 0; while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0) { total += r; } TEST_THAT(total == LARGE_DATA_SIZE); } for(unsigned int c = 0; c < conns.size(); ++c) { conns[c]->Write("QUIT\n", 5); } for(unsigned int c = 0; c < conns.size(); ++c) { if ( getline[c] ) delete getline[c]; getline[c] = 0; } if ( getline ) delete [] getline; getline = 0; } void TestStreamReceive(TestProtocolClient &protocol, int value, bool uncertainstream) { std::auto_ptr reply(protocol.QueryGetStream(value, uncertainstream)); TEST_THAT(reply->GetStartingValue() == value); // Get a stream std::auto_ptr stream(protocol.ReceiveStream()); // check uncertainty TEST_THAT(uncertainstream == (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown)); printf("stream is %s\n", uncertainstream?"uncertain size":"fixed size"); // Then check the contents int values[998]; int v = value; int count = 0; int bytesleft = 0; int bytessofar = 0; while(stream->StreamDataLeft()) { // Read some data int bytes = stream->Read(((char*)values) + bytesleft, sizeof(values) - bytesleft); bytessofar += bytes; bytes += bytesleft; int n = bytes / 4; //printf("read %d, n = %d, so far = %d\n", bytes, n, bytessofar); for(int t = 0; t < n; ++t) { if(values[t] != v) printf("%d, %d, %d\n", t, values[t], v); TEST_THAT(values[t] == v++); } count += n; bytesleft = bytes - (n*4); if(bytesleft) ::memmove(values, ((char*)values) + bytes - bytesleft, bytesleft); } TEST_THAT(bytesleft == 0); TEST_THAT(count == (24273*3)); // over 64 k of data, definately } int test(int argc, const char *argv[]) { // Server launching stuff if(argc >= 2) { // this is a quick hack to allow passing some options // to the daemon const char* mode = argv[1]; if (test_args.length() > 0) { argv[1] = test_args.c_str(); } else { argc--; argv++; } if(strcmp(mode, "srv1") == 0) { // Run very basic daemon basicdaemon daemon; return daemon.Main("doesnotexist", argc, argv); } else if(strcmp(mode, "srv2") == 0) { // Run daemon which accepts connections testserver daemon; return daemon.Main("doesnotexist", argc, argv); } else if(strcmp(mode, "srv3") == 0) { testTLSserver daemon; return daemon.Main("doesnotexist", argc, argv); } else if(strcmp(mode, "srv4") == 0) { testProtocolServer daemon; return daemon.Main("doesnotexist", argc, argv); } } //printf("SKIPPING TESTS------------------------\n"); //goto protocolserver; // Launch a basic server { std::string cmd = "./test --test-daemon-args="; cmd += test_args; cmd += " srv1 testfiles/srv1.conf"; int pid = LaunchServer(cmd, "testfiles/srv1.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { // Check that it's written the expected file TEST_THAT(TestFileExists("testfiles" DIRECTORY_SEPARATOR "srv1.test1")); TEST_THAT(ServerIsAlive(pid)); // Move the config file over #ifdef WIN32 TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "srv1.conf") != -1); #endif TEST_THAT(::rename( "testfiles" DIRECTORY_SEPARATOR "srv1b.conf", "testfiles" DIRECTORY_SEPARATOR "srv1.conf") != -1); #ifndef WIN32 // Get it to reread the config file TEST_THAT(HUPServer(pid)); ::sleep(1); TEST_THAT(ServerIsAlive(pid)); // Check that new file exists TEST_THAT(TestFileExists("testfiles" DIRECTORY_SEPARATOR "srv1.test2")); #endif // !WIN32 // Kill it off TEST_THAT(KillServer(pid)); #ifndef WIN32 TestRemoteProcessMemLeaks( "generic-daemon.memleaks"); #endif // !WIN32 } } // Launch a test forking server { std::string cmd = "./test --test-daemon-args="; cmd += test_args; cmd += " srv2 testfiles/srv2.conf"; int pid = LaunchServer(cmd, "testfiles/srv2.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { // Will it restart? TEST_THAT(ServerIsAlive(pid)); #ifndef WIN32 TEST_THAT(HUPServer(pid)); ::sleep(1); TEST_THAT(ServerIsAlive(pid)); #endif // !WIN32 // Make some connections { SocketStream conn1; conn1.Open(Socket::TypeINET, "localhost", 2003); #ifndef WIN32 SocketStream conn2; conn2.Open(Socket::TypeUNIX, "testfiles/srv2.sock"); SocketStream conn3; conn3.Open(Socket::TypeINET, "localhost", 2003); #endif // !WIN32 // Quick check that reconnections fail TEST_CHECK_THROWS(conn1.Open(Socket::TypeUNIX, "testfiles/srv2.sock");, ServerException, SocketAlreadyOpen); // Stuff some data around std::vector conns; conns.push_back(&conn1); #ifndef WIN32 conns.push_back(&conn2); conns.push_back(&conn3); #endif // !WIN32 Srv2TestConversations(conns); // Implicit close } #ifndef WIN32 // HUP again TEST_THAT(HUPServer(pid)); ::sleep(1); TEST_THAT(ServerIsAlive(pid)); #endif // !WIN32 // Kill it TEST_THAT(KillServer(pid)); ::sleep(1); TEST_THAT(!ServerIsAlive(pid)); #ifndef WIN32 TestRemoteProcessMemLeaks("test-srv2.memleaks"); #endif // !WIN32 } } // Launch a test SSL server { std::string cmd = "./test --test-daemon-args="; cmd += test_args; cmd += " srv3 testfiles/srv3.conf"; int pid = LaunchServer(cmd, "testfiles/srv3.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { // Will it restart? TEST_THAT(ServerIsAlive(pid)); #ifndef WIN32 TEST_THAT(HUPServer(pid)); ::sleep(1); TEST_THAT(ServerIsAlive(pid)); #endif // Make some connections { // SSL library SSLLib::Initialise(); // Context first TLSContext context; context.Initialise(false /* client */, "testfiles/clientCerts.pem", "testfiles/clientPrivKey.pem", "testfiles/clientTrustedCAs.pem"); SocketStreamTLS conn1; conn1.Open(context, Socket::TypeINET, "localhost", 2003); #ifndef WIN32 SocketStreamTLS conn2; conn2.Open(context, Socket::TypeUNIX, "testfiles/srv3.sock"); SocketStreamTLS conn3; conn3.Open(context, Socket::TypeINET, "localhost", 2003); #endif // Quick check that reconnections fail TEST_CHECK_THROWS(conn1.Open(context, Socket::TypeUNIX, "testfiles/srv3.sock");, ServerException, SocketAlreadyOpen); // Stuff some data around std::vector conns; conns.push_back(&conn1); #ifndef WIN32 conns.push_back(&conn2); conns.push_back(&conn3); #endif Srv2TestConversations(conns); // Implicit close } #ifndef WIN32 // HUP again TEST_THAT(HUPServer(pid)); ::sleep(1); TEST_THAT(ServerIsAlive(pid)); #endif // Kill it TEST_THAT(KillServer(pid)); ::sleep(1); TEST_THAT(!ServerIsAlive(pid)); #ifndef WIN32 TestRemoteProcessMemLeaks("test-srv3.memleaks"); #endif } } //protocolserver: // Launch a test protocol handling server { std::string cmd = "./test --test-daemon-args="; cmd += test_args; cmd += " srv4 testfiles/srv4.conf"; int pid = LaunchServer(cmd, "testfiles/srv4.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { ::sleep(1); TEST_THAT(ServerIsAlive(pid)); // Open a connection to it SocketStream conn; #ifdef WIN32 conn.Open(Socket::TypeINET, "localhost", 2003); #else conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock"); #endif // Create a protocol TestProtocolClient protocol(conn); // Simple query { std::auto_ptr reply(protocol.QuerySimple(41)); TEST_THAT(reply->GetValuePlusOne() == 42); } { std::auto_ptr reply(protocol.QuerySimple(809)); TEST_THAT(reply->GetValuePlusOne() == 810); } // Streams, twice, both uncertain and certain sizes TestStreamReceive(protocol, 374, false); TestStreamReceive(protocol, 23983, true); TestStreamReceive(protocol, 12098, false); TestStreamReceive(protocol, 4342, true); // Try to send a stream { CollectInBufferStream s; char buf[1663]; s.Write(buf, sizeof(buf)); s.SetForReading(); std::auto_ptr reply(protocol.QuerySendStream(0x73654353298ffLL, s)); TEST_THAT(reply->GetStartingValue() == sizeof(buf)); } // Lots of simple queries for(int q = 0; q < 514; q++) { std::auto_ptr reply(protocol.QuerySimple(q)); TEST_THAT(reply->GetValuePlusOne() == (q+1)); } // Send a list of strings to it { std::vector strings; strings.push_back(std::string("test1")); strings.push_back(std::string("test2")); strings.push_back(std::string("test3")); std::auto_ptr reply(protocol.QueryLists(strings)); TEST_THAT(reply->GetNumberOfStrings() == 3); } // And another { std::auto_ptr reply(protocol.QueryHello(41,87,11,std::string("pingu"))); TEST_THAT(reply->GetNumber32() == 12); TEST_THAT(reply->GetNumber16() == 89); TEST_THAT(reply->GetNumber8() == 22); TEST_THAT(reply->GetText() == "Hello world!"); } // Quit query to finish protocol.QueryQuit(); // Kill it TEST_THAT(KillServer(pid)); ::sleep(1); TEST_THAT(!ServerIsAlive(pid)); #ifndef WIN32 TestRemoteProcessMemLeaks("test-srv4.memleaks"); #endif } } return 0; } boxbackup/test/basicserver/testfiles/0000775000175000017500000000000011652362372020612 5ustar siretartsiretartboxbackup/test/basicserver/testfiles/rootkey.pem0000664000175000017500000000156710347400657023021 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS 8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep -----END RSA PRIVATE KEY----- boxbackup/test/basicserver/testfiles/serverPrivKey.pem0000664000175000017500000000156710347400657024145 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40Xs mCEdHnDlAjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrY aQkgME2OmPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQAB AoGBAJSH7zAC9OmXXHoGhWeQEbzO+yT6aHxdY8/KGeBZUMasYB7qqZb8eYWbToYm nS2cpVAh0gHZcfrdyuDwSQpPQIIA8gAPFHqR8T8VGrpChxgetYzkoPDapmcqKU4H YobFVA1gypK1IM5z3Z5kargqGmmzRIxX8BwWr6FGmFPp2+NBAkEA7A17g4JewNtY vtpM0NhIyw+7HN3ljf+pAvHM2pMw1Wk8TrbPJNQ20ZWnhGMdIvP0m25zna6pShL6 0laf5EUWFQJBANx1SJ+Xb3P9IyrIlyMhrsYvAveezh6wimjAFFNYWmGEZ6uuHM5P eBSc3P0x0LbFKlGQWomxMb3ULwpjEueX9HkCQDMf0GpxJ/h5CUV8njp1PX7NT2c3 H+qbPo2mtQl564+tFSSvLzn4xE6sLPXdSYgycf3f9CZol721UqGPpV2ZIOkCQQCQ trxxZmrW7LgFAZ+UhCvCFGISQcB0DNcOY+fzve+2S7/xxl1KYIgmn8HAws6K62oY GHYWJKbOQVaPrvFd7TWhAkA8VQPjDSRkdg2fU5RDTRfOQBczgc8aHTiqAv/S2g47 lpsw8CLitobBvi3e5XuBKNIbnjeoZMbHcBZ+RXAAZe/Q -----END RSA PRIVATE KEY----- boxbackup/test/basicserver/testfiles/srv1.conf0000664000175000017500000000011210347400657022345 0ustar siretartsiretartServer { PidFile = testfiles/srv1.pid } TestFile = testfiles/srv1.test1 boxbackup/test/basicserver/testfiles/root.pem0000664000175000017500000000326510347400657022305 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y 3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG 9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH 1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS 8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep -----END RSA PRIVATE KEY----- boxbackup/test/basicserver/testfiles/rootreq.pem0000664000175000017500000000120710347400657023007 0ustar siretartsiretart-----BEGIN CERTIFICATE REQUEST----- MIIBpDCCAQ0CAQAwZDELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 ZXIxDTALBgNVBAMTBFJPT1QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMxh cn4gjQKT2JyvAN4bKZ1ncCHtE6YLfYulLexBVyFB3vnY7i2DJNQ/p3just0zbGxW N1Ed5v0AP4NWvMM1Fau09jFAzKM+FfB5DP+PDEDJVB3JgQhSR5xgC9YPCm31/rMX kilyrq52TG0TckcKdHfUQ5bUJxy36nKDwoGCPeRTAgMBAAGgADANBgkqhkiG9w0B AQUFAAOBgQCmy4L/D/m1Q23y+WB1Ub2u1efl0sb7zMWNzHsD/IR1CXSvXmAfPpr2 hpJQj118ccaTqkRhA8gwhktMTBuGH5KiOLHYXRlniKo3G0yr0+fHWnjclZ+m6Bg1 9HjJZYqIWRMQ78+wTpLCxliX6yp0JxMdx/v6/7jx3BtXz8cyU8ANAw== -----END CERTIFICATE REQUEST----- boxbackup/test/basicserver/testfiles/clientReq.pem0000664000175000017500000000120710347400657023242 0ustar siretartsiretart-----BEGIN CERTIFICATE REQUEST----- MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 ZXIxDzANBgNVBAMTBkNMSUVOVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA uwCQ0CXRh4uBzu1V2SXYv9wvK0Mdbk8TDukBwRXqdMEGeiN+2XK/k0ax5EPAnpcp iAcdmX0FzCyrKAfBUnCKdMyE/3aytAIIblhOVD1OnZ7T5awOucuNe6pOczY4jsun KFpfu5LWdCLQrsu1R1q05h0WzWXlyrm+347nHAyXYssCAwEAAaAAMA0GCSqGSIb3 DQEBBQUAA4GBAKV3H/yWrYep6yfEDQp61zn60tEnJOS5LVbuV7ivNjAue0/09wBT PGzTblwx116AT9GbTcbERK/ll549+tziTLT9NUT12ZcvaRezYP2PpaD8fiDKHs3D vSwpFoihLmUnDeMWE9Vbt+b0Fl/mdsH6sm3Mo0COG/DkolOVsydOj2Hp -----END CERTIFICATE REQUEST----- boxbackup/test/basicserver/testfiles/clientCerts.pem0000664000175000017500000000147610347400657023603 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIICOTCCAaICAQUwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDQz WhcNMzEwMTIyMTYyNDQzWjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj IHNlcnZlcjEPMA0GA1UEAxMGQ0xJRU5UMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB iQKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Zcr+TRrHk Q8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65y417qk5z NjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQABMA0GCSqG SIb3DQEBBQUAA4GBACYkSYlrKNv1v6lrES4j68S8u8SNlnSM+Z4pTHF/7K7SQeIn SKVV8EI8CLR5jIsQRRHKB9rYgYS4kB8SFbPyrsH8VKngjIUcjmTKLq9zpAt2zDNo m+y5SMXsaJF6Xbtbz+MSxXZZ6YBBuseY+Wkpz4ZGSVlQrHxjsuYdBFHIguM3 -----END CERTIFICATE----- boxbackup/test/basicserver/testfiles/clientTrustedCAs.pem0000664000175000017500000000147610347400657024544 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y 3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG 9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH 1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== -----END CERTIFICATE----- boxbackup/test/basicserver/testfiles/srv3.conf0000664000175000017500000000036110347400657022355 0ustar siretartsiretartServer { PidFile = testfiles/srv3.pid ListenAddresses = inet:localhost,unix:testfiles/srv3.sock CertificateFile = testfiles/serverCerts.pem PrivateKeyFile = testfiles/serverPrivKey.pem TrustedCAsFile = testfiles/serverTrustedCAs.pem } boxbackup/test/basicserver/testfiles/key-creation.txt0000664000175000017500000000622410347400657023750 0ustar siretartsiretart$ openssl genrsa -out rootkey.pem 1024 $ openssl req -new -key rootkey.pem -sha1 -out rootreq.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []:GB State or Province Name (full name) []:London Locality Name (eg, city) []:London Organization Name (eg, company) []:Test Organizational Unit Name (eg, section) []:basic server Common Name (eg, fully qualified host name) []:ROOT Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: $ openssl x509 -req -in rootreq.pem -sha1 -extensions v3_ca -signkey rootkey.pem -out rootcert.pem -days 10000 Signature ok subject=/C=GB/ST=London/L=London/O=Test/OU=basic server/CN=ROOT Getting Private key $ cp rootcert.pem serverTrustedCAs.pem $ cp rootcert.pem clientTrustedCAs.pem $ openssl genrsa -out clientPrivKey.pem 1024 $ openssl req -new -key clientPrivKey.pem -sha1 -out clientReq.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []:GB State or Province Name (full name) []:London Locality Name (eg, city) []:London Organization Name (eg, company) []:Test Organizational Unit Name (eg, section) []:basic server Common Name (eg, fully qualified host name) []:CLIENT Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: $ cat rootcert.pem rootkey.pem > root.pem $ echo 01 > root.srl $ openssl x509 -req -in clientReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out clientCerts.pem -days 10000 $ openssl genrsa -out serverPrivKey.pem 1024 $ openssl req -new -key serverPrivKey.pem -sha1 -out serverReq.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []:GB State or Province Name (full name) []:London Locality Name (eg, city) []:London Organization Name (eg, company) []:Test Organizational Unit Name (eg, section) []:basic server Common Name (eg, fully qualified host name) []:SERVER Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: $ openssl x509 -req -in serverReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out serverCerts.pem -days 10000 boxbackup/test/basicserver/testfiles/root.srl0000664000175000017500000000000310347400657022307 0ustar siretartsiretart07 boxbackup/test/basicserver/testfiles/srv2.conf0000664000175000017500000000014510347400657022354 0ustar siretartsiretartServer { PidFile = testfiles/srv2.pid ListenAddresses = inet:localhost,unix:testfiles/srv2.sock } boxbackup/test/basicserver/testfiles/srv4.conf0000664000175000017500000000014510614736220022351 0ustar siretartsiretartServer { PidFile = testfiles/srv4.pid ListenAddresses = unix:testfiles/srv4.sock,inet:localhost } boxbackup/test/basicserver/testfiles/serverCerts.pem0000664000175000017500000000147610347400657023633 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIICOTCCAaICAQYwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNTA0 WhcNMzEwMTIyMTYyNTA0WjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj IHNlcnZlcjEPMA0GA1UEAxMGU0VSVkVSMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB iQKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40XsmCEdHnDl AjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrYaQkgME2O mPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQABMA0GCSqG SIb3DQEBBQUAA4GBALgh7u/7GZUMjzOPGuIenkdrsP0Gbst7wuXrLaMrAMlAaWMH E9AgU/6Q9+2yFxisgAzRmyKydNP4E4YomsE8rbx08vGw/6Rc7L19/UsFJxeNC5Ue 6hziI9boB9LL5em4N8v+z4yhGvj2CrKzBxLNy8MYPi2S3KfQ69FdipvRQRp/ -----END CERTIFICATE----- boxbackup/test/basicserver/testfiles/clientPrivKey.pem0000664000175000017500000000156710347400657024115 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Z cr+TRrHkQ8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65 y417qk5zNjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQAB AoGAF92enbH158KaMnp/tlLqMrI7It5R5z4YRJLgMnBFl9j6pqPZEI9ge79N/L/Y 2WSZXE7sLCaUktYwkc9LkOXkBYQI7EIOonLdmSsNCMbSBVbeczdM77dBscuCTKva nvre/2+hlmuWBNINqXlprBkvd5YF4Q/yeXzoXPuMIQ0tROECQQDqifOZOfCle8uA CgdHT9pO638PwrrldMHmZSK3gUmHmFe7ziGpNGCfKZ+wkSIvDg9INQvEXvQfLZiV n4J78IOHAkEAzB0SoUU0cL+wK3OQTTOlx4cgxaxgtsuvccIhqTh4Jp1Aj9iMKiPW yXvbGhDBTZP2IL5HoqSLc3SxfXgvn6O/nQJBALgJMYWdalBf2GoK9HUnmpTsw1I5 qe/c8z13RIubvnfQuZ8be1xLRjn+LlkdOSaVMLanMSmQnJxOafmWJYxdSMcCQFBc 5ffe8n2tyyPgdSEgQ5YiatHJQ67U1Te50lz44b16TnAUN2NkBu3/OM2zaRgtOEu9 /yBXHpyPhk47Iqz84LUCQQCIDIKluoughLVjJS2eD28UJHM9Z+OvmyIE0fF0Q0vi E+Rn/+iWCoEJYa7WP5AEo/aeVXiCeHONXGF1AI8a8gb5 -----END RSA PRIVATE KEY----- boxbackup/test/basicserver/testfiles/rootcert.pem0000664000175000017500000000147610347400657023165 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y 3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG 9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH 1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== -----END CERTIFICATE----- boxbackup/test/basicserver/testfiles/serverTrustedCAs.pem0000664000175000017500000000147610347400657024574 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y 3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG 9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH 1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== -----END CERTIFICATE----- boxbackup/test/basicserver/testfiles/srv1b.conf0000664000175000017500000000011210347400657022507 0ustar siretartsiretartServer { PidFile = testfiles/srv1.pid } TestFile = testfiles/srv1.test2 boxbackup/test/basicserver/testfiles/serverReq.pem0000664000175000017500000000120710347400657023272 0ustar siretartsiretart-----BEGIN CERTIFICATE REQUEST----- MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 ZXIxDzANBgNVBAMTBlNFUlZFUjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA y0e7RWnjbwnbwuZ0IH2IIBR8WTOTXLMSUmHBduxoT8pRxONF7JghHR5w5QI1rOPB q2De7X02u/yRBTPbO7IBfTsY/rbITEzWb8duboofIBxQoNEa2GkJIDBNjpj7j7RX AP4rEzAijuw/2htkmqY2PP15HtE7Hijt43z/S7cWc+0CAwEAAaAAMA0GCSqGSIb3 DQEBBQUAA4GBAGdUCS76aBzPw4zcU999r6gE7/F8/bYlT/tr2SEyKzF+vC0widZN P3bg9IaNAWi84vw8WEB+j2wM3TPB5/kSKFpO2MxOHPERX+aOXh6JkN6a/ay5CDOT r/wCERRkqY2gphU5m3/S0Gd7wLbH/neBgNsHUzbNwwQ+uqkF2NRGg0V/ -----END CERTIFICATE REQUEST----- boxbackup/test/basicserver/Makefile.extra0000664000175000017500000000105411345266370021372 0ustar siretartsiretart MAKEPROTOCOL = ../../lib/server/makeprotocol.pl GEN_CMD_SRV = $(MAKEPROTOCOL) Server testprotocol.txt GEN_CMD_CLI = $(MAKEPROTOCOL) Client testprotocol.txt # AUTOGEN SEEDING autogen_TestProtocolServer.cpp: $(MAKEPROTOCOL) testprotocol.txt $(_PERL) $(GEN_CMD_SRV) autogen_TestProtocolServer.h: $(MAKEPROTOCOL) testprotocol.txt $(_PERL) $(GEN_CMD_SRV) # AUTOGEN SEEDING autogen_TestProtocolClient.cpp: $(MAKEPROTOCOL) testprotocol.txt $(_PERL) $(GEN_CMD_CLI) autogen_TestProtocolClient.h: $(MAKEPROTOCOL) testprotocol.txt $(_PERL) $(GEN_CMD_CLI) boxbackup/test/basicserver/TestContext.h0000664000175000017500000000010110347400657021234 0ustar siretartsiretart class TestContext { public: TestContext(); ~TestContext(); }; boxbackup/test/compress/0000775000175000017500000000000011652362372016133 5ustar siretartsiretartboxbackup/test/compress/testcompress.cpp0000664000175000017500000001364510775523641021406 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testcompress.cpp // Purpose: Test lib/compress // Created: 5/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "Test.h" #include "Compress.h" #include "CompressStream.h" #include "CollectInBufferStream.h" #include "MemLeakFindOn.h" #define DATA_SIZE (1024*128+103) #define CHUNK_SIZE 2561 #define DECOMP_CHUNK_SIZE 3 // Stream for testing class CopyInToOutStream : public IOStream { public: CopyInToOutStream() : currentBuffer(0) {buffers[currentBuffer].SetForReading();} ~CopyInToOutStream() {} int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) { if(buffers[currentBuffer].StreamDataLeft()) { return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout); } // Swap buffers? if(buffers[(currentBuffer + 1) & 1].GetSize() > 0) { buffers[currentBuffer].Reset(); currentBuffer = (currentBuffer + 1) & 1; buffers[currentBuffer].SetForReading(); return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout); } return 0; } void Write(const void *pBuffer, int NBytes) { buffers[(currentBuffer + 1) & 1].Write(pBuffer, NBytes); } bool StreamDataLeft() { return buffers[currentBuffer].StreamDataLeft() || buffers[(currentBuffer + 1) % 1].GetSize() > 0; } bool StreamClosed() { return false; } int currentBuffer; CollectInBufferStream buffers[2]; }; // Test stream based interface int test_stream() { // Make a load of compressible data to compress CollectInBufferStream source; uint16_t data[1024]; for(int x = 0; x < 1024; ++x) { data[x] = x; } for(int x = 0; x < (32*1024); ++x) { source.Write(data, (x % 1024) * 2); } source.SetForReading(); // Straight compress from one stream to another { CollectInBufferStream *poutput = new CollectInBufferStream; CompressStream compress(poutput, true /* take ownership */, false /* read */, true /* write */); source.CopyStreamTo(compress); compress.Close(); poutput->SetForReading(); // Check sizes TEST_THAT(poutput->GetSize() < source.GetSize()); BOX_TRACE("compressed size = " << poutput->GetSize() << ", source size = " << source.GetSize()); // Decompress the data { CollectInBufferStream decompressed; CompressStream decompress(poutput, false /* don't take ownership */, true /* read */, false /* write */); decompress.CopyStreamTo(decompressed); decompress.Close(); TEST_THAT(decompressed.GetSize() == source.GetSize()); TEST_THAT(::memcmp(decompressed.GetBuffer(), source.GetBuffer(), decompressed.GetSize()) == 0); } // Don't delete poutput, let mem leak testing ensure it's deleted. } // Set source to the beginning source.Seek(0, IOStream::SeekType_Absolute); // Test where the same stream compresses and decompresses, should be fun! { CollectInBufferStream output; CopyInToOutStream copyer; CompressStream compress(©er, false /* no ownership */, true, true); bool done = false; int count = 0; int written = 0; while(!done) { ++count; bool do_sync = (count % 256) == 0; uint8_t buffer[4096]; int r = source.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite); if(r == 0) { done = true; compress.Close(); } else { compress.Write(buffer, r); written += r; if(do_sync) { compress.WriteAllBuffered(); } } int r2 = 0; do { r2 = compress.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite); if(r2 > 0) { output.Write(buffer, r2); } } while(r2 > 0); if(do_sync && r != 0) { // Check that everything is synced TEST_THAT(output.GetSize() == written); TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0); } } output.SetForReading(); // Test that it's the same TEST_THAT(output.GetSize() == source.GetSize()); TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0); } return 0; } // Test basic interface int test(int argc, const char *argv[]) { // Bad data to compress! char *data = (char *)malloc(DATA_SIZE); for(int l = 0; l < DATA_SIZE; ++l) { data[l] = l*23; } // parameters about compression int maxOutput = Compress_MaxSizeForCompressedData(DATA_SIZE); TEST_THAT(maxOutput >= DATA_SIZE); char *compressed = (char *)malloc(maxOutput); int compressedSize = 0; // Do compression, in small chunks { Compress compress; int in_loc = 0; while(!compress.OutputHasFinished()) { int ins = DATA_SIZE - in_loc; if(ins > CHUNK_SIZE) ins = CHUNK_SIZE; if(ins == 0) { compress.FinishInput(); } else { compress.Input(data + in_loc, ins); } in_loc += ins; // Get output data int s = 0; do { TEST_THAT(compressedSize < maxOutput); s = compress.Output(compressed + compressedSize, maxOutput - compressedSize); compressedSize += s; } while(s > 0); } } // a reasonable test, especially given the compressability of the input data. TEST_THAT(compressedSize < DATA_SIZE); // decompression char *decompressed = (char*)malloc(DATA_SIZE * 2); int decomp_size = 0; { Compress decompress; int in_loc = 0; while(!decompress.OutputHasFinished()) { int ins = compressedSize - in_loc; if(ins > DECOMP_CHUNK_SIZE) ins = DECOMP_CHUNK_SIZE; if(ins == 0) { decompress.FinishInput(); } else { decompress.Input(compressed + in_loc, ins); } in_loc += ins; // Get output data int s = 0; do { TEST_THAT(decomp_size <= DATA_SIZE); s = decompress.Output(decompressed + decomp_size, (DATA_SIZE*2) - decomp_size); decomp_size += s; } while(s > 0); } } TEST_THAT(decomp_size == DATA_SIZE); TEST_THAT(::memcmp(data, decompressed, DATA_SIZE) == 0); ::free(data); ::free(compressed); ::free(decompressed); return test_stream(); } boxbackup/test/crypto/0000775000175000017500000000000011652362372015620 5ustar siretartsiretartboxbackup/test/crypto/testcrypto.cpp0000664000175000017500000002612010347407074020544 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testcrypto.cpp // Purpose: test lib/crypto // Created: 1/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include "Test.h" #include "CipherContext.h" #include "CipherBlowfish.h" #include "CipherAES.h" #include "CipherException.h" #include "RollingChecksum.h" #include "Random.h" #include "MemLeakFindOn.h" #define STRING1 "Mary had a little lamb" #define STRING2 "Skjdf sdjf sjksd fjkhsdfjk hsdfuiohcverfg sdfnj sdfgkljh sdfjb jlhdfvghsdip vjsdfv bsdfhjvg yuiosdvgpvj kvbn m,sdvb sdfuiovg sdfuivhsdfjkv" #define KEY "0123456701234567012345670123456" #define KEY2 "1234567012345670123456A" #define CHECKSUM_DATA_SIZE (128*1024) #define CHECKSUM_BLOCK_SIZE_BASE (65*1024) #define CHECKSUM_BLOCK_SIZE_LAST (CHECKSUM_BLOCK_SIZE_BASE + 64) #define CHECKSUM_ROLLS 16 void check_random_int(uint32_t max) { for(int c = 0; c < 1024; ++c) { uint32_t v = Random::RandomInt(max); TEST_THAT(v >= 0 && v <= max); } } #define ZERO_BUFFER(x) ::memset(x, 0, sizeof(x)); template void test_cipher() { { // Make a couple of cipher contexts CipherContext encrypt1; encrypt1.Reset(); encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); TEST_CHECK_THROWS(encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))), CipherException, AlreadyInitialised); // Encrpt something char buf1[256]; unsigned int buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1)); TEST_THAT(buf1_used >= sizeof(STRING1)); // Decrypt it CipherContext decrypt1; decrypt1.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); char buf1_de[256]; unsigned int buf1_de_used = decrypt1.TransformBlock(buf1_de, sizeof(buf1_de), buf1, buf1_used); TEST_THAT(buf1_de_used == sizeof(STRING1)); TEST_THAT(memcmp(STRING1, buf1_de, sizeof(STRING1)) == 0); // Use them again... char buf1_de2[256]; unsigned int buf1_de2_used = decrypt1.TransformBlock(buf1_de2, sizeof(buf1_de2), buf1, buf1_used); TEST_THAT(buf1_de2_used == sizeof(STRING1)); TEST_THAT(memcmp(STRING1, buf1_de2, sizeof(STRING1)) == 0); // Test the interface char buf2[256]; TEST_CHECK_THROWS(encrypt1.Transform(buf2, sizeof(buf2), STRING1, sizeof(STRING1)), CipherException, BeginNotCalled); TEST_CHECK_THROWS(encrypt1.Final(buf2, sizeof(buf2)), CipherException, BeginNotCalled); encrypt1.Begin(); int e = 0; e = encrypt1.Transform(buf2, sizeof(buf2), STRING2, sizeof(STRING2) - 16); e += encrypt1.Transform(buf2 + e, sizeof(buf2) - e, STRING2 + sizeof(STRING2) - 16, 16); e += encrypt1.Final(buf2 + e, sizeof(buf2) - e); TEST_THAT(e >= (int)sizeof(STRING2)); // Then decrypt char buf2_de[256]; decrypt1.Begin(); TEST_CHECK_THROWS(decrypt1.Transform(buf2_de, 2, buf2, e), CipherException, OutputBufferTooSmall); TEST_CHECK_THROWS(decrypt1.Final(buf2_de, 2), CipherException, OutputBufferTooSmall); int d = decrypt1.Transform(buf2_de, sizeof(buf2_de), buf2, e - 48); d += decrypt1.Transform(buf2_de + d, sizeof(buf2_de) - d, buf2 + e - 48, 48); d += decrypt1.Final(buf2_de + d, sizeof(buf2_de) - d); TEST_THAT(d == sizeof(STRING2)); TEST_THAT(memcmp(STRING2, buf2_de, sizeof(STRING2)) == 0); // Try a reset and rekey encrypt1.Reset(); encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY2, sizeof(KEY2))); buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1)); } // Test initialisation vectors { // Init with random IV CipherContext encrypt2; encrypt2.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); int ivLen; char iv2[BLOCKSIZE]; const void *ivGen = encrypt2.SetRandomIV(ivLen); TEST_THAT(ivLen == BLOCKSIZE); // block size TEST_THAT(ivGen != 0); memcpy(iv2, ivGen, ivLen); char buf3[256]; unsigned int buf3_used = encrypt2.TransformBlock(buf3, sizeof(buf3), STRING2, sizeof(STRING2)); // Encrypt again with different IV char iv3[BLOCKSIZE]; int ivLen3; const void *ivGen3 = encrypt2.SetRandomIV(ivLen3); TEST_THAT(ivLen3 == BLOCKSIZE); // block size TEST_THAT(ivGen3 != 0); memcpy(iv3, ivGen3, ivLen3); // Check the two generated IVs are different TEST_THAT(memcmp(iv2, iv3, BLOCKSIZE) != 0); char buf4[256]; unsigned int buf4_used = encrypt2.TransformBlock(buf4, sizeof(buf4), STRING2, sizeof(STRING2)); // check encryptions are different TEST_THAT(buf3_used == buf4_used); TEST_THAT(memcmp(buf3, buf4, buf3_used) != 0); // Test that decryption with the right IV works CipherContext decrypt2; decrypt2.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY), iv2)); char buf3_de[256]; unsigned int buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used); TEST_THAT(buf3_de_used == sizeof(STRING2)); TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) == 0); // And that using the wrong one doesn't decrypt2.SetIV(iv3); buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used); TEST_THAT(buf3_de_used == sizeof(STRING2)); TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) != 0); } // Test with padding off. { CipherContext encrypt3; encrypt3.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); encrypt3.UsePadding(false); // Should fail because the encrypted size is not a multiple of the block size char buf4[256]; encrypt3.Begin(); ZERO_BUFFER(buf4); int buf4_used = encrypt3.Transform(buf4, sizeof(buf4), STRING2, 6); TEST_CHECK_THROWS(encrypt3.Final(buf4, sizeof(buf4)), CipherException, EVPFinalFailure); // Check a nice encryption with the correct block size CipherContext encrypt4; encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); encrypt4.UsePadding(false); encrypt4.Begin(); ZERO_BUFFER(buf4); buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, 16); buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4)); TEST_THAT(buf4_used == 16); // Check it's encrypted to the same thing as when there's padding on CipherContext encrypt4b; encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); encrypt4b.Begin(); char buf4b[256]; ZERO_BUFFER(buf4b); int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, 16); buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b)); TEST_THAT(buf4b_used == 16+BLOCKSIZE); TEST_THAT(::memcmp(buf4, buf4b, 16) == 0); // Decrypt char buf4_de[256]; CipherContext decrypt4; decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); decrypt4.UsePadding(false); decrypt4.Begin(); ZERO_BUFFER(buf4_de); int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, 16); buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de)); TEST_THAT(buf4_de_used == 16); TEST_THAT(::memcmp(buf4_de, STRING2, 16) == 0); // Test that the TransformBlock thing works as expected too with blocks the same size as the input TEST_THAT(encrypt4.TransformBlock(buf4, 16, STRING2, 16) == 16); // But that it exceptions if we try the trick with padding on encrypt4.UsePadding(true); TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, 16, STRING2, 16), CipherException, OutputBufferTooSmall); } // And again, but with different string size { char buf4[256]; int buf4_used; // Check a nice encryption with the correct block size CipherContext encrypt4; encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); encrypt4.UsePadding(false); encrypt4.Begin(); ZERO_BUFFER(buf4); buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, (BLOCKSIZE*3)); // do three blocks worth buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4)); TEST_THAT(buf4_used == (BLOCKSIZE*3)); // Check it's encrypted to the same thing as when there's padding on CipherContext encrypt4b; encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); encrypt4b.Begin(); char buf4b[256]; ZERO_BUFFER(buf4b); int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, (BLOCKSIZE*3)); buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b)); TEST_THAT(buf4b_used == (BLOCKSIZE*4)); TEST_THAT(::memcmp(buf4, buf4b, (BLOCKSIZE*3)) == 0); // Decrypt char buf4_de[256]; CipherContext decrypt4; decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); decrypt4.UsePadding(false); decrypt4.Begin(); ZERO_BUFFER(buf4_de); int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, (BLOCKSIZE*3)); buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de)); TEST_THAT(buf4_de_used == (BLOCKSIZE*3)); TEST_THAT(::memcmp(buf4_de, STRING2, (BLOCKSIZE*3)) == 0); // Test that the TransformBlock thing works as expected too with blocks the same size as the input TEST_THAT(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)) == (BLOCKSIZE*3)); // But that it exceptions if we try the trick with padding on encrypt4.UsePadding(true); TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)), CipherException, OutputBufferTooSmall); } } int test(int argc, const char *argv[]) { Random::Initialise(); // Cipher type ::printf("Blowfish...\n"); test_cipher(); #ifndef HAVE_OLD_SSL ::printf("AES...\n"); test_cipher(); #else ::printf("Skipping AES -- not supported by version of OpenSSL in use.\n"); #endif ::printf("Misc...\n"); // Check rolling checksums uint8_t *checkdata_blk = (uint8_t *)malloc(CHECKSUM_DATA_SIZE); uint8_t *checkdata = checkdata_blk; RAND_pseudo_bytes(checkdata, CHECKSUM_DATA_SIZE); for(int size = CHECKSUM_BLOCK_SIZE_BASE; size <= CHECKSUM_BLOCK_SIZE_LAST; ++size) { // Test skip-roll code RollingChecksum rollFast(checkdata, size); rollFast.RollForwardSeveral(checkdata, checkdata+size, size, CHECKSUM_ROLLS/2); RollingChecksum calc(checkdata + (CHECKSUM_ROLLS/2), size); TEST_THAT(calc.GetChecksum() == rollFast.GetChecksum()); //printf("size = %d\n", size); // Checksum to roll RollingChecksum roll(checkdata, size); // Roll forward for(int l = 0; l < CHECKSUM_ROLLS; ++l) { // Calculate new one RollingChecksum calc(checkdata, size); //printf("%08X %08X %d %d\n", roll.GetChecksum(), calc.GetChecksum(), checkdata[0], checkdata[size]); // Compare them! TEST_THAT(calc.GetChecksum() == roll.GetChecksum()); // Roll it onwards roll.RollForward(checkdata[0], checkdata[size], size); // increment ++checkdata; } } ::free(checkdata_blk); // Random integers check_random_int(0); check_random_int(1); check_random_int(5); check_random_int(15); // all 1's check_random_int(1022); return 0; } boxbackup/test/raidfile/0000775000175000017500000000000011652362372016057 5ustar siretartsiretartboxbackup/test/raidfile/testextra0000664000175000017500000000021210347400657020017 0ustar siretartsiretartmkdir testfiles/0_0 mkdir testfiles/0_1 mkdir testfiles/0_2 mkdir testfiles/1_0 mkdir testfiles/1_1 mkdir testfiles/1_2 mkdir testfiles/2 boxbackup/test/raidfile/testraidfile.cpp0000664000175000017500000007566011224217235021250 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: test/raidfile/test.cpp // Purpose: Test RaidFile system // Created: 2003/07/08 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #ifdef HAVE_SYSCALL #include #endif #include #include "Test.h" #include "RaidFileController.h" #include "RaidFileWrite.h" #include "RaidFileException.h" #include "RaidFileRead.h" #include "Guards.h" #include "MemLeakFindOn.h" #define RAID_BLOCK_SIZE 2048 #define RAID_NUMBER_DISCS 3 #define TEST_DATA_SIZE (8*1024 + 173) #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE #define TRF_CAN_INTERCEPT #endif #ifdef TRF_CAN_INTERCEPT // function in intercept.cpp for setting up errors void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror); bool intercept_triggered(); void intercept_clear_setup(); #endif // Nice random data for testing written files class R250 { public: // Set up internal state table with 32-bit random numbers. // The bizarre bit-twiddling is because rand() returns 16 bits of which // the bottom bit is always zero! Hence, I use only some of the bits. // You might want to do something better than this.... R250(int seed) : posn1(0), posn2(103) { // populate the state and incr tables srand(seed); for (int i = 0; i != stateLen; ++i) { state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); incrTable[i] = i == stateLen - 1 ? 0 : i + 1; } // stir up the numbers to ensure they're random for (int j = 0; j != stateLen * 4; ++j) (void) next(); } // Returns the next random number. Xor together two elements separated // by 103 mod 250, replacing the first element with the result. Then // increment the two indices mod 250. inline int next() { int ret = (state[posn1] ^= state[posn2]); // xor and replace element posn1 = incrTable[posn1]; // increment indices using lookup table posn2 = incrTable[posn2]; return ret; } private: enum { stateLen = 250 }; // length of the state table int state[stateLen]; // holds the random number state int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen int posn1, posn2; // indices into the state table }; void testReadingFileContents(int set, const char *filename, void *data, int datasize, bool TestRAIDProperties, int UsageInBlocks = -1) { // Work out which disc is the "start" disc. int h = 0; int n = 0; while(filename[n] != 0) { h += filename[n]; n++; } int startDisc = h % RAID_NUMBER_DISCS; //printf("UsageInBlocks = %d\n", UsageInBlocks); // sizes of data to read static int readsizes[] = {2047, 1, 1, 2047, 12, 1, 1, RAID_BLOCK_SIZE - (12+1+1), RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 246, (RAID_BLOCK_SIZE * 3) + 3, 243}; // read the data in to test it char testbuff[(RAID_BLOCK_SIZE * 3) + 128]; // bigger than the max request above! std::auto_ptr pread = RaidFileRead::Open(set, filename); if(UsageInBlocks != -1) { TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); } //printf("%d, %d\n", pread->GetFileSize(), datasize); TEST_THAT(pread->GetFileSize() == datasize); IOStream &readstream1 = *(pread.get()); int dataread = 0; int r; int readsize = readsizes[0]; int bsc = 1; while((r = readstream1.Read(testbuff, readsize)) > 0) { //printf("== read, asked: %d actual: %d\n", readsize, r); TEST_THAT(((dataread+r) == datasize) || r == readsize); TEST_THAT(r > 0); TEST_THAT(readstream1.StreamDataLeft()); // check IOStream interface is correct for(int z = 0; z < r; ++z) { TEST_THAT(((char*)data)[dataread+z] == testbuff[z]); /*if(((char*)data)[dataread+z] != testbuff[z]) { printf("z = %d\n", z); }*/ } // Next size... if(bsc <= (int)((sizeof(readsizes) / sizeof(readsizes[0])) - 1)) { readsize = readsizes[bsc++]; } dataread += r; } TEST_THAT(dataread == datasize); pread->Close(); // open and close it... pread.reset(RaidFileRead::Open(set, filename).release()); if(UsageInBlocks != -1) { TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); } IOStream &readstream2 = *(pread.get()); // positions to try seeking too.. static int seekpos[] = {0, 1, 2, 887, 887+256 /* no seek required */, RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 1, RAID_BLOCK_SIZE - 1, RAID_BLOCK_SIZE*3, RAID_BLOCK_SIZE + 23, RAID_BLOCK_SIZE * 4, RAID_BLOCK_SIZE * 4 + 1}; for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p) { //printf("== seekpos = %d\n", seekpos[p]); // only try if test file size is big enough if(seekpos[p]+256 > datasize) continue; readstream2.Seek(seekpos[p], IOStream::SeekType_Absolute); TEST_THAT(readstream2.Read(testbuff, 256) == 256); TEST_THAT(readstream2.GetPosition() == seekpos[p] + 256); TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0); } // open and close it... pread.reset(RaidFileRead::Open(set, filename).release()); if(UsageInBlocks != -1) { TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); } IOStream &readstream3 = *(pread.get()); int pos = 0; for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p) { // only try if test file size is big enough if(seekpos[p]+256 > datasize) continue; //printf("pos %d, seekpos %d, p %d\n", pos, seekpos[p], p); readstream3.Seek(seekpos[p] - pos, IOStream::SeekType_Relative); TEST_THAT(readstream3.Read(testbuff, 256) == 256); pos = seekpos[p] + 256; TEST_THAT(readstream3.GetPosition() == pos); TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0); } // Straight read of file pread.reset(RaidFileRead::Open(set, filename).release()); if(UsageInBlocks != -1) { TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); } IOStream &readstream4 = *(pread.get()); pos = 0; int bytesread = 0; while((r = readstream4.Read(testbuff, 988)) != 0) { TEST_THAT(readstream4.StreamDataLeft()); // check IOStream interface is behaving as expected // check contents TEST_THAT(::memcmp(((char*)data) + pos, testbuff, r) == 0); // move on pos += r; bytesread += r; } TEST_THAT(!readstream4.StreamDataLeft()); // check IOStream interface is correct pread.reset(); // Be nasty, and create some errors for the RAID stuff to recover from... if(TestRAIDProperties) { char stripe1fn[256], stripe1fnRename[256]; sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename); sprintf(stripe1fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rf-REMOVED", set, startDisc, filename); char stripe2fn[256], stripe2fnRename[256]; sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rf", set, (startDisc + 1) % RAID_NUMBER_DISCS, filename); sprintf(stripe2fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rf-REMOVED", set, (startDisc + 1) % RAID_NUMBER_DISCS, filename); // Read with stripe1 + parity TEST_THAT(::rename(stripe2fn, stripe2fnRename) == 0); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(::rename(stripe2fnRename, stripe2fn) == 0); // Read with stripe2 + parity TEST_THAT(::rename(stripe1fn, stripe1fnRename) == 0); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(::rename(stripe1fnRename, stripe1fn) == 0); // Munged filename for avoidance char mungefilename[256]; char filenamepart[256]; sprintf(filenamepart, "%s.rf", filename); int m = 0, s = 0; while(filenamepart[s] != '\0') { if(filenamepart[s] == '/') { mungefilename[m++] = '_'; } else if(filenamepart[s] == '_') { mungefilename[m++] = '_'; mungefilename[m++] = '_'; } else { mungefilename[m++] = filenamepart[s]; } s++; } mungefilename[m++] = '\0'; char stripe1munge[256]; sprintf(stripe1munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR ".raidfile-unreadable" DIRECTORY_SEPARATOR "%s", set, startDisc, mungefilename); char stripe2munge[256]; sprintf(stripe2munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR ".raidfile-unreadable" DIRECTORY_SEPARATOR "%s", set, (startDisc + 1) % RAID_NUMBER_DISCS, mungefilename); #ifdef TRF_CAN_INTERCEPT // Test I/O errors on opening // stripe 1 intercept_setup_error(stripe1fn, 0, EIO, SYS_open); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(intercept_triggered()); intercept_clear_setup(); // Check that the file was moved correctly. TEST_THAT(TestFileExists(stripe1munge)); TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); // Test error in reading stripe 2 intercept_setup_error(stripe2fn, 0, EIO, SYS_open); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(intercept_triggered()); intercept_clear_setup(); // Check that the file was moved correctly. TEST_THAT(TestFileExists(stripe2munge)); TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); // Test I/O errors on seeking // stripe 1, if the file is bigger than the minimum thing that it'll get seeked for if(datasize > 257) { intercept_setup_error(stripe1fn, 1, EIO, SYS_lseek); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(intercept_triggered()); intercept_clear_setup(); // Check that the file was moved correctly. TEST_THAT(TestFileExists(stripe1munge)); TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); } // Stripe 2, only if the file is big enough to merit this if(datasize > (RAID_BLOCK_SIZE + 4)) { intercept_setup_error(stripe2fn, 1, EIO, SYS_lseek); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(intercept_triggered()); intercept_clear_setup(); // Check that the file was moved correctly. TEST_THAT(TestFileExists(stripe2munge)); TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); } // Test I/O errors on read, but only if the file is size greater than 0 if(datasize > 0) { // Where shall we error after? int errafter = datasize / 4; // Test error in reading stripe 1 intercept_setup_error(stripe1fn, errafter, EIO, SYS_readv); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(intercept_triggered()); intercept_clear_setup(); // Check that the file was moved correctly. TEST_THAT(TestFileExists(stripe1munge)); TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); // Can only test error if file size > RAID_BLOCK_SIZE, as otherwise stripe2 has nothing in it if(datasize > RAID_BLOCK_SIZE) { // Test error in reading stripe 2 intercept_setup_error(stripe2fn, errafter, EIO, SYS_readv); testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); TEST_THAT(intercept_triggered()); intercept_clear_setup(); // Check that the file was moved correctly. TEST_THAT(TestFileExists(stripe2munge)); TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); } } #endif // TRF_CAN_INTERCEPT } } void testReadWriteFileDo(int set, const char *filename, void *data, int datasize, bool DoTransform) { // Work out which disc is the "start" disc. int h = 0; int n = 0; while(filename[n] != 0) { h += filename[n]; n++; } int startDisc = h % RAID_NUMBER_DISCS; // Another to test the transform works OK... RaidFileWrite write4(set, filename); write4.Open(); write4.Write(data, datasize); // This time, don't discard and transform it to a RAID File char writefnPre[256]; sprintf(writefnPre, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rfwX", set, startDisc, filename); TEST_THAT(TestFileExists(writefnPre)); char writefn[256]; sprintf(writefn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rfw", set, startDisc, filename); int usageInBlocks = write4.GetDiscUsageInBlocks(); write4.Commit(DoTransform); // Check that files are nicely done... if(!DoTransform) { TEST_THAT(TestFileExists(writefn)); TEST_THAT(!TestFileExists(writefnPre)); } else { TEST_THAT(!TestFileExists(writefn)); TEST_THAT(!TestFileExists(writefnPre)); // Stripe file sizes int fullblocks = datasize / RAID_BLOCK_SIZE; int leftover = datasize - (fullblocks * RAID_BLOCK_SIZE); int fs1 = -2; if((fullblocks & 1) == 0) { // last block of data will be on the first stripe fs1 = ((fullblocks / 2) * RAID_BLOCK_SIZE) + leftover; } else { // last block is on second stripe fs1 = ((fullblocks / 2)+1) * RAID_BLOCK_SIZE; } char stripe1fn[256]; sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename); TEST_THAT(TestGetFileSize(stripe1fn) == fs1); char stripe2fn[256]; sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rf", set, (startDisc + 1) % RAID_NUMBER_DISCS, filename); TEST_THAT(TestGetFileSize(stripe2fn) == (int)(datasize - fs1)); // Parity file size char parityfn[256]; sprintf(parityfn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" DIRECTORY_SEPARATOR "%s.rf", set, (startDisc + 2) % RAID_NUMBER_DISCS, filename); // Mildly complex calculation unsigned int blocks = datasize / RAID_BLOCK_SIZE; unsigned int bytesOver = datasize % RAID_BLOCK_SIZE; int paritysize = (blocks / 2) * RAID_BLOCK_SIZE; // Then add in stuff for the last couple of blocks if((blocks & 1) == 0) { if(bytesOver == 0) { paritysize += sizeof(RaidFileRead::FileSizeType); } else { paritysize += (bytesOver == sizeof(RaidFileRead::FileSizeType))?(RAID_BLOCK_SIZE+sizeof(RaidFileRead::FileSizeType)):bytesOver; } } else { paritysize += RAID_BLOCK_SIZE; if(bytesOver == 0 || bytesOver >= (RAID_BLOCK_SIZE-sizeof(RaidFileRead::FileSizeType))) { paritysize += sizeof(RaidFileRead::FileSizeType); } } //printf("datasize = %d, calc paritysize = %d, actual size of file = %d\n", datasize, paritysize, TestGetFileSize(parityfn)); TEST_THAT(TestGetFileSize(parityfn) == paritysize); //printf("stripe1 size = %d, stripe2 size = %d, parity size = %d\n", TestGetFileSize(stripe1fn), TestGetFileSize(stripe2fn), TestGetFileSize(parityfn)); // Check that block calculation is correct //printf("filesize = %d\n", datasize); #define TO_BLOCKS_ROUND_UP(x) (((x) + (RAID_BLOCK_SIZE-1)) / RAID_BLOCK_SIZE) TEST_THAT(usageInBlocks == (TO_BLOCKS_ROUND_UP(paritysize) + TO_BLOCKS_ROUND_UP(fs1) + TO_BLOCKS_ROUND_UP(datasize - fs1))); // See about whether or not the files look correct char testblock[1024]; // compiler bug? This can't go in the block below without corrupting stripe2fn... if(datasize > (3*1024)) { int f; TEST_THAT((f = ::open(stripe1fn, O_RDONLY | O_BINARY, 0)) != -1); TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock))); for(unsigned int q = 0; q < sizeof(testblock); ++q) { TEST_THAT(testblock[q] == ((char*)data)[q]); } ::close(f); TEST_THAT((f = ::open(stripe2fn, O_RDONLY | O_BINARY, 0)) != -1); TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock))); for(unsigned int q = 0; q < sizeof(testblock); ++q) { TEST_THAT(testblock[q] == ((char*)data)[q+RAID_BLOCK_SIZE]); } ::close(f); } } // See if the contents look right testReadingFileContents(set, filename, data, datasize, DoTransform /* only test RAID stuff if it has been transformed to RAID */, usageInBlocks); } void testReadWriteFile(int set, const char *filename, void *data, int datasize) { // Test once, transforming it... testReadWriteFileDo(set, filename, data, datasize, true); // And then again, not transforming it std::string fn(filename); fn += "NT"; testReadWriteFileDo(set, fn.c_str(), data, datasize, false); } bool list_matches(const std::vector &rList, const char *compareto[]) { // count in compare to int count = 0; while(compareto[count] != 0) count++; if((int)rList.size() != count) { return false; } // Space for bools bool *found = new bool[count]; for(int c = 0; c < count; ++c) { found[c] = false; } for(int c = 0; c < count; ++c) { bool f = false; for(int l = 0; l < (int)rList.size(); ++l) { if(rList[l] == compareto[c]) { f = true; break; } } found[c] = f; } bool ret = true; for(int c = 0; c < count; ++c) { if(found[c] == false) { ret = false; } } delete [] found; return ret; } void test_overwrites() { // Opening twice is bad { RaidFileWrite writeA(0, "overwrite_A"); writeA.Open(); writeA.Write("TESTTEST", 8); { #if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK RaidFileWrite writeA2(0, "overwrite_A"); TEST_CHECK_THROWS(writeA2.Open(), RaidFileException, FileIsCurrentlyOpenForWriting); #endif } } // But opening a file which has previously been open, but isn't now, is OK. // Generate a random pre-existing write file (and ensure that it doesn't exist already) int f; TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "overwrite_B.rfwX", O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0755)) != -1); TEST_THAT(::write(f, "TESTTEST", 8) == 8); ::close(f); // Attempt to overwrite it, which should work nicely. RaidFileWrite writeB(0, "overwrite_B"); writeB.Open(); writeB.Write("TEST", 4); TEST_THAT(writeB.GetFileSize() == 4); writeB.Commit(); } int test(int argc, const char *argv[]) { #ifndef TRF_CAN_INTERCEPT printf("NOTE: Skipping intercept based tests on this platform.\n\n"); #endif // Initialise the controller RaidFileController &rcontroller = RaidFileController::GetController(); rcontroller.Initialise("testfiles" DIRECTORY_SEPARATOR "raidfile.conf"); // some data char data[TEST_DATA_SIZE]; R250 random(619); for(unsigned int l = 0; l < sizeof(data); ++l) { data[l] = random.next() & 0xff; } char data2[57]; for(unsigned int l = 0; l < sizeof(data2); ++l) { data2[l] = l; } // Try creating a directory RaidFileWrite::CreateDirectory(0, "test-dir"); TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "test-dir")); TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "test-dir")); TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "test-dir")); TEST_THAT(RaidFileRead::DirectoryExists(0, "test-dir")); TEST_THAT(!RaidFileRead::DirectoryExists(0, "test-dir-not")); // Test converting to disc set names { std::string n1(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 0)); std::string n2(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 1)); std::string n3(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 2)); std::string n4(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 3)); TEST_THAT(n1 != n2); TEST_THAT(n2 != n3); TEST_THAT(n1 != n3); TEST_THAT(n1 == n4); // ie wraps around BOX_TRACE("Gen paths = '" << n1 << "', '" << n2 << "', '" << n3); } // Test that creating and deleting a RaidFile with the wrong // reference counts throws the expected errors. { RaidFileWrite write1(0, "write1", 1); write1.Open(); write1.Commit(); TEST_CHECK_THROWS(write1.Delete(), RaidFileException, RequestedDeleteReferencedFile); } { RaidFileWrite write1(0, "write1", 0); write1.Open(true); TEST_CHECK_THROWS(write1.Commit(), RaidFileException, RequestedModifyUnreferencedFile); write1.Delete(); } { TEST_CHECK_THROWS(RaidFileWrite write1(0, "write1", 2), RaidFileException, RequestedModifyMultiplyReferencedFile); } // Create a RaidFile RaidFileWrite write1(0, "test1"); IOStream &write1stream = write1; // use the stream interface where possible write1.Open(); write1stream.Write(data, sizeof(data)); write1stream.Seek(1024, IOStream::SeekType_Absolute); write1stream.Write(data2, sizeof(data2)); write1stream.Seek(1024, IOStream::SeekType_Relative); write1stream.Write(data2, sizeof(data2)); write1stream.Seek(0, IOStream::SeekType_End); write1stream.Write(data, sizeof(data)); // Before it's deleted, check to see the contents are as expected int f; TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "test1.rfwX", O_RDONLY | O_BINARY, 0)) >= 0); char buffer[sizeof(data)]; int bytes_read = ::read(f, buffer, sizeof(buffer)); TEST_THAT(bytes_read == sizeof(buffer)); for(unsigned int l = 0; l < 1024; ++l) { TEST_THAT(buffer[l] == data[l]); } for(unsigned int l = 0; l < sizeof(data2); ++l) { TEST_THAT(buffer[l+1024] == data2[l]); } for(unsigned int l = 0; l < sizeof(data2); ++l) { TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]); } TEST_THAT(::lseek(f, sizeof(data), SEEK_SET) == sizeof(buffer)); bytes_read = ::read(f, buffer, sizeof(buffer)); TEST_THAT(bytes_read == sizeof(buffer)); for(unsigned int l = 0; l < 1024; ++l) { TEST_THAT(buffer[l] == data[l]); } // make sure that's the end of the file TEST_THAT(::read(f, buffer, sizeof(buffer)) == 0); ::close(f); // Commit the data write1.Commit(); TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) >= 0); ::close(f); // Now try and read it { std::auto_ptr pread = RaidFileRead::Open(0, "test1"); TEST_THAT(pread->GetFileSize() == sizeof(buffer)*2); char buffer[sizeof(data)]; TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer)); for(unsigned int l = 0; l < 1024; ++l) { TEST_THAT(buffer[l] == data[l]); } for(unsigned int l = 0; l < sizeof(data2); ++l) { TEST_THAT(buffer[l+1024] == data2[l]); } for(unsigned int l = 0; l < sizeof(data2); ++l) { TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]); } pread->Seek(sizeof(data), IOStream::SeekType_Absolute); TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer)); for(unsigned int l = 0; l < 1024; ++l) { TEST_THAT(buffer[l] == data[l]); } // make sure that's the end of the file TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); // Seek backwards a bit pread->Seek(-1024, IOStream::SeekType_Relative); TEST_THAT(pread->Read(buffer, 1024) == 1024); // make sure that's the end of the file TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); // Test seeking to end works pread->Seek(-1024, IOStream::SeekType_Relative); TEST_THAT(pread->Read(buffer, 512) == 512); pread->Seek(0, IOStream::SeekType_End); TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); } // Delete it RaidFileWrite writeDel(0, "test1"); writeDel.Delete(); // And again... RaidFileWrite write2(0, "test1"); write2.Open(); write2.Write(data, sizeof(data)); // This time, discard it write2.Discard(); TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) == -1); // And leaving it there... RaidFileWrite writeLeave(0, "test1"); writeLeave.Open(); writeLeave.Write(data, sizeof(data)); // This time, commit it writeLeave.Commit(); TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) != -1); ::close(f); // Then check that the thing will refuse to open it again. RaidFileWrite write3(0, "test1"); TEST_CHECK_THROWS(write3.Open(), RaidFileException, CannotOverwriteExistingFile); // Test overwrite behaviour test_overwrites(); // Then... open it again allowing overwrites RaidFileWrite write3b(0, "test1"); write3b.Open(true); // Write something write3b.Write(data + 3, sizeof(data) - 3); write3b.Commit(); // Test it testReadingFileContents(0, "test1", data+3, sizeof(data) - 3, false /* TestRAIDProperties */); // And once again, but this time making it a raid file RaidFileWrite write3c(0, "test1"); write3c.Open(true); // Write something write3c.Write(data + 7, sizeof(data) - 7); write3c.Commit(true); // make RAID // Test it testReadingFileContents(0, "test1", data+7, sizeof(data) - 7, false /*TestRAIDProperties*/); // Test opening a file which doesn't exist TEST_CHECK_THROWS( std::auto_ptr preadnotexist = RaidFileRead::Open(1, "doesnt-exist"), RaidFileException, RaidFileDoesntExist); { // Test unrecoverable damage RaidFileWrite w(0, "damage"); w.Open(); w.Write(data, sizeof(data)); w.Commit(true); // Try removing the parity file TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf", "testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf-NT") == 0); { std::auto_ptr pr0 = RaidFileRead::Open(0, "damage"); pr0->Read(buffer, sizeof(data)); } TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf-NT", "testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0); // Delete one of the files TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "damage.rf") == 0); // stripe 1 #ifdef TRF_CAN_INTERCEPT // Open it and read... { intercept_setup_error("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "damage.rf", 0, EIO, SYS_read); // stripe 2 std::auto_ptr pr1 = RaidFileRead::Open(0, "damage"); TEST_CHECK_THROWS( pr1->Read(buffer, sizeof(data)), RaidFileException, OSError); TEST_THAT(intercept_triggered()); intercept_clear_setup(); } #endif //TRF_CAN_INTERCEPT // Delete another TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0); // parity TEST_CHECK_THROWS( std::auto_ptr pread2 = RaidFileRead::Open(0, "damage"), RaidFileException, FileIsDamagedNotRecoverable); } // Test reading a directory { RaidFileWrite::CreateDirectory(0, "dirread"); // Make some contents RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "dfsdf1"); RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "ponwq2"); { RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "sdf9873241"); w.Open(); w.Write(data, sizeof(data)); w.Commit(true); } { RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "fsdcxjni3242"); w.Open(); w.Write(data, sizeof(data)); w.Commit(true); } { RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "cskjnds3"); w.Open(); w.Write(data, sizeof(data)); w.Commit(false); } const static char *dir_list1[] = {"dfsdf1", "ponwq2", 0}; const static char *file_list1[] = {"sdf9873241", "fsdcxjni3242", "cskjnds3", 0}; const static char *file_list2[] = {"fsdcxjni3242", "cskjnds3", 0}; std::vector names; TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); TEST_THAT(list_matches(names, file_list1)); TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_DirsOnly, names)); TEST_THAT(list_matches(names, dir_list1)); // Delete things TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); TEST_THAT(list_matches(names, file_list1)); // Delete something else so that it's not recoverable TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); TEST_THAT(false == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); TEST_THAT(list_matches(names, file_list1)); // And finally... TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); TEST_THAT(list_matches(names, file_list2)); } // Check that sizes are reported correctly for non-raid discs { int sizeInBlocks = (sizeof(data) + RAID_BLOCK_SIZE - 1) / RAID_BLOCK_SIZE; // for writing { RaidFileWrite write(2, "testS"); write.Open(); write.Write(data, sizeof(data)); TEST_THAT(write.GetDiscUsageInBlocks() == sizeInBlocks); write.Commit(); } // for reading { std::auto_ptr pread(RaidFileRead::Open(2, "testS")); TEST_THAT(pread->GetDiscUsageInBlocks() == sizeInBlocks); } } //printf("SKIPPING tests ------------------\n"); //return 0; // Test a load of transformed things #define BIG_BLOCK_SIZE (25*1024 + 19) MemoryBlockGuard bigblock(BIG_BLOCK_SIZE); R250 randomX2(2165); for(unsigned int l = 0; l < BIG_BLOCK_SIZE; ++l) { ((char*)(void*)bigblock)[l] = randomX2.next() & 0xff; } // First on one size of data, on different discs testReadWriteFile(0, "testdd", data, sizeof(data)); testReadWriteFile(0, "test2", bigblock, BIG_BLOCK_SIZE); testReadWriteFile(1, "testThree", bigblock, BIG_BLOCK_SIZE - 2048); testReadWriteFile(1, "testX", bigblock, BIG_BLOCK_SIZE - 2289); testReadWriteFile(1, "testSmall0", data, 0); testReadWriteFile(1, "testSmall1", data, 1); testReadWriteFile(1, "testSmall2", data, 2); testReadWriteFile(1, "testSmall3", data, 3); testReadWriteFile(1, "testSmall4", data, 4); testReadWriteFile(0, "testSmall5", data, 5); testReadWriteFile(0, "testSmall6", data, 6); testReadWriteFile(1, "testSmall7", data, 7); testReadWriteFile(1, "testSmall8", data, 8); testReadWriteFile(1, "testSmall9", data, 9); testReadWriteFile(1, "testSmall10", data, 10); // See about a file which is one block bigger than the previous tests { char dataonemoreblock[TEST_DATA_SIZE + RAID_BLOCK_SIZE]; R250 random(715); for(unsigned int l = 0; l < sizeof(dataonemoreblock); ++l) { dataonemoreblock[l] = random.next() & 0xff; } testReadWriteFile(0, "testfour", dataonemoreblock, sizeof(dataonemoreblock)); } // Some more nasty sizes static int nastysize[] = {0, 1, 2, 7, 8, 9, (RAID_BLOCK_SIZE/2)+3, RAID_BLOCK_SIZE-9, RAID_BLOCK_SIZE-8, RAID_BLOCK_SIZE-7, RAID_BLOCK_SIZE-6, RAID_BLOCK_SIZE-5, RAID_BLOCK_SIZE-4, RAID_BLOCK_SIZE-3, RAID_BLOCK_SIZE-2, RAID_BLOCK_SIZE-1}; for(int o = 0; o <= 5; ++o) { for(unsigned int n = 0; n < (sizeof(nastysize)/sizeof(nastysize[0])); ++n) { int s = (o*RAID_BLOCK_SIZE)+nastysize[n]; char fn[64]; sprintf(fn, "testN%d", s); testReadWriteFile(n&1, fn, bigblock, s); } } // Finally, a mega test (not necessary for every run, I would have thought) /* unsigned int megamax = (1024*128) + 9; MemoryBlockGuard megablock(megamax); R250 randomX3(183); for(unsigned int l = 0; l < megamax; ++l) { ((char*)(void*)megablock)[l] = randomX3.next() & 0xff; } for(unsigned int s = 0; s < megamax; ++s) { testReadWriteFile(s & 1, "mega", megablock, s); RaidFileWrite deleter(s & 1, "mega"); deleter.Delete(); RaidFileWrite deleter2(s & 1, "megaNT"); deleter2.Delete(); }*/ return 0; } boxbackup/test/raidfile/testfiles/0000775000175000017500000000000011652362372020061 5ustar siretartsiretartboxbackup/test/raidfile/testfiles/raidfile.conf0000664000175000017500000000050710347400657022510 0ustar siretartsiretart disc0 { SetNumber = 0 BlockSize = 2048 Dir0 = testfiles/0_0 Dir1 = testfiles/0_1 Dir2 = testfiles/0_2 } disc1 { SetNumber = 1 BlockSize = 2048 Dir0 = testfiles/1_0 Dir1 = testfiles/1_1 Dir2 = testfiles/1_2 } disc2 { SetNumber = 2 BlockSize = 2048 Dir0 = testfiles/2 Dir1 = testfiles/2 Dir2 = testfiles/2 } boxbackup/test/backupstorepatch/0000775000175000017500000000000011652362372017642 5ustar siretartsiretartboxbackup/test/backupstorepatch/testbackupstorepatch.cpp0000664000175000017500000004626111345265201024611 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testbackupstorepatch.cpp // Purpose: Test storage of patches on the backup store server // Created: 13/7/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include "autogen_BackupProtocolClient.h" #include "BackupClientCryptoKeys.h" #include "BackupClientFileAttributes.h" #include "BackupStoreAccountDatabase.h" #include "BackupStoreAccounts.h" #include "BackupStoreConstants.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" #include "BackupStoreFilenameClear.h" #include "BackupStoreInfo.h" #include "BoxPortsAndFiles.h" #include "CollectInBufferStream.h" #include "FileStream.h" #include "MemBlockStream.h" #include "RaidFileController.h" #include "RaidFileException.h" #include "RaidFileRead.h" #include "RaidFileUtil.h" #include "RaidFileWrite.h" #include "SSLLib.h" #include "ServerControl.h" #include "Socket.h" #include "SocketStreamTLS.h" #include "StoreStructure.h" #include "TLSContext.h" #include "Test.h" #include "MemLeakFindOn.h" typedef struct { int ChangePoint, InsertBytes, DeleteBytes; int64_t IDOnServer; bool IsCompletelyDifferent; bool HasBeenDeleted; int64_t DepNewer, DepOlder; } file_info; file_info test_files[] = { // ChPnt, Insert, Delete, ID, IsCDf, BeenDel {0, 0, 0, 0, false, false}, // 0 dummy first entry {32000, 2087, 0, 0, false, false}, // 1 {1000, 1998, 2976, 0, false, false}, // 2 {27800, 0, 288, 0, false, false}, // 3 {3208, 1087, 98, 0, false, false}, // 4 {56000, 23087, 98, 0, false, false}, // 5 {0, 98765, 9999999,0, false, false}, // 6 completely different, make a break in the storage {9899, 9887, 2, 0, false, false}, // 7 {12984, 12345, 1234, 0, false, false}, // 8 {1209, 29885, 3498, 0, false, false} // 9 }; // Order in which the files will be removed from the server int test_file_remove_order[] = {0, 2, 3, 5, 8, 1, 4, -1}; #define NUMBER_FILES ((sizeof(test_files) / sizeof(test_files[0]))) #define FIRST_FILE_SIZE (64*1024+3) #define BUFFER_SIZE (256*1024) // Chunk of memory to use for copying files, etc static void *buffer = 0; int64_t ModificationTime = 7766333330000LL; #define MODIFICATION_TIME_INC 10000000; // Nice random data for testing written files class R250 { public: // Set up internal state table with 32-bit random numbers. // The bizarre bit-twiddling is because rand() returns 16 bits of which // the bottom bit is always zero! Hence, I use only some of the bits. // You might want to do something better than this.... R250(int seed) : posn1(0), posn2(103) { // populate the state and incr tables srand(seed); for (int i = 0; i != stateLen; ++i) { state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); incrTable[i] = i == stateLen - 1 ? 0 : i + 1; } // stir up the numbers to ensure they're random for (int j = 0; j != stateLen * 4; ++j) (void) next(); } // Returns the next random number. Xor together two elements separated // by 103 mod 250, replacing the first element with the result. Then // increment the two indices mod 250. inline int next() { int ret = (state[posn1] ^= state[posn2]); // xor and replace element posn1 = incrTable[posn1]; // increment indices using lookup table posn2 = incrTable[posn2]; return ret; } private: enum { stateLen = 250 }; // length of the state table int state[stateLen]; // holds the random number state int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen int posn1, posn2; // indices into the state table }; // will overrun the buffer! void make_random_data(void *buffer, int size, int seed) { R250 rand(seed); int n = (size / sizeof(int)) + 1; int *b = (int*)buffer; for(int l = 0; l < n; ++l) { b[l] = rand.next(); } } bool files_identical(const char *file1, const char *file2) { FileStream f1(file1); FileStream f2(file2); if(f1.BytesLeftToRead() != f2.BytesLeftToRead()) { return false; } while(f1.StreamDataLeft()) { char buffer1[2048]; char buffer2[2048]; int s = f1.Read(buffer1, sizeof(buffer1)); if(f2.Read(buffer2, s) != s) { return false; } if(::memcmp(buffer1, buffer2, s) != 0) { return false; } } if(f2.StreamDataLeft()) { return false; } return true; } void create_test_files() { // Create first file { make_random_data(buffer, FIRST_FILE_SIZE, 98); FileStream out("testfiles/0.test", O_WRONLY | O_CREAT); out.Write(buffer, FIRST_FILE_SIZE); } // Create other files int seed = 987; for(unsigned int f = 1; f < NUMBER_FILES; ++f) { // Open files char fnp[64]; sprintf(fnp, "testfiles/%d.test", f - 1); FileStream previous(fnp); char fnt[64]; sprintf(fnt, "testfiles/%d.test", f); FileStream out(fnt, O_WRONLY | O_CREAT); // Copy up to the change point int b = previous.Read(buffer, test_files[f].ChangePoint, IOStream::TimeOutInfinite); out.Write(buffer, b); // Add new bytes? if(test_files[f].InsertBytes > 0) { make_random_data(buffer, test_files[f].InsertBytes, ++seed); out.Write(buffer, test_files[f].InsertBytes); } // Delete bytes? if(test_files[f].DeleteBytes > 0) { previous.Seek(test_files[f].DeleteBytes, IOStream::SeekType_Relative); } // Copy rest of data b = previous.Read(buffer, BUFFER_SIZE, IOStream::TimeOutInfinite); out.Write(buffer, b); } } void test_depends_in_dirs() { BackupStoreFilenameClear storeFilename("test"); { // Save directory with no dependency info BackupStoreDirectory dir(1000, 1001); // some random ids dir.AddEntry(storeFilename, 1, 2, 3, BackupStoreDirectory::Entry::Flags_File, 4); dir.AddEntry(storeFilename, 1, 3, 3, BackupStoreDirectory::Entry::Flags_File, 4); dir.AddEntry(storeFilename, 1, 4, 3, BackupStoreDirectory::Entry::Flags_File, 4); dir.AddEntry(storeFilename, 1, 5, 3, BackupStoreDirectory::Entry::Flags_File, 4); { FileStream out("testfiles/dir.0", O_WRONLY | O_CREAT); dir.WriteToStream(out); } // Add some dependency info to one of them BackupStoreDirectory::Entry *en = dir.FindEntryByID(3); TEST_THAT(en != 0); en->SetDependsNewer(4); // Save again { FileStream out("testfiles/dir.1", O_WRONLY | O_CREAT); dir.WriteToStream(out); } // Check that the file size increases as expected. TEST_THAT(TestGetFileSize("testfiles/dir.1") == (TestGetFileSize("testfiles/dir.0") + (4*16))); } { // Load the directory back in BackupStoreDirectory dir2; FileStream in("testfiles/dir.1"); dir2.ReadFromStream(in, IOStream::TimeOutInfinite); // Check entries TEST_THAT(dir2.GetNumberOfEntries() == 4); for(int i = 2; i <= 5; ++i) { BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); TEST_THAT(en != 0); TEST_THAT(en->GetDependsNewer() == ((i == 3)?4:0)); TEST_THAT(en->GetDependsOlder() == 0); } dir2.Dump(0, true); // Test that numbers go in and out as required for(int i = 2; i <= 5; ++i) { BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); TEST_THAT(en != 0); en->SetDependsNewer(i + 1); en->SetDependsOlder(i - 1); } // Save { FileStream out("testfiles/dir.2", O_WRONLY | O_CREAT); dir2.WriteToStream(out); } // Load and check { BackupStoreDirectory dir3; FileStream in("testfiles/dir.2"); dir3.ReadFromStream(in, IOStream::TimeOutInfinite); dir3.Dump(0, true); for(int i = 2; i <= 5; ++i) { BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); TEST_THAT(en != 0); TEST_THAT(en->GetDependsNewer() == (i + 1)); TEST_THAT(en->GetDependsOlder() == (i - 1)); } } } } int test(int argc, const char *argv[]) { // Allocate a buffer buffer = ::malloc(BUFFER_SIZE); TEST_THAT(buffer != 0); // SSL library SSLLib::Initialise(); // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used // for seeing what's going on. BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); // Trace errors out SET_DEBUG_SSLLIB_TRACE_ERRORS // Initialise the raid file controller RaidFileController &rcontroller = RaidFileController::GetController(); rcontroller.Initialise("testfiles/raidfile.conf"); // Context TLSContext context; context.Initialise(false /* client */, "testfiles/clientCerts.pem", "testfiles/clientPrivKey.pem", "testfiles/clientTrustedCAs.pem"); // Create an account TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf " "create 01234567 0 30000B 40000B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // Create test files create_test_files(); // Check the basic directory stuff works test_depends_in_dirs(); std::string storeRootDir; int discSet = 0; { std::auto_ptr apDatabase( BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); BackupStoreAccounts accounts(*apDatabase); accounts.GetAccountRoot(0x1234567, storeRootDir, discSet); } RaidFileDiscSet rfd(rcontroller.GetDiscSet(discSet)); int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf", "testfiles/bbstored.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { TEST_THAT(ServerIsAlive(pid)); { // Open a connection to the server SocketStreamTLS conn; conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED_TEST); // Make a protocol BackupProtocolClient protocol(conn); // Login { // Check the version std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); // Login protocol.QueryLogin(0x01234567, 0); } // Filename for server BackupStoreFilenameClear storeFilename("test"); // Upload the first file { std::auto_ptr upload(BackupStoreFile::EncodeFile("testfiles/0.test", BackupProtocolClientListDirectory::RootDirectory, storeFilename)); std::auto_ptr stored(protocol.QueryStoreFile( BackupProtocolClientListDirectory::RootDirectory, ModificationTime, ModificationTime, 0 /* no diff from file ID */, storeFilename, *upload)); test_files[0].IDOnServer = stored->GetObjectID(); test_files[0].IsCompletelyDifferent = true; ModificationTime += MODIFICATION_TIME_INC; } // Upload the other files, using the diffing process for(unsigned int f = 1; f < NUMBER_FILES; ++f) { // Get an index for the previous version std::auto_ptr getBlockIndex(protocol.QueryGetBlockIndexByName( BackupProtocolClientListDirectory::RootDirectory, storeFilename)); int64_t diffFromID = getBlockIndex->GetObjectID(); TEST_THAT(diffFromID != 0); if(diffFromID != 0) { // Found an old version -- get the index std::auto_ptr blockIndexStream(protocol.ReceiveStream()); // Diff the file char filename[64]; ::sprintf(filename, "testfiles/%d.test", f); bool isCompletelyDifferent = false; std::auto_ptr patchStream( BackupStoreFile::EncodeFileDiff( filename, BackupProtocolClientListDirectory::RootDirectory, /* containing directory */ storeFilename, diffFromID, *blockIndexStream, protocol.GetTimeout(), NULL, // DiffTimer impl 0 /* not interested in the modification time */, &isCompletelyDifferent)); // Upload the patch to the store std::auto_ptr stored(protocol.QueryStoreFile( BackupProtocolClientListDirectory::RootDirectory, ModificationTime, ModificationTime, isCompletelyDifferent?(0):(diffFromID), storeFilename, *patchStream)); ModificationTime += MODIFICATION_TIME_INC; // Store details test_files[f].IDOnServer = stored->GetObjectID(); test_files[f].IsCompletelyDifferent = isCompletelyDifferent; #ifdef WIN32 printf("ID %I64d, completely different: %s\n", #else printf("ID %lld, completely different: %s\n", #endif test_files[f].IDOnServer, test_files[f].IsCompletelyDifferent?"yes":"no"); } else { ::printf("WARNING: Block index not obtained when diffing file %d!\n", f); } } // List the directory from the server, and check that no dependency info is sent -- waste of bytes { std::auto_ptr dirreply(protocol.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocol.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { TEST_THAT(en->GetDependsNewer() == 0); TEST_THAT(en->GetDependsOlder() == 0); } } // Finish the connection protocol.QueryFinished(); conn.Close(); } // Fill in initial dependency information for(unsigned int f = 0; f < NUMBER_FILES; ++f) { int64_t newer = (f < (NUMBER_FILES - 1))?test_files[f + 1].IDOnServer:0; int64_t older = (f > 0)?test_files[f - 1].IDOnServer:0; if(test_files[f].IsCompletelyDifferent) { older = 0; } if(f < (NUMBER_FILES - 1) && test_files[f + 1].IsCompletelyDifferent) { newer = 0; } test_files[f].DepNewer = newer; test_files[f].DepOlder = older; } // Check the stuff on the server int deleteIndex = 0; while(true) { // Load up the root directory BackupStoreDirectory dir; { std::auto_ptr dirStream(RaidFileRead::Open(0, "backup/01234567/o01")); dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); dir.Dump(0, true); // Check that dependency info is correct for(unsigned int f = 0; f < NUMBER_FILES; ++f) { //TRACE1("t f = %d\n", f); BackupStoreDirectory::Entry *en = dir.FindEntryByID(test_files[f].IDOnServer); if(en == 0) { TEST_THAT(test_files[f].HasBeenDeleted); // check that unreferenced // object was removed by // housekeeping std::string filenameOut; int startDisc = 0; StoreStructure::MakeObjectFilename( test_files[f].IDOnServer, storeRootDir, discSet, filenameOut, false /* don't bother ensuring the directory exists */); TEST_EQUAL(RaidFileUtil::NoFile, RaidFileUtil::RaidFileExists( rfd, filenameOut)); } else { TEST_THAT(!test_files[f].HasBeenDeleted); TEST_THAT(en->GetDependsNewer() == test_files[f].DepNewer); TEST_THAT(en->GetDependsOlder() == test_files[f].DepOlder); // Test that size is plausible if(en->GetDependsNewer() == 0) { // Should be a full file TEST_THAT(en->GetSizeInBlocks() > 40); } else { // Should be a patch TEST_THAT(en->GetSizeInBlocks() < 40); } } } } // Open a connection to the server (need to do this each time, otherwise housekeeping won't delete files) SocketStreamTLS conn; conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED_TEST); BackupProtocolClient protocol(conn); { std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); protocol.QueryLogin(0x01234567, 0); } // Pull all the files down, and check that they match the files on disc for(unsigned int f = 0; f < NUMBER_FILES; ++f) { ::printf("r=%d, f=%d\n", deleteIndex, f); // Might have been deleted if(test_files[f].HasBeenDeleted) { continue; } // Filenames char filename[64], filename_fetched[64]; ::sprintf(filename, "testfiles/%d.test", f); ::sprintf(filename_fetched, "testfiles/%d.test.fetched", f); ::unlink(filename_fetched); // Fetch the file { std::auto_ptr getobj(protocol.QueryGetFile( BackupProtocolClientListDirectory::RootDirectory, test_files[f].IDOnServer)); TEST_THAT(getobj->GetObjectID() == test_files[f].IDOnServer); // BLOCK { // Get stream std::auto_ptr filestream(protocol.ReceiveStream()); // Get and decode BackupStoreFile::DecodeFile(*filestream, filename_fetched, IOStream::TimeOutInfinite); } } // Test for identicalness TEST_THAT(files_identical(filename_fetched, filename)); // Download the index, and check it looks OK { std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByID(test_files[f].IDOnServer)); TEST_THAT(getblockindex->GetObjectID() == test_files[f].IDOnServer); std::auto_ptr blockIndexStream(protocol.ReceiveStream()); TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(filename, *blockIndexStream, IOStream::TimeOutInfinite)); } } // Close the connection protocol.QueryFinished(); conn.Close(); // Mark one of the elements as deleted if(test_file_remove_order[deleteIndex] == -1) { // Nothing left to do break; } int todel = test_file_remove_order[deleteIndex++]; // Modify the entry BackupStoreDirectory::Entry *pentry = dir.FindEntryByID(test_files[todel].IDOnServer); TEST_THAT(pentry != 0); pentry->AddFlags(BackupStoreDirectory::Entry::Flags_RemoveASAP); // Save it back { RaidFileWrite writedir(0, "backup/01234567/o01"); writedir.Open(true /* overwrite */); dir.WriteToStream(writedir); writedir.Commit(true); } #ifdef WIN32 // Cannot signal bbstored to do housekeeping now, // so just wait until we're sure it's done wait_for_operation(12, "housekeeping to run"); #else // Send the server a restart signal, so it does // housekeeping immediately, and wait for it to happen // Wait for old connections to terminate ::sleep(1); ::kill(pid, SIGHUP); #endif // Get the revision number of the info file int64_t first_revision = 0; RaidFileRead::FileExists(0, "backup/01234567/o01", &first_revision); for(int l = 0; l < 32; ++l) { // Sleep a while, and print a dot ::sleep(1); ::printf("."); ::fflush(stdout); // Early end? if(l > 2) { int64_t revid = 0; RaidFileRead::FileExists(0, "backup/01234567/o01", &revid); if(revid != first_revision) { break; } } } ::printf("\n"); // Flag for test test_files[todel].HasBeenDeleted = true; // Update dependency info int z = todel; while(z > 0 && test_files[z].HasBeenDeleted && test_files[z].DepOlder != 0) { --z; } if(z >= 0) test_files[z].DepNewer = test_files[todel].DepNewer; z = todel; while(z < (int)NUMBER_FILES && test_files[z].HasBeenDeleted && test_files[z].DepNewer != 0) { ++z; } if(z < (int)NUMBER_FILES) test_files[z].DepOlder = test_files[todel].DepOlder; } // Kill store server TEST_THAT(KillServer(pid)); TEST_THAT(!ServerIsAlive(pid)); #ifndef WIN32 TestRemoteProcessMemLeaks("bbstored.memleaks"); #endif } ::free(buffer); return 0; } boxbackup/test/backupstorepatch/testextra0000664000175000017500000000022310347400657021604 0ustar siretartsiretartrm -rf testfiles mkdir testfiles mkdir testfiles/0_0 mkdir testfiles/0_1 mkdir testfiles/0_2 cp ../../../test/backupstore/testfiles/*.* testfiles/ boxbackup/test/backupstore/0000775000175000017500000000000011652362372016622 5ustar siretartsiretartboxbackup/test/backupstore/testextra0000664000175000017500000000013210347400657020563 0ustar siretartsiretartmkdir testfiles/0_0 mkdir testfiles/0_1 mkdir testfiles/0_2 mkdir testfiles/bbackupd-data boxbackup/test/backupstore/testbackupstore.cpp0000664000175000017500000022331011221742616022544 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testbackupstore.cpp // Purpose: Test backup store server // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "Test.h" #include "autogen_BackupProtocolClient.h" #include "SSLLib.h" #include "TLSContext.h" #include "SocketStreamTLS.h" #include "BoxPortsAndFiles.h" #include "BackupStoreConstants.h" #include "Socket.h" #include "BackupStoreFilenameClear.h" #include "CollectInBufferStream.h" #include "BackupStoreDirectory.h" #include "BackupStoreFile.h" #include "FileStream.h" #include "RaidFileController.h" #include "RaidFileWrite.h" #include "BackupStoreInfo.h" #include "BackupStoreException.h" #include "RaidFileException.h" #include "MemBlockStream.h" #include "BackupClientFileAttributes.h" #include "BackupClientCryptoKeys.h" #include "ServerControl.h" #include "BackupStoreAccountDatabase.h" #include "BackupStoreRefCountDatabase.h" #include "BackupStoreAccounts.h" #include "HousekeepStoreAccount.h" #include "MemLeakFindOn.h" #define ENCFILE_SIZE 2765 typedef struct { BackupStoreFilenameClear fn; box_time_t mod; int64_t id; int64_t size; int16_t flags; box_time_t attrmod; } dirtest; static dirtest ens[] = { {BackupStoreFilenameClear(), 324324, 3432, 324, BackupStoreDirectory::Entry::Flags_File, 458763243422LL}, {BackupStoreFilenameClear(), 3432, 32443245645LL, 78, BackupStoreDirectory::Entry::Flags_Dir, 3248972347LL}, {BackupStoreFilenameClear(), 544435, 234234, 23324, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 2348974782LL}, {BackupStoreFilenameClear(), 234, 235436, 6523, BackupStoreDirectory::Entry::Flags_File, 32458923175634LL}, {BackupStoreFilenameClear(), 0x3242343532144LL, 8978979789LL, 21345, BackupStoreDirectory::Entry::Flags_File, 329483243432LL}, {BackupStoreFilenameClear(), 324265765734LL, 12312312321LL, 324987324329874LL, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 32489747234LL}, {BackupStoreFilenameClear(), 3452134, 7868578768LL, 324243, BackupStoreDirectory::Entry::Flags_Dir, 34786457432LL}, {BackupStoreFilenameClear(), 43543543, 324234, 21432, BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_Deleted, 3489723478327LL}, {BackupStoreFilenameClear(), 325654765874324LL, 4353543, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 32489734789237LL}, {BackupStoreFilenameClear(), 32144325, 436547657, 9, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 234897347234LL} }; static const char *ens_filenames[] = {"obj1ertewt", "obj2", "obj3", "obj4dfedfg43", "obj5", "obj6dfgs", "obj7", "obj8xcvbcx", "obj9", "obj10fgjhfg"}; #define DIR_NUM 10 #define DIR_DIRS 3 #define DIR_FILES 7 #define DIR_OLD 2 #define DIR_DELETED 3 typedef struct { const char *fnextra; BackupStoreFilenameClear name; int seed; int size; box_time_t mod_time; int64_t allocated_objid; bool should_be_old_version; bool delete_file; } uploadtest; #define TEST_FILE_FOR_PATCHING "testfiles/test2" // a few bytes will be inserted at this point: #define TEST_FILE_FOR_PATCHING_PATCH_AT ((64*1024)-128) #define TEST_FILE_FOR_PATCHING_SIZE ((128*1024)+2564) #define UPLOAD_PATCH_EN 2 uploadtest uploads[] = { {"0", BackupStoreFilenameClear(), 324, 455, 0, 0, false, false}, {"1", BackupStoreFilenameClear(), 3232432, 2674, 0, 0, true, false}, // old ver {"2", BackupStoreFilenameClear(), 234, TEST_FILE_FOR_PATCHING_SIZE, 0, 0, false, false}, {"3", BackupStoreFilenameClear(), 324324, 6763, 0, 0, false, false}, {"4", BackupStoreFilenameClear(), 23456, 124, 0, 0, true, false}, // old ver {"5", BackupStoreFilenameClear(), 675745, 1, 0, 0, false, false}, // will upload new attrs for this one! {"6", BackupStoreFilenameClear(), 345213, 0, 0, 0, false, false}, {"7", BackupStoreFilenameClear(), 12313, 3246, 0, 0, true, true}, // old ver, will get deleted {"8", BackupStoreFilenameClear(), 457, 3434, 0, 0, false, false}, // overwrites {"9", BackupStoreFilenameClear(), 12315, 446, 0, 0, false, false}, {"a", BackupStoreFilenameClear(), 3476, 2466, 0, 0, false, false}, {"b", BackupStoreFilenameClear(), 124334, 4562, 0, 0, false, false}, {"c", BackupStoreFilenameClear(), 45778, 234, 0, 0, false, false}, // overwrites {"d", BackupStoreFilenameClear(), 2423425, 435, 0, 0, false, true} // overwrites, will be deleted }; static const char *uploads_filenames[] = {"49587fds", "cvhjhj324", "sdfcscs324", "dsfdsvsdc3214", "XXsfdsdf2342", "dsfdsc232", "sfdsdce2345", "YYstfbdtrdf76", "cvhjhj324", "fbfd098.ycy", "dfs98732hj", "svd987kjsad", "XXsfdsdf2342", "YYstfbdtrdf76"}; #define UPLOAD_NUM 14 #define UPLOAD_LATEST_FILES 12 // file we'll upload some new attributes for #define UPLOAD_ATTRS_EN 5 #define UPLOAD_DELETE_EN 13 // file which will be moved (as well as it's old version) #define UPLOAD_FILE_TO_MOVE 8 // Nice random data for testing written files class R250 { public: // Set up internal state table with 32-bit random numbers. // The bizarre bit-twiddling is because rand() returns 16 bits of which // the bottom bit is always zero! Hence, I use only some of the bits. // You might want to do something better than this.... R250(int seed) : posn1(0), posn2(103) { // populate the state and incr tables srand(seed); for (int i = 0; i != stateLen; ++i) { state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); incrTable[i] = i == stateLen - 1 ? 0 : i + 1; } // stir up the numbers to ensure they're random for (int j = 0; j != stateLen * 4; ++j) (void) next(); } // Returns the next random number. Xor together two elements separated // by 103 mod 250, replacing the first element with the result. Then // increment the two indices mod 250. inline int next() { int ret = (state[posn1] ^= state[posn2]); // xor and replace element posn1 = incrTable[posn1]; // increment indices using lookup table posn2 = incrTable[posn2]; return ret; } private: enum { stateLen = 250 }; // length of the state table int state[stateLen]; // holds the random number state int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen int posn1, posn2; // indices into the state table }; int SkipEntries(int e, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) { if(e >= DIR_NUM) return e; bool skip = false; do { skip = false; if(FlagsMustBeSet != BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING) { if((ens[e].flags & FlagsMustBeSet) != FlagsMustBeSet) { skip = true; } } if((ens[e].flags & FlagsNotToBeSet) != 0) { skip = true; } if(skip) { ++e; } } while(skip && e < DIR_NUM); return e; } void CheckEntries(BackupStoreDirectory &rDir, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) { int e = 0; BackupStoreDirectory::Iterator i(rDir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { TEST_THAT(e < DIR_NUM); // Skip to entry in the ens array which matches e = SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet); // Does it match? TEST_THAT(en->GetName() == ens[e].fn && en->GetModificationTime() == ens[e].mod && en->GetObjectID() == ens[e].id && en->GetFlags() == ens[e].flags && en->GetSizeInBlocks() == ens[e].size); // next ++e; } // Got them all? TEST_THAT(en == 0); TEST_THAT(DIR_NUM == SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet)); } int test1(int argc, const char *argv[]) { // Initialise the raid file controller RaidFileController &rcontroller = RaidFileController::GetController(); rcontroller.Initialise("testfiles/raidfile.conf"); // test some basics -- encoding and decoding filenames { // Make some filenames in various ways BackupStoreFilenameClear fn1; fn1.SetClearFilename(std::string("filenameXYZ")); BackupStoreFilenameClear fn2(std::string("filenameXYZ")); BackupStoreFilenameClear fn3(fn1); TEST_THAT(fn1 == fn2); TEST_THAT(fn1 == fn3); // Check that it's been encrypted std::string name(fn2.GetEncodedFilename()); TEST_THAT(name.find("name") == name.npos); // Bung it in a stream, get it out in a Clear filename { CollectInBufferStream stream; fn1.WriteToStream(stream); stream.SetForReading(); BackupStoreFilenameClear fn4; fn4.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(fn4.GetClearFilename() == "filenameXYZ"); TEST_THAT(fn4 == fn1); } // Bung it in a stream, get it out in a server non-Clear filename (two of them into the same var) { BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf "); CollectInBufferStream stream; fn1.WriteToStream(stream); fno.WriteToStream(stream); stream.SetForReading(); BackupStoreFilename fn5; fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(fn5 == fn1); fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(fn5 == fno); } // Same again with clear strings { BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf "); CollectInBufferStream stream; fn1.WriteToStream(stream); fno.WriteToStream(stream); stream.SetForReading(); BackupStoreFilenameClear fn5; fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(fn5.GetClearFilename() == "filenameXYZ"); fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(fn5.GetClearFilename() == "pinglet dksfnsf jksjdf "); } // Test a very big filename { const char *fnr = "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789"; BackupStoreFilenameClear fnLong(fnr); CollectInBufferStream stream; fnLong.WriteToStream(stream); stream.SetForReading(); BackupStoreFilenameClear fn9; fn9.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(fn9.GetClearFilename() == fnr); TEST_THAT(fn9 == fnLong); } // Test a filename which went wrong once { BackupStoreFilenameClear dodgy("content-negotiation.html"); } } return 0; } int test2(int argc, const char *argv[]) { { // Now play with directories // Fill in... BackupStoreDirectory dir1(12, 98); for(int e = 0; e < DIR_NUM; ++e) { dir1.AddEntry(ens[e].fn, ens[e].mod, ens[e].id, ens[e].size, ens[e].flags, ens[e].attrmod); } // Got the right number TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM); // Stick it into a stream and get it out again { CollectInBufferStream stream; dir1.WriteToStream(stream); stream.SetForReading(); BackupStoreDirectory dir2; dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(dir2.GetNumberOfEntries() == DIR_NUM); TEST_THAT(dir2.GetObjectID() == 12); TEST_THAT(dir2.GetContainerID() == 98); CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); } // Then do selective writes and reads { CollectInBufferStream stream; dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File); stream.SetForReading(); BackupStoreDirectory dir2; dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES); CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); } { CollectInBufferStream stream; dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_File); stream.SetForReading(); BackupStoreDirectory dir2; dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(dir2.GetNumberOfEntries() == DIR_DIRS); CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_Dir, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); } { CollectInBufferStream stream; dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion); stream.SetForReading(); BackupStoreDirectory dir2; dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - DIR_OLD); CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion); } // Finally test deleting items { dir1.DeleteEntry(12312312321LL); // Verify TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM - 1); CollectInBufferStream stream; dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File); stream.SetForReading(); BackupStoreDirectory dir2; dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - 1); } // Check attributes { int attrI[4] = {1, 2, 3, 4}; StreamableMemBlock attr(attrI, sizeof(attrI)); BackupStoreDirectory d1(16, 546); d1.SetAttributes(attr, 56234987324232LL); TEST_THAT(d1.GetAttributes() == attr); TEST_THAT(d1.GetAttributesModTime() == 56234987324232LL); CollectInBufferStream stream; d1.WriteToStream(stream); stream.SetForReading(); BackupStoreDirectory d2; d2.ReadFromStream(stream, IOStream::TimeOutInfinite); TEST_THAT(d2.GetAttributes() == attr); TEST_THAT(d2.GetAttributesModTime() == 56234987324232LL); } } return 0; } void write_test_file(int t) { std::string filename("testfiles/test"); filename += uploads[t].fnextra; printf("%s\n", filename.c_str()); FileStream write(filename.c_str(), O_WRONLY | O_CREAT); R250 r(uploads[t].seed); unsigned char *data = (unsigned char*)malloc(uploads[t].size); for(int l = 0; l < uploads[t].size; ++l) { data[l] = r.next() & 0xff; } write.Write(data, uploads[t].size); free(data); } void test_test_file(int t, IOStream &rStream) { // Decode to a file BackupStoreFile::DecodeFile(rStream, "testfiles/test_download", IOStream::TimeOutInfinite); // Compare... FileStream in("testfiles/test_download"); TEST_THAT(in.BytesLeftToRead() == uploads[t].size); R250 r(uploads[t].seed); unsigned char *data = (unsigned char*)malloc(uploads[t].size); TEST_THAT(in.ReadFullBuffer(data, uploads[t].size, 0 /* not interested in bytes read if this fails */)); for(int l = 0; l < uploads[t].size; ++l) { TEST_THAT(data[l] == (r.next() & 0xff)); } free(data); in.Close(); TEST_THAT(unlink("testfiles/test_download") == 0); } void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID) { printf("Test for del: %llx\n", DirID); // Command std::auto_ptr dirreply(protocol.QueryListDirectory( DirID, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocol.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; int files = 0; int dirs = 0; while((en = i.Next()) != 0) { if(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Dir) { dirs++; // Recurse test_everything_deleted(protocol, en->GetObjectID()); } else { files++; } // Check it's deleted TEST_THAT(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Deleted); } // Check there were the right number of files and directories TEST_THAT(files == 3); TEST_THAT(dirs == 0 || dirs == 2); } std::vector ExpectedRefCounts; void set_refcount(int64_t ObjectID, uint32_t RefCount = 1) { if (ExpectedRefCounts.size() <= ObjectID); { ExpectedRefCounts.resize(ObjectID + 1, 0); } ExpectedRefCounts[ObjectID] = RefCount; } void create_file_in_dir(std::string name, std::string source, int64_t parentId, BackupProtocolClient &protocol, BackupStoreRefCountDatabase& rRefCount) { BackupStoreFilenameClear name_encoded("file_One"); std::auto_ptr upload(BackupStoreFile::EncodeFile( source.c_str(), parentId, name_encoded)); std::auto_ptr stored( protocol.QueryStoreFile( parentId, 0x123456789abcdefLL, /* modification time */ 0x7362383249872dfLL, /* attr hash */ 0, /* diff from ID */ name_encoded, *upload)); int64_t objectId = stored->GetObjectID(); TEST_EQUAL(objectId, rRefCount.GetLastObjectIDUsed()); TEST_EQUAL(1, rRefCount.GetRefCount(objectId)) set_refcount(objectId, 1); } int64_t create_test_data_subdirs(BackupProtocolClient &protocol, int64_t indir, const char *name, int depth, BackupStoreRefCountDatabase& rRefCount) { // Create a directory int64_t subdirid = 0; BackupStoreFilenameClear dirname(name); { // Create with dummy attributes int attrS = 0; MemBlockStream attr(&attrS, sizeof(attrS)); std::auto_ptr dirCreate(protocol.QueryCreateDirectory( indir, 9837429842987984LL, dirname, attr)); subdirid = dirCreate->GetObjectID(); } printf("Create subdirs, depth = %d, dirid = %llx\n", depth, subdirid); TEST_EQUAL(subdirid, rRefCount.GetLastObjectIDUsed()); TEST_EQUAL(1, rRefCount.GetRefCount(subdirid)) set_refcount(subdirid, 1); // Put more directories in it, if we haven't gone down too far if(depth > 0) { create_test_data_subdirs(protocol, subdirid, "dir_One", depth - 1, rRefCount); create_test_data_subdirs(protocol, subdirid, "dir_Two", depth - 1, rRefCount); } // Stick some files in it create_file_in_dir("file_One", "testfiles/file1", subdirid, protocol, rRefCount); create_file_in_dir("file_Two", "testfiles/file1", subdirid, protocol, rRefCount); create_file_in_dir("file_Three", "testfiles/file1", subdirid, protocol, rRefCount); return subdirid; } void check_dir_after_uploads(BackupProtocolClient &protocol, const StreamableMemBlock &Attributes) { // Command std::auto_ptr dirreply(protocol.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); TEST_THAT(dirreply->GetObjectID() == BackupProtocolClientListDirectory::RootDirectory); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocol.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(dir.GetNumberOfEntries() == UPLOAD_NUM + 1 /* for the first test file */); TEST_THAT(!dir.HasAttributes()); // Check them! BackupStoreDirectory::Iterator i(dir); // Discard first BackupStoreDirectory::Entry *en = i.Next(); TEST_THAT(en != 0); for(int t = 0; t < UPLOAD_NUM; ++t) { en = i.Next(); TEST_THAT(en != 0); TEST_THAT(en->GetName() == uploads[t].name); TEST_THAT(en->GetObjectID() == uploads[t].allocated_objid); TEST_THAT(en->GetModificationTime() == uploads[t].mod_time); int correct_flags = BackupProtocolClientListDirectory::Flags_File; if(uploads[t].should_be_old_version) correct_flags |= BackupProtocolClientListDirectory::Flags_OldVersion; if(uploads[t].delete_file) correct_flags |= BackupProtocolClientListDirectory::Flags_Deleted; TEST_THAT(en->GetFlags() == correct_flags); if(t == UPLOAD_ATTRS_EN) { TEST_THAT(en->HasAttributes()); TEST_THAT(en->GetAttributesHash() == 32498749832475LL); TEST_THAT(en->GetAttributes() == Attributes); } else { // No attributes on this one TEST_THAT(!en->HasAttributes()); } } en = i.Next(); TEST_THAT(en == 0); } typedef struct { int objectsNotDel; int deleted; int old; } recursive_count_objects_results; void recursive_count_objects_r(BackupProtocolClient &protocol, int64_t id, recursive_count_objects_results &results) { // Command std::auto_ptr dirreply(protocol.QueryListDirectory( id, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocol.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); // Check them! BackupStoreDirectory::Iterator i(dir); // Discard first BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { if((en->GetFlags() & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) == 0) results.objectsNotDel++; if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) results.deleted++; if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) results.old++; if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) { recursive_count_objects_r(protocol, en->GetObjectID(), results); } } } void recursive_count_objects(const char *hostname, int64_t id, recursive_count_objects_results &results) { // Context TLSContext context; context.Initialise(false /* client */, "testfiles/clientCerts.pem", "testfiles/clientPrivKey.pem", "testfiles/clientTrustedCAs.pem"); // Get a connection SocketStreamTLS connReadOnly; connReadOnly.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED_TEST); BackupProtocolClient protocolReadOnly(connReadOnly); { std::auto_ptr serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); std::auto_ptr loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly)); } // Count objects recursive_count_objects_r(protocolReadOnly, id, results); // Close it protocolReadOnly.QueryFinished(); } bool check_block_index(const char *encoded_file, IOStream &rBlockIndex) { // Open file, and move to the right position FileStream enc(encoded_file); BackupStoreFile::MoveStreamPositionToBlockIndex(enc); bool same = true; // Now compare the two... while(enc.StreamDataLeft()) { char buffer1[2048]; char buffer2[2048]; int s = enc.Read(buffer1, sizeof(buffer1)); if(rBlockIndex.Read(buffer2, s) != s) { same = false; break; } if(::memcmp(buffer1, buffer2, s) != 0) { same = false; break; } } if(rBlockIndex.StreamDataLeft()) { same = false; // Absorb all this excess data so procotol is in the first state char buffer[2048]; while(rBlockIndex.StreamDataLeft()) { rBlockIndex.Read(buffer, sizeof(buffer)); } } return same; } bool check_files_same(const char *f1, const char *f2) { // Open file, and move to the right position FileStream f1s(f1); FileStream f2s(f2); bool same = true; // Now compare the two... while(f1s.StreamDataLeft()) { char buffer1[2048]; char buffer2[2048]; int s = f1s.Read(buffer1, sizeof(buffer1)); if(f2s.Read(buffer2, s) != s) { same = false; break; } if(::memcmp(buffer1, buffer2, s) != 0) { same = false; break; } } if(f2s.StreamDataLeft()) { same = false; } return same; } void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protocolReadOnly) { int encfile[ENCFILE_SIZE]; { for(int l = 0; l < ENCFILE_SIZE; ++l) { encfile[l] = l * 173; } // Write this to a file { FileStream f("testfiles/file1", O_WRONLY | O_CREAT | O_EXCL); f.Write(encfile, sizeof(encfile)); } } // Read the root directory a few times (as it's cached, so make sure it doesn't hurt anything) for(int l = 0; l < 3; ++l) { // Command std::auto_ptr dirreply(protocol.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocol.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(dir.GetNumberOfEntries() == 0); } // Read the dir from the readonly connection (make sure it gets in the cache) { // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(dir.GetNumberOfEntries() == 0); } // Store a file -- first make the encoded file BackupStoreFilenameClear store1name("testfiles/file1"); { FileStream out("testfiles/file1_upload1", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/file1", BackupProtocolClientListDirectory::RootDirectory, store1name)); encoded->CopyStreamTo(out); } // printf("SKIPPING\n"); // goto skip; { // Then send it int64_t store1objid = 0; { FileStream upload("testfiles/file1_upload1"); std::auto_ptr stored(protocol.QueryStoreFile( BackupProtocolClientListDirectory::RootDirectory, 0x123456789abcdefLL, /* modification time */ 0x7362383249872dfLL, /* attr hash */ 0, /* diff from ID */ store1name, upload)); store1objid = stored->GetObjectID(); TEST_THAT(store1objid == 2); } set_refcount(store1objid, 1); // And retrieve it { // Retrieve as object std::auto_ptr getfile(protocol.QueryGetObject(store1objid)); TEST_THAT(getfile->GetObjectID() == store1objid); // BLOCK { // Get stream std::auto_ptr filestream(protocol.ReceiveStream()); // Need to put it in another stream, because it's not in stream order CollectInBufferStream f; filestream->CopyStreamTo(f); f.SetForReading(); // Get and decode BackupStoreFile::DecodeFile(f, "testfiles/file1_upload_retrieved", IOStream::TimeOutInfinite); } // Retrieve as file std::auto_ptr getobj(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, store1objid)); TEST_THAT(getobj->GetObjectID() == store1objid); // BLOCK { // Get stream std::auto_ptr filestream(protocol.ReceiveStream()); // Get and decode BackupStoreFile::DecodeFile(*filestream, "testfiles/file1_upload_retrieved_str", IOStream::TimeOutInfinite); } // Read in rebuilt original, and compare contents { FileStream in("testfiles/file1_upload_retrieved"); int encfile_i[ENCFILE_SIZE]; in.Read(encfile_i, sizeof(encfile_i)); TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); } { FileStream in("testfiles/file1_upload_retrieved_str"); int encfile_i[ENCFILE_SIZE]; in.Read(encfile_i, sizeof(encfile_i)); TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); } // Retrieve the block index, by ID { std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByID(store1objid)); TEST_THAT(getblockindex->GetObjectID() == store1objid); std::auto_ptr blockIndexStream(protocol.ReceiveStream()); // Check against uploaded file TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream)); } // and again, by name { std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByName(BackupProtocolClientListDirectory::RootDirectory, store1name)); TEST_THAT(getblockindex->GetObjectID() == store1objid); std::auto_ptr blockIndexStream(protocol.ReceiveStream()); // Check against uploaded file TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream)); } } // Get the directory again, and see if the entry is in it { // Command std::auto_ptr dirreply(protocol.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocol.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(dir.GetNumberOfEntries() == 1); BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = i.Next(); TEST_THAT(en != 0); TEST_THAT(i.Next() == 0); if(en != 0) { TEST_THAT(en->GetName() == store1name); TEST_THAT(en->GetModificationTime() == 0x123456789abcdefLL); TEST_THAT(en->GetAttributesHash() == 0x7362383249872dfLL); TEST_THAT(en->GetObjectID() == store1objid); TEST_THAT(en->GetSizeInBlocks() < ((ENCFILE_SIZE * 4 * 3) / 2 / 2048)+2); TEST_THAT(en->GetFlags() == BackupStoreDirectory::Entry::Flags_File); } } // Try using GetFile on a directory { TEST_CHECK_THROWS(std::auto_ptr getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::RootDirectory)), ConnectionException, Conn_Protocol_UnexpectedReply); } } void init_context(TLSContext& rContext) { rContext.Initialise(false /* client */, "testfiles/clientCerts.pem", "testfiles/clientPrivKey.pem", "testfiles/clientTrustedCAs.pem"); } std::auto_ptr open_conn(const char *hostname, TLSContext& rContext) { init_context(rContext); std::auto_ptr conn(new SocketStreamTLS); conn->Open(rContext, Socket::TypeINET, hostname, BOX_PORT_BBSTORED_TEST); return conn; } std::auto_ptr test_server_login(SocketStreamTLS& rConn) { // Make a protocol std::auto_ptr protocol(new BackupProtocolClient(rConn)); // Check the version std::auto_ptr serverVersion( protocol->QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); // Login std::auto_ptr loginConf( protocol->QueryLogin(0x01234567, 0)); return protocol; } void run_housekeeping(BackupStoreAccountDatabase::Entry& rAccount) { std::string rootDir = BackupStoreAccounts::GetAccountRoot(rAccount); int discSet = rAccount.GetDiscSet(); // Do housekeeping on this account HousekeepStoreAccount housekeeping(rAccount.GetID(), rootDir, discSet, NULL); housekeeping.DoHousekeeping(true /* keep trying forever */); } int test_server(const char *hostname) { TLSContext context; std::auto_ptr conn = open_conn(hostname, context); std::auto_ptr apProtocol( test_server_login(*conn)); BackupProtocolClient& protocol(*apProtocol); // Make some test attributes #define ATTR1_SIZE 245 #define ATTR2_SIZE 23 #define ATTR3_SIZE 122 int attr1[ATTR1_SIZE]; int attr2[ATTR2_SIZE]; int attr3[ATTR3_SIZE]; { R250 r(3465657); for(int l = 0; l < ATTR1_SIZE; ++l) {attr1[l] = r.next();} for(int l = 0; l < ATTR2_SIZE; ++l) {attr2[l] = r.next();} for(int l = 0; l < ATTR3_SIZE; ++l) {attr3[l] = r.next();} } // BLOCK { // Get it logging FILE *protocolLog = ::fopen("testfiles/protocol.log", "w"); TEST_THAT(protocolLog != 0); protocol.SetLogToFile(protocolLog); #ifndef WIN32 // Check that we can't open a new connection which requests write permissions { SocketStreamTLS conn; conn.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED_TEST); BackupProtocolClient protocol(conn); std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); TEST_CHECK_THROWS(std::auto_ptr loginConf(protocol.QueryLogin(0x01234567, 0)), ConnectionException, Conn_Protocol_UnexpectedReply); protocol.QueryFinished(); } #endif // Set the client store marker protocol.QuerySetClientStoreMarker(0x8732523ab23aLL); #ifndef WIN32 // Open a new connection which is read only SocketStreamTLS connReadOnly; connReadOnly.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED_TEST); BackupProtocolClient protocolReadOnly(connReadOnly); // Get it logging FILE *protocolReadOnlyLog = ::fopen("testfiles/protocolReadOnly.log", "w"); TEST_THAT(protocolReadOnlyLog != 0); protocolReadOnly.SetLogToFile(protocolReadOnlyLog); { std::auto_ptr serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); std::auto_ptr loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly)); // Check client store marker TEST_THAT(loginConf->GetClientStoreMarker() == 0x8732523ab23aLL); } #else // WIN32 BackupProtocolClient& protocolReadOnly(protocol); #endif test_server_1(protocol, protocolReadOnly); // sleep to ensure that the timestamp on the file will change ::safe_sleep(1); // Create and upload some test files int64_t maxID = 0; for(int t = 0; t < UPLOAD_NUM; ++t) { write_test_file(t); std::string filename("testfiles/test"); filename += uploads[t].fnextra; int64_t modtime = 0; std::auto_ptr upload(BackupStoreFile::EncodeFile(filename.c_str(), BackupProtocolClientListDirectory::RootDirectory, uploads[t].name, &modtime)); TEST_THAT(modtime != 0); std::auto_ptr stored(protocol.QueryStoreFile( BackupProtocolClientListDirectory::RootDirectory, modtime, modtime, /* use it for attr hash too */ 0, /* diff from ID */ uploads[t].name, *upload)); uploads[t].allocated_objid = stored->GetObjectID(); uploads[t].mod_time = modtime; if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID(); set_refcount(stored->GetObjectID(), 1); BOX_TRACE("wrote file " << filename << " to server " "as object " << BOX_FORMAT_OBJECTID(stored->GetObjectID())); } // Add some attributes onto one of them { MemBlockStream attrnew(attr3, sizeof(attr3)); std::auto_ptr set(protocol.QuerySetReplacementFileAttributes( BackupProtocolClientListDirectory::RootDirectory, 32498749832475LL, uploads[UPLOAD_ATTRS_EN].name, attrnew)); TEST_THAT(set->GetObjectID() == uploads[UPLOAD_ATTRS_EN].allocated_objid); } // Delete one of them (will implicitly delete an old version) { std::auto_ptr del(protocol.QueryDeleteFile( BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_DELETE_EN].name)); TEST_THAT(del->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid); } // Check that the block index can be obtained by name even though it's been deleted { // Fetch the raw object { FileStream out("testfiles/downloaddelobj", O_WRONLY | O_CREAT); std::auto_ptr getobj(protocol.QueryGetObject(uploads[UPLOAD_DELETE_EN].allocated_objid)); std::auto_ptr objstream(protocol.ReceiveStream()); objstream->CopyStreamTo(out); } // query index and test std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByName( BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_DELETE_EN].name)); TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid); std::auto_ptr blockIndexStream(protocol.ReceiveStream()); TEST_THAT(check_block_index("testfiles/downloaddelobj", *blockIndexStream)); } // Download them all... (even deleted files) for(int t = 0; t < UPLOAD_NUM; ++t) { printf("%d\n", t); std::auto_ptr getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, uploads[t].allocated_objid)); TEST_THAT(getFile->GetObjectID() == uploads[t].allocated_objid); std::auto_ptr filestream(protocol.ReceiveStream()); test_test_file(t, *filestream); } { StreamableMemBlock attrtest(attr3, sizeof(attr3)); // Use the read only connection to verify that the directory is as we expect printf("\n\n==== Reading directory using read-only connection\n"); check_dir_after_uploads(protocolReadOnly, attrtest); printf("done.\n\n"); // And on the read/write one check_dir_after_uploads(protocol, attrtest); } // sleep to ensure that the timestamp on the file will change ::safe_sleep(1); // Check diffing and rsync like stuff... // Build a modified file { // Basically just insert a bit in the middle TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING) == TEST_FILE_FOR_PATCHING_SIZE); FileStream in(TEST_FILE_FOR_PATCHING); void *buf = ::malloc(TEST_FILE_FOR_PATCHING_SIZE); FileStream out(TEST_FILE_FOR_PATCHING ".mod", O_WRONLY | O_CREAT | O_EXCL); TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_PATCH_AT); out.Write(buf, TEST_FILE_FOR_PATCHING_PATCH_AT); char insert[13] = "INSERTINSERT"; out.Write(insert, sizeof(insert)); TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT); out.Write(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT); ::free(buf); } { // Fetch the block index for this one std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByName( BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_PATCH_EN].name)); TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_PATCH_EN].allocated_objid); std::auto_ptr blockIndexStream(protocol.ReceiveStream()); // Do the patching bool isCompletelyDifferent = false; int64_t modtime; std::auto_ptr patchstream( BackupStoreFile::EncodeFileDiff( TEST_FILE_FOR_PATCHING ".mod", BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_PATCH_EN].name, uploads[UPLOAD_PATCH_EN].allocated_objid, *blockIndexStream, IOStream::TimeOutInfinite, NULL, // pointer to DiffTimer impl &modtime, &isCompletelyDifferent)); TEST_THAT(isCompletelyDifferent == false); // Sent this to a file, so we can check the size, rather than uploading it directly { FileStream patch(TEST_FILE_FOR_PATCHING ".patch", O_WRONLY | O_CREAT | O_EXCL); patchstream->CopyStreamTo(patch); } // Make sure the stream is a plausible size for a patch containing only one new block TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING ".patch") < (8*1024)); // Upload it int64_t patchedID = 0; { FileStream uploadpatch(TEST_FILE_FOR_PATCHING ".patch"); std::auto_ptr stored(protocol.QueryStoreFile( BackupProtocolClientListDirectory::RootDirectory, modtime, modtime, /* use it for attr hash too */ uploads[UPLOAD_PATCH_EN].allocated_objid, /* diff from ID */ uploads[UPLOAD_PATCH_EN].name, uploadpatch)); TEST_THAT(stored->GetObjectID() > 0); if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID(); patchedID = stored->GetObjectID(); } set_refcount(patchedID, 1); // Then download it to check it's OK std::auto_ptr getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, patchedID)); TEST_THAT(getFile->GetObjectID() == patchedID); std::auto_ptr filestream(protocol.ReceiveStream()); BackupStoreFile::DecodeFile(*filestream, TEST_FILE_FOR_PATCHING ".downloaded", IOStream::TimeOutInfinite); // Check it's the same TEST_THAT(check_files_same(TEST_FILE_FOR_PATCHING ".downloaded", TEST_FILE_FOR_PATCHING ".mod")); } // Create a directory int64_t subdirid = 0; BackupStoreFilenameClear dirname("lovely_directory"); { // Attributes MemBlockStream attr(attr1, sizeof(attr1)); std::auto_ptr dirCreate(protocol.QueryCreateDirectory( BackupProtocolClientListDirectory::RootDirectory, 9837429842987984LL, dirname, attr)); subdirid = dirCreate->GetObjectID(); TEST_THAT(subdirid == maxID + 1); } set_refcount(subdirid, 1); // Stick a file in it int64_t subdirfileid = 0; { std::string filename("testfiles/test0"); int64_t modtime; std::auto_ptr upload(BackupStoreFile::EncodeFile(filename.c_str(), subdirid, uploads[0].name, &modtime)); std::auto_ptr stored(protocol.QueryStoreFile( subdirid, modtime, modtime, /* use for attr hash too */ 0, /* diff from ID */ uploads[0].name, *upload)); subdirfileid = stored->GetObjectID(); } set_refcount(subdirfileid, 1); printf("\n==== Checking upload using read-only connection\n"); // Check the directories on the read only connection { // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(dir.GetNumberOfEntries() == UPLOAD_NUM + 3 /* for the first test file, the patched upload, and this new dir */); // Check the last one... BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; BackupStoreDirectory::Entry *t = 0; while((t = i.Next()) != 0) { if(en != 0) { // here for all but last object TEST_THAT(en->GetObjectID() != subdirid); TEST_THAT(en->GetName() != dirname); } en = t; } // Does it look right? TEST_THAT(en->GetName() == dirname); TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir); TEST_THAT(en->GetObjectID() == subdirid); TEST_THAT(en->GetModificationTime() == 0); // dirs don't have modification times. } { // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( subdirid, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, true /* get attributes */)); TEST_THAT(dirreply->GetObjectID() == subdirid); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(dir.GetNumberOfEntries() == 1); // Check the last one... BackupStoreDirectory::Iterator i(dir); // Discard first BackupStoreDirectory::Entry *en = i.Next(); TEST_THAT(en != 0); // Does it look right? TEST_THAT(en->GetName() == uploads[0].name); TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_File); TEST_THAT(en->GetObjectID() == subdirfileid); TEST_THAT(en->GetModificationTime() != 0); // Attributes TEST_THAT(dir.HasAttributes()); TEST_THAT(dir.GetAttributesModTime() == 9837429842987984LL); StreamableMemBlock attr(attr1, sizeof(attr1)); TEST_THAT(dir.GetAttributes() == attr); } printf("done.\n\n"); // Check that we don't get attributes if we don't ask for them { // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( subdirid, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(!dir.HasAttributes()); } // sleep to ensure that the timestamp on the file will change ::safe_sleep(1); // Change attributes on the directory { MemBlockStream attrnew(attr2, sizeof(attr2)); std::auto_ptr changereply(protocol.QueryChangeDirAttributes( subdirid, 329483209443598LL, attrnew)); TEST_THAT(changereply->GetObjectID() == subdirid); } // Check the new attributes { // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( subdirid, 0, // no flags BackupProtocolClientListDirectory::Flags_EXCLUDE_EVERYTHING, true /* get attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); TEST_THAT(dir.GetNumberOfEntries() == 0); // Attributes TEST_THAT(dir.HasAttributes()); TEST_THAT(dir.GetAttributesModTime() == 329483209443598LL); StreamableMemBlock attrtest(attr2, sizeof(attr2)); TEST_THAT(dir.GetAttributes() == attrtest); } // sleep to ensure that the timestamp on the file will change ::safe_sleep(1); // Test moving a file { BackupStoreFilenameClear newName("moved-files"); std::auto_ptr rep(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, BackupProtocolClientListDirectory::RootDirectory, subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName)); TEST_THAT(rep->GetObjectID() == uploads[UPLOAD_FILE_TO_MOVE].allocated_objid); } // Try some dodgy renames { BackupStoreFilenameClear newName("moved-files"); TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, BackupProtocolClientListDirectory::RootDirectory, subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName), ConnectionException, Conn_Protocol_UnexpectedReply); TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, subdirid, subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName), ConnectionException, Conn_Protocol_UnexpectedReply); } // Rename within a directory { BackupStoreFilenameClear newName("moved-files-x"); protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, subdirid, subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName); } // Check it's all gone from the root directory... { // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); // Read all entries BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { TEST_THAT(en->GetName() != uploads[UPLOAD_FILE_TO_MOVE].name); } } // Check the old and new versions are in the other directory { BackupStoreFilenameClear lookFor("moved-files-x"); // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( subdirid, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); // Check entries BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; bool foundCurrent = false; bool foundOld = false; while((en = i.Next()) != 0) { if(en->GetName() == lookFor) { if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File)) foundCurrent = true; if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion)) foundOld = true; } } TEST_THAT(foundCurrent); TEST_THAT(foundOld); } // sleep to ensure that the timestamp on the file will change ::safe_sleep(1); // make a little bit more of a thing to look at int64_t subsubdirid = 0; int64_t subsubfileid = 0; { BackupStoreFilenameClear nd("sub2"); // Attributes MemBlockStream attr(attr1, sizeof(attr1)); std::auto_ptr dirCreate(protocol.QueryCreateDirectory( subdirid, 9837429842987984LL, nd, attr)); subsubdirid = dirCreate->GetObjectID(); FileStream upload("testfiles/file1_upload1"); BackupStoreFilenameClear nf("file2"); std::auto_ptr stored(protocol.QueryStoreFile( subsubdirid, 0x123456789abcdefLL, /* modification time */ 0x7362383249872dfLL, /* attr hash */ 0, /* diff from ID */ nf, upload)); subsubfileid = stored->GetObjectID(); } set_refcount(subsubdirid, 1); set_refcount(subsubfileid, 1); // Query names -- test that invalid stuff returns not found OK { std::auto_ptr nameRep(protocol.QueryGetObjectName(3248972347823478927LL, subsubdirid)); TEST_THAT(nameRep->GetNumNameElements() == 0); } { std::auto_ptr nameRep(protocol.QueryGetObjectName(subsubfileid, 2342378424LL)); TEST_THAT(nameRep->GetNumNameElements() == 0); } { std::auto_ptr nameRep(protocol.QueryGetObjectName(38947234789LL, 2342378424LL)); TEST_THAT(nameRep->GetNumNameElements() == 0); } { std::auto_ptr nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, 2234342378424LL)); TEST_THAT(nameRep->GetNumNameElements() == 0); } // Query names... first, get info for the file { std::auto_ptr nameRep(protocol.QueryGetObjectName(subsubfileid, subsubdirid)); std::auto_ptr namestream(protocol.ReceiveStream()); TEST_THAT(nameRep->GetNumNameElements() == 3); TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_File); TEST_THAT(nameRep->GetModificationTime() == 0x123456789abcdefLL); TEST_THAT(nameRep->GetAttributesHash() == 0x7362383249872dfLL); static const char *testnames[] = {"file2","sub2","lovely_directory"}; for(int l = 0; l < nameRep->GetNumNameElements(); ++l) { BackupStoreFilenameClear fn; fn.ReadFromStream(*namestream, 10000); TEST_THAT(fn.GetClearFilename() == testnames[l]); } } // Query names... secondly, for the directory { std::auto_ptr nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, subsubdirid)); std::auto_ptr namestream(protocol.ReceiveStream()); TEST_THAT(nameRep->GetNumNameElements() == 2); TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir); static const char *testnames[] = {"sub2","lovely_directory"}; for(int l = 0; l < nameRep->GetNumNameElements(); ++l) { BackupStoreFilenameClear fn; fn.ReadFromStream(*namestream, 10000); TEST_THAT(fn.GetClearFilename() == testnames[l]); } } //} skip: std::auto_ptr apAccounts( BackupStoreAccountDatabase::Read( "testfiles/accounts.txt")); std::auto_ptr apRefCount( BackupStoreRefCountDatabase::Load( apAccounts->GetEntry(0x1234567), true)); // Create some nice recursive directories int64_t dirtodelete = create_test_data_subdirs(protocol, BackupProtocolClientListDirectory::RootDirectory, "test_delete", 6 /* depth */, *apRefCount); // And delete them { std::auto_ptr dirdel(protocol.QueryDeleteDirectory( dirtodelete)); TEST_THAT(dirdel->GetObjectID() == dirtodelete); } // Get the root dir, checking for deleted items { // Command std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::Flags_Dir | BackupProtocolClientListDirectory::Flags_Deleted, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); // Stream BackupStoreDirectory dir; std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); // Check there's only that one entry TEST_THAT(dir.GetNumberOfEntries() == 1); BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = i.Next(); TEST_THAT(en != 0); if(en) { TEST_THAT(en->GetObjectID() == dirtodelete); BackupStoreFilenameClear n("test_delete"); TEST_THAT(en->GetName() == n); } // Then... check everything's deleted test_everything_deleted(protocolReadOnly, dirtodelete); } // Finish the connections #ifndef WIN32 protocolReadOnly.QueryFinished(); #endif protocol.QueryFinished(); // Close logs #ifndef WIN32 ::fclose(protocolReadOnlyLog); #endif ::fclose(protocolLog); } return 0; } int test3(int argc, const char *argv[]) { // Now test encoded files // TODO: This test needs to check failure situations as well as everything working, // but this will be saved for the full implementation. int encfile[ENCFILE_SIZE]; { for(int l = 0; l < ENCFILE_SIZE; ++l) { encfile[l] = l * 173; } // Encode and decode a small block (shouldn't be compressed) { #define SMALL_BLOCK_SIZE 251 int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(SMALL_BLOCK_SIZE); TEST_THAT(encBlockSize > SMALL_BLOCK_SIZE); BackupStoreFile::EncodingBuffer encoded; encoded.Allocate(encBlockSize / 8); // make sure reallocation happens // Encode! int encSize = BackupStoreFile::EncodeChunk(encfile, SMALL_BLOCK_SIZE, encoded); // Check the header says it's not been compressed TEST_THAT((encoded.mpBuffer[0] & 1) == 0); // Check the output size has been inflated (no compression) TEST_THAT(encSize > SMALL_BLOCK_SIZE); // Decode it int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(SMALL_BLOCK_SIZE); TEST_THAT(decBlockSize > SMALL_BLOCK_SIZE); uint8_t *decoded = (uint8_t*)malloc(decBlockSize); int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize); TEST_THAT(decSize < decBlockSize); TEST_THAT(decSize == SMALL_BLOCK_SIZE); // Check it came out of the wash the same TEST_THAT(::memcmp(encfile, decoded, SMALL_BLOCK_SIZE) == 0); free(decoded); } // Encode and decode a big block (should be compressed) { int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(ENCFILE_SIZE); TEST_THAT(encBlockSize > ENCFILE_SIZE); BackupStoreFile::EncodingBuffer encoded; encoded.Allocate(encBlockSize / 8); // make sure reallocation happens // Encode! int encSize = BackupStoreFile::EncodeChunk(encfile, ENCFILE_SIZE, encoded); // Check the header says it's compressed TEST_THAT((encoded.mpBuffer[0] & 1) == 1); // Check the output size make it likely that it's compressed (is very compressible data) TEST_THAT(encSize < ENCFILE_SIZE); // Decode it int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(ENCFILE_SIZE); TEST_THAT(decBlockSize > ENCFILE_SIZE); uint8_t *decoded = (uint8_t*)malloc(decBlockSize); int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize); TEST_THAT(decSize < decBlockSize); TEST_THAT(decSize == ENCFILE_SIZE); // Check it came out of the wash the same TEST_THAT(::memcmp(encfile, decoded, ENCFILE_SIZE) == 0); free(decoded); } // The test block to a file { FileStream f("testfiles/testenc1", O_WRONLY | O_CREAT | O_EXCL); f.Write(encfile, sizeof(encfile)); } // Encode it { FileStream out("testfiles/testenc1_enc", O_WRONLY | O_CREAT | O_EXCL); BackupStoreFilenameClear name("testfiles/testenc1"); std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testenc1", 32, name)); encoded->CopyStreamTo(out); } // Verify it { FileStream enc("testfiles/testenc1_enc"); TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc) == true); } // Decode it { FileStream enc("testfiles/testenc1_enc"); BackupStoreFile::DecodeFile(enc, "testfiles/testenc1_orig", IOStream::TimeOutInfinite); } // Read in rebuilt original, and compare contents { TEST_THAT(TestGetFileSize("testfiles/testenc1_orig") == sizeof(encfile)); FileStream in("testfiles/testenc1_orig"); int encfile_i[ENCFILE_SIZE]; in.Read(encfile_i, sizeof(encfile_i)); TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); } // Check how many blocks it had, and test the stream based interface { FileStream enc("testfiles/testenc1_enc"); std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(enc, IOStream::TimeOutInfinite)); CollectInBufferStream d; decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */); d.SetForReading(); TEST_THAT(d.GetSize() == sizeof(encfile)); TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0); TEST_THAT(decoded->GetNumBlocks() == 3); } // Test that the last block in a file, if less than 256 bytes, gets put into the last block { #define FILE_SIZE_JUST_OVER ((4096*2)+58) FileStream f("testfiles/testenc2", O_WRONLY | O_CREAT | O_EXCL); f.Write(encfile + 2, FILE_SIZE_JUST_OVER); BackupStoreFilenameClear name("testenc2"); std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testenc2", 32, name)); CollectInBufferStream e; encoded->CopyStreamTo(e); e.SetForReading(); std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(e, IOStream::TimeOutInfinite)); CollectInBufferStream d; decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 879 /* buffer block size */); d.SetForReading(); TEST_THAT(d.GetSize() == FILE_SIZE_JUST_OVER); TEST_THAT(memcmp(encfile + 2, d.GetBuffer(), FILE_SIZE_JUST_OVER) == 0); TEST_THAT(decoded->GetNumBlocks() == 2); } // Test that reordered streams work too { FileStream enc("testfiles/testenc1_enc"); std::auto_ptr reordered(BackupStoreFile::ReorderFileToStreamOrder(&enc, false)); std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(*reordered, IOStream::TimeOutInfinite)); CollectInBufferStream d; decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */); d.SetForReading(); TEST_THAT(d.GetSize() == sizeof(encfile)); TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0); TEST_THAT(decoded->GetNumBlocks() == 3); } #ifndef WIN32 // no symlinks on Win32 // Try out doing this on a symlink { TEST_THAT(::symlink("does/not/exist", "testfiles/testsymlink") == 0); BackupStoreFilenameClear name("testsymlink"); std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testsymlink", 32, name)); // Can't decode it from the stream, because it's in file order, and doesn't have the // required properties to be able to reorder it. So buffer it... CollectInBufferStream b; encoded->CopyStreamTo(b); b.SetForReading(); // Decode it BackupStoreFile::DecodeFile(b, "testfiles/testsymlink_2", IOStream::TimeOutInfinite); } #endif } // Store info { RaidFileWrite::CreateDirectory(0, "test-info"); BackupStoreInfo::CreateNew(76, "test-info/", 0, 3461231233455433LL, 2934852487LL); TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, "test-info/", 0, 0, 0), RaidFileException, CannotOverwriteExistingFile); std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, true)); TEST_CHECK_THROWS(info->Save(), BackupStoreException, StoreInfoIsReadOnly); TEST_CHECK_THROWS(info->ChangeBlocksUsed(1), BackupStoreException, StoreInfoIsReadOnly); TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(1), BackupStoreException, StoreInfoIsReadOnly); TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(1), BackupStoreException, StoreInfoIsReadOnly); TEST_CHECK_THROWS(info->RemovedDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly); TEST_CHECK_THROWS(info->AddDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly); } { std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, false)); info->ChangeBlocksUsed(8); info->ChangeBlocksInOldFiles(9); info->ChangeBlocksInDeletedFiles(10); info->ChangeBlocksUsed(-1); info->ChangeBlocksInOldFiles(-4); info->ChangeBlocksInDeletedFiles(-9); TEST_CHECK_THROWS(info->ChangeBlocksUsed(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); info->AddDeletedDirectory(2); info->AddDeletedDirectory(3); info->AddDeletedDirectory(4); info->RemovedDeletedDirectory(3); TEST_CHECK_THROWS(info->RemovedDeletedDirectory(9), BackupStoreException, StoreInfoDirNotInList); info->Save(); } { std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, true)); TEST_THAT(info->GetBlocksUsed() == 7); TEST_THAT(info->GetBlocksInOldFiles() == 5); TEST_THAT(info->GetBlocksInDeletedFiles() == 1); TEST_THAT(info->GetBlocksSoftLimit() == 3461231233455433LL); TEST_THAT(info->GetBlocksHardLimit() == 2934852487LL); const std::vector &delfiles(info->GetDeletedDirectories()); TEST_THAT(delfiles.size() == 2); TEST_THAT(delfiles[0] == 2); TEST_THAT(delfiles[1] == 4); } //printf("SKIPPINGTESTS---------\n"); //return 0; // Context TLSContext context; context.Initialise(false /* client */, "testfiles/clientCerts.pem", "testfiles/clientPrivKey.pem", "testfiles/clientTrustedCAs.pem"); // First, try logging in without an account having been created... just make sure login fails. std::string cmd = BBSTORED " " + bbstored_args + " testfiles/bbstored.conf"; int pid = LaunchServer(cmd.c_str(), "testfiles/bbstored.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { ::sleep(1); TEST_THAT(ServerIsAlive(pid)); // BLOCK { // Open a connection to the server SocketStreamTLS conn; conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED_TEST); // Make a protocol BackupProtocolClient protocol(conn); // Check the version std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); // Login TEST_CHECK_THROWS(std::auto_ptr loginConf(protocol.QueryLogin(0x01234567, 0)), ConnectionException, Conn_Protocol_UnexpectedReply); // Finish the connection protocol.QueryFinished(); } // Create an account for the test client TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf create 01234567 0 " "10000B 20000B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); TEST_THAT(TestDirExists("testfiles/0_0/backup/01234567")); TEST_THAT(TestDirExists("testfiles/0_1/backup/01234567")); TEST_THAT(TestDirExists("testfiles/0_2/backup/01234567")); TEST_THAT(TestGetFileSize("testfiles/accounts.txt") > 8); // make sure something is written to it std::auto_ptr apAccounts( BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); std::auto_ptr apReferences( BackupStoreRefCountDatabase::Load( apAccounts->GetEntry(0x1234567), true)); TEST_EQUAL(BACKUPSTORE_ROOT_DIRECTORY_ID, apReferences->GetLastObjectIDUsed()); TEST_EQUAL(1, apReferences->GetRefCount(BACKUPSTORE_ROOT_DIRECTORY_ID)) apReferences.reset(); // Delete the refcount database and log in again, // check that it is recreated automatically but with // no objects in it, to ensure seamless upgrade. TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.db.rfw")); TLSContext context; std::auto_ptr conn = open_conn("localhost", context); test_server_login(*conn)->QueryFinished(); BackupStoreAccountDatabase::Entry account = apAccounts->GetEntry(0x1234567); apReferences = BackupStoreRefCountDatabase::Load(account, true); TEST_EQUAL(0, apReferences->GetLastObjectIDUsed()); TEST_THAT(ServerIsAlive(pid)); run_housekeeping(account); // Check that housekeeping fixed the ref counts TEST_EQUAL(BACKUPSTORE_ROOT_DIRECTORY_ID, apReferences->GetLastObjectIDUsed()); TEST_EQUAL(1, apReferences->GetRefCount(BACKUPSTORE_ROOT_DIRECTORY_ID)) TEST_THAT(ServerIsAlive(pid)); set_refcount(BACKUPSTORE_ROOT_DIRECTORY_ID, 1); TEST_THAT(test_server("localhost") == 0); // test that all object reference counts have the // expected values TEST_EQUAL(ExpectedRefCounts.size() - 1, apReferences->GetLastObjectIDUsed()); for (unsigned int i = BACKUPSTORE_ROOT_DIRECTORY_ID; i < ExpectedRefCounts.size(); i++) { TEST_EQUAL_LINE(ExpectedRefCounts[i], apReferences->GetRefCount(i), "object " << BOX_FORMAT_OBJECTID(i)); } // Delete the refcount database again, and let // housekeeping recreate it and fix the ref counts. // This could also happen after upgrade, if a housekeeping // runs before the user logs in. apReferences.reset(); TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.db.rfw")); run_housekeeping(account); apReferences = BackupStoreRefCountDatabase::Load(account, true); TEST_EQUAL(ExpectedRefCounts.size() - 1, apReferences->GetLastObjectIDUsed()); for (unsigned int i = BACKUPSTORE_ROOT_DIRECTORY_ID; i < ExpectedRefCounts.size(); i++) { TEST_EQUAL_LINE(ExpectedRefCounts[i], apReferences->GetRefCount(i), "object " << BOX_FORMAT_OBJECTID(i)); } // Test the deletion of objects by the housekeeping system // First, things as they are now. recursive_count_objects_results before = {0,0,0}; recursive_count_objects("localhost", BackupProtocolClientListDirectory::RootDirectory, before); TEST_THAT(before.objectsNotDel != 0); TEST_THAT(before.deleted != 0); TEST_THAT(before.old != 0); // Kill it TEST_THAT(KillServer(pid)); ::sleep(1); TEST_THAT(!ServerIsAlive(pid)); #ifdef WIN32 TEST_THAT(unlink("testfiles/bbstored.pid") == 0); #else TestRemoteProcessMemLeaks("bbstored.memleaks"); #endif // Set a new limit on the account -- leave the hard limit // high to make sure the target for freeing space is the // soft limit. TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf setlimit 01234567 " "10B 20000B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // Start things up pid = LaunchServer(BBSTORED " testfiles/bbstored.conf", "testfiles/bbstored.pid"); ::sleep(1); TEST_THAT(ServerIsAlive(pid)); // wait for housekeeping to happen printf("waiting for housekeeping:\n"); for(int l = 0; l < 30; ++l) { ::sleep(1); printf("."); fflush(stdout); } printf("\n"); // Count the objects again recursive_count_objects_results after = {0,0,0}; recursive_count_objects("localhost", BackupProtocolClientListDirectory::RootDirectory, after); // If these tests fail then try increasing the timeout above TEST_THAT(after.objectsNotDel == before.objectsNotDel); TEST_THAT(after.deleted == 0); TEST_THAT(after.old == 0); // Set a really small hard limit TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf setlimit 01234567 " "10B 20B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // Try to upload a file and create a directory, and check an error is generated { // Open a connection to the server SocketStreamTLS conn; conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED_TEST); // Make a protocol BackupProtocolClient protocol(conn); // Check the version std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); // Login std::auto_ptr loginConf(protocol.QueryLogin(0x01234567, 0)); int64_t modtime = 0; BackupStoreFilenameClear fnx("exceed-limit"); std::auto_ptr upload(BackupStoreFile::EncodeFile("testfiles/test3", BackupProtocolClientListDirectory::RootDirectory, fnx, &modtime)); TEST_THAT(modtime != 0); TEST_CHECK_THROWS(std::auto_ptr stored(protocol.QueryStoreFile( BackupProtocolClientListDirectory::RootDirectory, modtime, modtime, /* use it for attr hash too */ 0, /* diff from ID */ fnx, *upload)), ConnectionException, Conn_Protocol_UnexpectedReply); MemBlockStream attr(&modtime, sizeof(modtime)); BackupStoreFilenameClear fnxd("exceed-limit-dir"); TEST_CHECK_THROWS(std::auto_ptr dirCreate(protocol.QueryCreateDirectory( BackupProtocolClientListDirectory::RootDirectory, 9837429842987984LL, fnxd, attr)), ConnectionException, Conn_Protocol_UnexpectedReply); // Finish the connection protocol.QueryFinished(); } // Kill it again TEST_THAT(KillServer(pid)); ::sleep(1); TEST_THAT(!ServerIsAlive(pid)); #ifdef WIN32 TEST_THAT(unlink("testfiles/bbstored.pid") == 0); #else TestRemoteProcessMemLeaks("bbstored.memleaks"); #endif } return 0; } int multi_server() { printf("Starting server for connection from remote machines...\n"); // Create an account for the test client TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf create 01234567 0 " "30000B 40000B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // First, try logging in without an account having been created... just make sure login fails. int pid = LaunchServer(BBSTORED " testfiles/bbstored_multi.conf", "testfiles/bbstored.pid"); TEST_THAT(pid != -1 && pid != 0); if(pid > 0) { ::sleep(1); TEST_THAT(ServerIsAlive(pid)); // Wait for a keypress printf("Press ENTER to terminate the server\n"); char line[512]; fgets(line, 512, stdin); printf("Terminating server...\n"); // Kill it TEST_THAT(KillServer(pid)); ::sleep(1); TEST_THAT(!ServerIsAlive(pid)); #ifdef WIN32 TEST_THAT(unlink("testfiles/bbstored.pid") == 0); #else TestRemoteProcessMemLeaks("bbstored.memleaks"); #endif } return 0; } #ifdef WIN32 WCHAR* ConvertUtf8ToWideString(const char* pString); std::string ConvertPathToAbsoluteUnicode(const char *pFileName); #endif int test(int argc, const char *argv[]) { #ifdef WIN32 // this had better work, or bbstored will die when combining diffs char* file = "foo"; std::string abs = ConvertPathToAbsoluteUnicode(file); WCHAR* wfile = ConvertUtf8ToWideString(abs.c_str()); DWORD accessRights = FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_READ_EA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA /*| FILE_ALL_ACCESS*/; DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; HANDLE h1 = CreateFileW(wfile, accessRights, shareMode, NULL, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL); assert(h1 != INVALID_HANDLE_VALUE); TEST_THAT(h1 != INVALID_HANDLE_VALUE); accessRights = FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_READ_EA; HANDLE h2 = CreateFileW(wfile, accessRights, shareMode, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); assert(h2 != INVALID_HANDLE_VALUE); TEST_THAT(h2 != INVALID_HANDLE_VALUE); CloseHandle(h2); CloseHandle(h1); delete [] wfile; h1 = openfile("foo", O_CREAT | O_RDWR, 0); TEST_THAT(h1 != INVALID_HANDLE_VALUE); h2 = openfile("foo", O_RDWR, 0); TEST_THAT(h2 != INVALID_HANDLE_VALUE); CloseHandle(h2); CloseHandle(h1); #endif // SSL library SSLLib::Initialise(); // Give a test key for the filenames // BackupStoreFilenameClear::SetBlowfishKey(FilenameEncodingKey, sizeof(FilenameEncodingKey)); // And set the encoding to blowfish // BackupStoreFilenameClear::SetEncodingMethod(BackupStoreFilename::Encoding_Blowfish); // And for directory attributes -- need to set it, as used in file encoding // BackupClientFileAttributes::SetBlowfishKey(AttributesEncodingKey, sizeof(AttributesEncodingKey)); // And finally for file encoding // BackupStoreFile::SetBlowfishKeys(FileEncodingKey, sizeof(FileEncodingKey), FileBlockEntryEncodingKey, sizeof(FileBlockEntryEncodingKey)); // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used // for seeing what's going on. BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); // encode in some filenames -- can't do static initialisation // because the key won't be set up when these are initialised { MEMLEAKFINDER_NO_LEAKS for(unsigned int l = 0; l < sizeof(ens_filenames) / sizeof(ens_filenames[0]); ++l) { ens[l].fn = BackupStoreFilenameClear(ens_filenames[l]); } for(unsigned int l = 0; l < sizeof(uploads_filenames) / sizeof(uploads_filenames[0]); ++l) { uploads[l].name = BackupStoreFilenameClear(uploads_filenames[l]); } } // Trace errors out SET_DEBUG_SSLLIB_TRACE_ERRORS if(argc == 2 && strcmp(argv[1], "server") == 0) { return multi_server(); } if(argc == 3 && strcmp(argv[1], "client") == 0) { return test_server(argv[2]); } // large file test /* { int64_t modtime = 0; std::auto_ptr upload(BackupStoreFile::EncodeFile("/Users/ben/temp/large.tar", BackupProtocolClientListDirectory::RootDirectory, uploads[0].name, &modtime)); TEST_THAT(modtime != 0); FileStream write("testfiles/large.enc", O_WRONLY | O_CREAT); upload->CopyStreamTo(write); } printf("SKIPPING TESTS ------------------------------------------------------\n"); return 0;*/ int r = 0; r = test1(argc, argv); if(r != 0) return r; r = test2(argc, argv); if(r != 0) return r; r = test3(argc, argv); if(r != 0) return r; return 0; } boxbackup/test/backupstore/testfiles/0000775000175000017500000000000011652362372020624 5ustar siretartsiretartboxbackup/test/backupstore/testfiles/rootkey.pem0000664000175000017500000000157310347400657023030 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2 tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0 5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01 TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe 3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w== -----END RSA PRIVATE KEY----- boxbackup/test/backupstore/testfiles/serverPrivKey.pem0000664000175000017500000000156710347400657024157 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4 bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l 4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN 8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48 q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn -----END RSA PRIVATE KEY----- boxbackup/test/backupstore/testfiles/root.pem0000664000175000017500000000272210347400657022314 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz MDgyMDExNTEyN1oXDTAzMDkxOTExNTEyN1owDzENMAsGA1UEAxMEUk9PVDCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQCPbEXLzpItnnh1kUPy0vui atzeQoTFzgEybKLqgM4irWUjUnVdcSFEJFgddABpMOlGymu/6NuqqVQR8OUUOUrk BUlucY1m3BuCJBsADKWXVBOky4aQ7oo7BZZUh7e9NeKHfu7u1+0kvIQlTc+1Xnub uAQzwDRZ5vAFMWzzvh5BtA== -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2 tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0 5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01 TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe 3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w== -----END RSA PRIVATE KEY----- boxbackup/test/backupstore/testfiles/rootreq.pem0000664000175000017500000000102210347400657023014 0ustar siretartsiretart-----BEGIN CERTIFICATE REQUEST----- MIIBTjCBuAIBADAPMQ0wCwYDVQQDEwRST09UMIGfMA0GCSqGSIb3DQEBAQUAA4GN ADCBiQKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDOcOtu HweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2tNI9 W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQABoAAw DQYJKoZIhvcNAQEFBQADgYEAarbwMXzojqzCzQLakpX8hMDiBnGb80M4au+r8MXI g492CbH+PgpSus4g58na+1S1xAV2a7kDN6udss+OjHvukePybWUkkR6DAfXVJuxO FrchOTv6Pwj1p4FZGzocnJ2sIp4fe+2p2ge2oAHw7EIX+1IhQUObGI/q7zEVDctK 5Fg= -----END CERTIFICATE REQUEST----- boxbackup/test/backupstore/testfiles/clientReq.pem0000664000175000017500000000104210347400657023251 0ustar siretartsiretart-----BEGIN CERTIFICATE REQUEST----- MIIBWTCBwwIBADAaMRgwFgYDVQQDEw9CQUNLVVAtMDEyMzQ1NjcwgZ8wDQYJKoZI hvcNAQEBBQADgY0AMIGJAoGBAL6bTOgPvmXZMWDfdjiOndDt9oIkXcm0nVFTSZNP VdaXVSRYV6+KMIbVb+nfMg5z2jToEtS5cWfQh3EeQC7RcCPcJhhwXOWtrhQmRjkU 2koIJ7QsrYb0Vdwxpv31O5kTBOy3tf7VlsY8nJ5NpEXs+nexX4+eklwxaMWNnSvD MEWrAgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQBtz10sGGYhbw9+7L8bOtOUV6j9 46jnbHGXHmdBZsg8ZWgKBJQ61HwvKCNA+KAEeb9yMxWgpJRGqFk6yvPb62XXuRGl 4RQN0/6rRc8GJh3Qi4oPV1GYnzyYg2+bjZAgeMoL6ro1YuH52CTHJpQ3Arg2Ortz xVxbWyMouzjc1g4gdw== -----END CERTIFICATE REQUEST----- boxbackup/test/backupstore/testfiles/accounts.txt0000664000175000017500000000000010347400657023171 0ustar siretartsiretartboxbackup/test/backupstore/testfiles/bbstored_multi.conf0000664000175000017500000000064010347400657024510 0ustar siretartsiretart RaidFileConf = testfiles/raidfile.conf AccountDatabase = testfiles/accounts.txt TimeBetweenHousekeeping = 5 Server { PidFile = testfiles/bbstored.pid # 0.0.0.0 is the 'any' address, allowing connections from things other than localhost ListenAddresses = inet:0.0.0.0 CertificateFile = testfiles/serverCerts.pem PrivateKeyFile = testfiles/serverPrivKey.pem TrustedCAsFile = testfiles/serverTrustedCAs.pem } boxbackup/test/backupstore/testfiles/clientCerts.pem0000664000175000017500000000114710347400657023610 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92 OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ== -----END CERTIFICATE----- boxbackup/test/backupstore/testfiles/clientTrustedCAs.pem0000664000175000017500000000112710347400657024547 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt SlL3iQzVXlF6NAhkzS54fQ== -----END CERTIFICATE----- boxbackup/test/backupstore/testfiles/root.srl0000664000175000017500000000000310347400657022321 0ustar siretartsiretart05 boxbackup/test/backupstore/testfiles/query.conf0000664000175000017500000000114210347400657022635 0ustar siretartsiretart # this is a dummy config file so that bbackupquery can be used CertificateFile = testfiles/clientCerts.pem PrivateKeyFile = testfiles/clientPrivKey.pem TrustedCAsFile = testfiles/clientTrustedCAs.pem KeysFile = testfiles/bbackupd.keys DataDirectory = testfiles/bbackupd-data StoreHostname = localhost AccountNumber = 0x01234567 UpdateStoreInterval = 3 MinimumFileAge = 4 MaxUploadWait = 24 FileTrackingSizeThreshold = 1024 DiffingUploadSizeThreshold = 1024 Server { PidFile = testfiles/bbackupd.pid } # this is just a dummy entry BackupLocations { test_delete { Path = testfiles/test_delete } } boxbackup/test/backupstore/testfiles/bbackupd.keys0000664000175000017500000000200010323670556023264 0ustar siretartsiretartpM=á´¨c6Ql‡0‰îHêPç¶R Å†më²cÙIB¼jžƨ§ê-ÅGÖ&¹˜§(¸æ,¿.º…Äk û´‚¤¡¦µª—›µäp)ÄìÀ¶wÊ]ÁÄ¡ÊmnýQ!ŒŒán|FU0ùÁM³VúcLÖ¼ñW ã‹V¢0D} »°fžÖ™ÖS#žLîõÏ: cûKãH.¥Œs§÷¿8 ‚p‹ü(ÑŽß~µba’z`d—Ÿˆ~ˆ^Ï7‚gxA{¬PÏe³‡iúT¯$‰v˜uÞˆZ4©Œ¦÷{×P1MÐ-)5«abÒp$à˜¾äJHõŽúEÇtN}? ¤‰Ñ't‚¤Yqšw¦c_c˜7¥b¿í^0 u÷Ñg€Týº¥¾ä‰Wª$ãg‚l>Ncö$^tÛ°˜¢^ ‡¹nÞñwlÈ×ÎÓöž¾’]UóúïqýÝ\¸gV®kY.'2Fˆe©ÒA„>ÃýG¡ìМòÐ’Þ: i-•qÎ Q ‘ŽŸ¦ƒÒ<ðÞIµ+)×vY¢Zæã}< ¹Ñd:ò}ñÝb¹9þÀ1ÎmFëÚýõ޶»¸jlǦ¸¶¨tî÷o§_€›,xÖé¦7®˜ ”] ÜTá~„ç‘GÈ“èÝÿ–Þ¿,fÇ2KI| "TÝ Q‚:ÆQ¤°cp4èˆfÇÚC YvlBKOçT–F:%|9äÔŸîlÄ}•ý€¶[&¬ïÊéùË4ñ(dÑ n¢)hÉ=¼Ú[8k4yPAyvFÆõá⌑ˆDÒîõ#´r¸ðü÷KÇ^$„D-@”_ŽñÞ@ËêºZ›ã‹=¨õwÀ‚œÝ-`œ/u´h÷`É+EŒ¬Bó-eVýÜb— €[‚^¼Tôz ‹y/“´YEÉXAÄÿ玳EDæPç)|v²ªè†â*`Û·Ï Ñ™6£Èˆ\óÊ€¸½O«&U“.Ü=® WzØ/X«‚ȸŒ}¥“£?ºaVN%` œ5s¹¡}Ƶ¢iÕIÉßë~ö_X.®²Õõi ôûÜó„ÃEÊkäPJyEÅ›‰‘÷v;N¯Uáù«ëg 戆ÖŽ'8×ш‚“”þÁsïp€Ä;« ßš!hL:™Üú ðê÷ÐãÜK†`P ~4”CK½ïÊi®ÐÄ✠‘,x2Ö·€.õ$*ŽF¡§ !ª¢’°ު•~”sˆJQÌe2ç±¾¿ö*/ê)"(úõ­î <Ï Þ˶Ff$ÿ­q/sÀ#LÇ.ÁXrª·ß'%¤Ÿ¡J5H‡wU@ŸñpÿÉýߎLboxbackup/test/backupstore/testfiles/serverCerts.pem0000664000175000017500000000114310347400657023634 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92 cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/ myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2 SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1 HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t -----END CERTIFICATE----- boxbackup/test/backupstore/testfiles/clientPrivKey.pem0000664000175000017500000000156710347400657024127 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G 9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp 81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9 qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8 eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26 Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw= -----END RSA PRIVATE KEY----- boxbackup/test/backupstore/testfiles/rootcert.pem0000664000175000017500000000112710347400657023170 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt SlL3iQzVXlF6NAhkzS54fQ== -----END CERTIFICATE----- boxbackup/test/backupstore/testfiles/serverTrustedCAs.pem0000664000175000017500000000112710347400657024577 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt SlL3iQzVXlF6NAhkzS54fQ== -----END CERTIFICATE----- boxbackup/test/backupstore/testfiles/serverReq.pem0000664000175000017500000000103610347400657023304 0ustar siretartsiretart-----BEGIN CERTIFICATE REQUEST----- MIIBWDCBwgIBADAZMRcwFQYDVQQDEw5TVE9SRS0wMDAwMDAwODCBnzANBgkqhkiG 9w0BAQEFAAOBjQAwgYkCgYEAzY9Xxkgmkpf9cNZUVSPKhOgao70+kdF1xnSFfnZP 5h5hNzTgj3Aav4MWTmHr6fkJ8DHPp1Ekuz8f5/qvdnJd2vLbSJ32Yy6oPaP8KXen QXx+IC0xuGxLo4TLdceGxFCZP7ln55ORYbCVwwa5R0SWTYkvQLJnvuWb4389z4ct GFsCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIdlFo8gbik1K/+4Ra87cQDZzn0L wE9bZrxRMPXqGjCQ8HBCfvQMFa1Oc6fEczCJ/nmmd76j0HIXW7uYOELIT8L/Zvf5 jw/z9/OvEOQal7H2JN2d6W4ZmYpQko5+e/bJmlrOxyBpcXk34BvyQen9pTmI6J4E pkBN/5XUUvVJSM67 -----END CERTIFICATE REQUEST----- boxbackup/test/backupstore/testfiles/bbstored.conf0000664000175000017500000000055111221742616023273 0ustar siretartsiretart RaidFileConf = testfiles/raidfile.conf AccountDatabase = testfiles/accounts.txt ExtendedLogging = yes TimeBetweenHousekeeping = 10 Server { PidFile = testfiles/bbstored.pid ListenAddresses = inet:localhost:22011 CertificateFile = testfiles/serverCerts.pem PrivateKeyFile = testfiles/serverPrivKey.pem TrustedCAsFile = testfiles/serverTrustedCAs.pem } boxbackup/test/backupstore/testfiles/raidfile.conf0000664000175000017500000000015710347400657023254 0ustar siretartsiretart disc0 { SetNumber = 0 BlockSize = 2048 Dir0 = testfiles/0_0 Dir1 = testfiles/0_1 Dir2 = testfiles/0_2 } boxbackup/test/backupstore/Makefile.extra0000664000175000017500000000006711221741522021375 0ustar siretartsiretartlink-extra: ../../bin/bbstored/HousekeepStoreAccount.o boxbackup/test/common/0000775000175000017500000000000011652362372015570 5ustar siretartsiretartboxbackup/test/common/testcommon.cpp0000664000175000017500000006251211173734217020471 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testcommon.cpp // Purpose: Tests for the code in lib/common // Created: 2003/07/23 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include "Test.h" #include "Configuration.h" #include "FdGetLine.h" #include "Guards.h" #include "FileStream.h" #include "InvisibleTempFileStream.h" #include "IOStreamGetLine.h" #include "NamedLock.h" #include "ReadGatherStream.h" #include "MemBlockStream.h" #include "ExcludeList.h" #include "CommonException.h" #include "Conversion.h" #include "autogen_ConversionException.h" #include "CollectInBufferStream.h" #include "Archive.h" #include "Timer.h" #include "Logging.h" #include "ZeroStream.h" #include "PartialReadStream.h" #include "MemLeakFindOn.h" using namespace BoxConvert; void test_conversions() { TEST_THAT((Convert(std::string("32"))) == 32); TEST_THAT((Convert("42")) == 42); TEST_THAT((Convert("-42")) == -42); TEST_CHECK_THROWS((Convert("500")), ConversionException, IntOverflowInConvertFromString); TEST_CHECK_THROWS((Convert("pants")), ConversionException, BadStringRepresentationOfInt); TEST_CHECK_THROWS((Convert("")), ConversionException, CannotConvertEmptyStringToInt); std::string a(Convert(63)); TEST_THAT(a == "63"); std::string b(Convert(-3473463)); TEST_THAT(b == "-3473463"); std::string c(Convert(344)); TEST_THAT(c == "344"); } ConfigurationVerifyKey verifykeys1_1_1[] = { ConfigurationVerifyKey("bing", ConfigTest_Exists), ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("terrible", ConfigTest_Exists | ConfigTest_LastEntry) }; ConfigurationVerifyKey verifykeys1_1_2[] = { ConfigurationVerifyKey("fish", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry) }; ConfigurationVerify verifysub1_1[] = { { "*", 0, verifykeys1_1_1, ConfigTest_Exists, 0 }, { "otherthing", 0, verifykeys1_1_2, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; ConfigurationVerifyKey verifykeys1_1[] = { ConfigurationVerifyKey("value", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("string1", ConfigTest_Exists), ConfigurationVerifyKey("string2", ConfigTest_Exists | ConfigTest_LastEntry) }; ConfigurationVerifyKey verifykeys1_2[] = { ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt), ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry) }; ConfigurationVerify verifysub1[] = { { "test1", verifysub1_1, verifykeys1_1, ConfigTest_Exists, 0 }, { "ping", 0, verifykeys1_2, ConfigTest_Exists | ConfigTest_LastEntry, 0 } }; ConfigurationVerifyKey verifykeys1[] = { ConfigurationVerifyKey("notExpected", 0), ConfigurationVerifyKey("HasDefaultValue", 0, "Lovely default value"), ConfigurationVerifyKey("MultiValue", ConfigTest_MultiValueAllowed), ConfigurationVerifyKey("BoolTrue1", ConfigTest_IsBool), ConfigurationVerifyKey("BoolTrue2", ConfigTest_IsBool), ConfigurationVerifyKey("BoolFalse1", ConfigTest_IsBool), ConfigurationVerifyKey("BoolFalse2", ConfigTest_IsBool), ConfigurationVerifyKey("TOPlevel", ConfigTest_LastEntry | ConfigTest_Exists) }; ConfigurationVerify verify = { "root", verifysub1, verifykeys1, ConfigTest_Exists | ConfigTest_LastEntry, 0 }; class TestLogger : public Logger { private: bool mTriggered; Log::Level mTargetLevel; public: TestLogger(Log::Level targetLevel) : mTriggered(false), mTargetLevel(targetLevel) { Logging::Add(this); } ~TestLogger() { Logging::Remove(this); } bool IsTriggered() { return mTriggered; } void Reset() { mTriggered = false; } virtual bool Log(Log::Level level, const std::string& rFile, int line, std::string& rMessage) { if (level == mTargetLevel) { mTriggered = true; } return true; } virtual const char* GetType() { return "Test"; } virtual void SetProgramName(const std::string& rProgramName) { } }; int test(int argc, const char *argv[]) { // Test PartialReadStream and ReadGatherStream handling of files // over 2GB (refs #2) { char buffer[8]; ZeroStream zero(0x80000003); zero.Seek(0x7ffffffe, IOStream::SeekType_Absolute); TEST_THAT(zero.GetPosition() == 0x7ffffffe); TEST_THAT(zero.Read(buffer, 8) == 5); TEST_THAT(zero.GetPosition() == 0x80000003); TEST_THAT(zero.Read(buffer, 8) == 0); zero.Seek(0, IOStream::SeekType_Absolute); TEST_THAT(zero.GetPosition() == 0); char* buffer2 = new char [0x1000000]; TEST_THAT(buffer2 != NULL); PartialReadStream part(zero, 0x80000002); for (int i = 0; i < 0x80; i++) { int read = part.Read(buffer2, 0x1000000); TEST_THAT(read == 0x1000000); } TEST_THAT(part.Read(buffer, 8) == 2); TEST_THAT(part.Read(buffer, 8) == 0); delete [] buffer2; ReadGatherStream gather(false); zero.Seek(0, IOStream::SeekType_Absolute); int component = gather.AddComponent(&zero); gather.AddBlock(component, 0x80000002); TEST_THAT(gather.Read(buffer, 8) == 8); } // Test self-deleting temporary file streams { std::string tempfile("testfiles/tempfile"); TEST_CHECK_THROWS(InvisibleTempFileStream fs(tempfile.c_str()), CommonException, OSFileOpenError); InvisibleTempFileStream fs(tempfile.c_str(), O_CREAT); #ifdef WIN32 // file is still visible under Windows TEST_THAT(TestFileExists(tempfile.c_str())); // opening it again should work InvisibleTempFileStream fs2(tempfile.c_str()); TEST_THAT(TestFileExists(tempfile.c_str())); // opening it to create should work InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT); TEST_THAT(TestFileExists(tempfile.c_str())); // opening it to create exclusively should fail TEST_CHECK_THROWS(InvisibleTempFileStream fs4(tempfile.c_str(), O_CREAT | O_EXCL), CommonException, OSFileOpenError); fs2.Close(); #else // file is not visible under Unix TEST_THAT(!TestFileExists(tempfile.c_str())); // opening it again should fail TEST_CHECK_THROWS(InvisibleTempFileStream fs2(tempfile.c_str()), CommonException, OSFileOpenError); // opening it to create should work InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT); TEST_THAT(!TestFileExists(tempfile.c_str())); // opening it to create exclusively should work InvisibleTempFileStream fs4(tempfile.c_str(), O_CREAT | O_EXCL); TEST_THAT(!TestFileExists(tempfile.c_str())); fs4.Close(); #endif fs.Close(); fs3.Close(); // now that it's closed, it should be invisible on all platforms TEST_THAT(!TestFileExists(tempfile.c_str())); } // Test that memory leak detection doesn't crash { char *test = new char[1024]; delete [] test; MemBlockStream *s = new MemBlockStream(test,12); delete s; } #ifdef BOX_MEMORY_LEAK_TESTING { Timers::Cleanup(); TEST_THAT(memleakfinder_numleaks() == 0); void *block = ::malloc(12); TEST_THAT(memleakfinder_numleaks() == 1); void *b2 = ::realloc(block, 128*1024); TEST_THAT(memleakfinder_numleaks() == 1); ::free(b2); TEST_THAT(memleakfinder_numleaks() == 0); char *test = new char[1024]; TEST_THAT(memleakfinder_numleaks() == 1); MemBlockStream *s = new MemBlockStream(test,12); TEST_THAT(memleakfinder_numleaks() == 2); delete s; TEST_THAT(memleakfinder_numleaks() == 1); delete [] test; TEST_THAT(memleakfinder_numleaks() == 0); Timers::Init(); } #endif // BOX_MEMORY_LEAK_TESTING // test main() initialises timers for us, so uninitialise them Timers::Cleanup(); // Check that using timer methods without initialisation // throws an assertion failure. Can only do this in debug mode #ifndef BOX_RELEASE_BUILD TEST_CHECK_THROWS(Timers::Add(*(Timer*)NULL), CommonException, AssertFailed); TEST_CHECK_THROWS(Timers::Remove(*(Timer*)NULL), CommonException, AssertFailed); #endif // TEST_CHECK_THROWS(Timers::Signal(), CommonException, AssertFailed); #ifndef BOX_RELEASE_BUILD TEST_CHECK_THROWS(Timers::Cleanup(), CommonException, AssertFailed); #endif // Check that we can initialise the timers Timers::Init(); // Check that double initialisation throws an exception #ifndef BOX_RELEASE_BUILD TEST_CHECK_THROWS(Timers::Init(), CommonException, AssertFailed); #endif // Check that we can clean up the timers Timers::Cleanup(); // Check that double cleanup throws an exception #ifndef BOX_RELEASE_BUILD TEST_CHECK_THROWS(Timers::Cleanup(), CommonException, AssertFailed); #endif Timers::Init(); Timer t0(0, "t0"); // should never expire Timer t1(1, "t1"); Timer t2(2, "t2"); Timer t3(3, "t3"); TEST_THAT(!t0.HasExpired()); TEST_THAT(!t1.HasExpired()); TEST_THAT(!t2.HasExpired()); TEST_THAT(!t3.HasExpired()); safe_sleep(1); TEST_THAT(!t0.HasExpired()); TEST_THAT(t1.HasExpired()); TEST_THAT(!t2.HasExpired()); TEST_THAT(!t3.HasExpired()); safe_sleep(1); TEST_THAT(!t0.HasExpired()); TEST_THAT(t1.HasExpired()); TEST_THAT(t2.HasExpired()); TEST_THAT(!t3.HasExpired()); t1 = Timer(1, "t1a"); t2 = Timer(2, "t2a"); TEST_THAT(!t0.HasExpired()); TEST_THAT(!t1.HasExpired()); TEST_THAT(!t2.HasExpired()); safe_sleep(1); TEST_THAT(!t0.HasExpired()); TEST_THAT(t1.HasExpired()); TEST_THAT(!t2.HasExpired()); TEST_THAT(t3.HasExpired()); // Leave timers initialised for rest of test. // Test main() will cleanup after test finishes. static const char *testfilelines[] = { "First line", "Second line", "Third", "", "", "", "sdf hjjk", "", "test", "test#not comment", "test#not comment", "", "nice line", "fish", "", "ping", "", "", "Nothing", "Nothing", 0 }; // First, test the FdGetLine class -- rather important this works! { FileHandleGuard file("testfiles" DIRECTORY_SEPARATOR "fdgetlinetest.txt"); FdGetLine getline(file); int l = 0; while(testfilelines[l] != 0) { TEST_THAT(!getline.IsEOF()); std::string line = getline.GetLine(true); //printf("expected |%s| got |%s|\n", lines[l], line.c_str()); TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0); l++; } TEST_THAT(getline.IsEOF()); TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF); } // and again without pre-processing { FileHandleGuard file("testfiles" DIRECTORY_SEPARATOR "fdgetlinetest.txt"); FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR "fdgetlinetest.txt", "r"); TEST_THAT_ABORTONFAIL(file2 != 0); FdGetLine getline(file); char ll[512]; while(!feof(file2)) { fgets(ll, sizeof(ll), file2); int e = strlen(ll); while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r')) { e--; } ll[e] = '\0'; TEST_THAT(!getline.IsEOF()); std::string line = getline.GetLine(false); //printf("expected |%s| got |%s|\n", ll, line.c_str()); TEST_THAT(strcmp(ll, line.c_str()) == 0); } TEST_THAT(getline.IsEOF()); TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF); fclose(file2); } // Then the IOStream version of get line, seeing as we're here... { FileStream file("testfiles" DIRECTORY_SEPARATOR "fdgetlinetest.txt", O_RDONLY); IOStreamGetLine getline(file); int l = 0; while(testfilelines[l] != 0) { TEST_THAT(!getline.IsEOF()); std::string line; while(!getline.GetLine(line, true)) ; //printf("expected |%s| got |%s|\n", lines[l], line.c_str()); TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0); l++; } TEST_THAT(getline.IsEOF()); std::string dummy; TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF); } // and again without pre-processing { FileStream file("testfiles" DIRECTORY_SEPARATOR "fdgetlinetest.txt", O_RDONLY); IOStreamGetLine getline(file); FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR "fdgetlinetest.txt", "r"); TEST_THAT_ABORTONFAIL(file2 != 0); char ll[512]; while(!feof(file2)) { fgets(ll, sizeof(ll), file2); int e = strlen(ll); while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r')) { e--; } ll[e] = '\0'; TEST_THAT(!getline.IsEOF()); std::string line; while(!getline.GetLine(line, false)) ; //printf("expected |%s| got |%s|\n", ll, line.c_str()); TEST_THAT(strcmp(ll, line.c_str()) == 0); } TEST_THAT(getline.IsEOF()); std::string dummy; TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF); fclose(file2); } // Doesn't exist { std::string errMsg; TEST_CHECK_THROWS(std::auto_ptr pconfig( Configuration::LoadAndVerify( "testfiles" DIRECTORY_SEPARATOR "DOESNTEXIST", &verify, errMsg)), CommonException, OSFileOpenError); } // Basic configuration test { std::string errMsg; std::auto_ptr pconfig( Configuration::LoadAndVerify( "testfiles" DIRECTORY_SEPARATOR "config1.txt", &verify, errMsg)); if(!errMsg.empty()) { printf("UNEXPECTED error msg is:\n------\n%s------\n", errMsg.c_str()); } TEST_THAT_ABORTONFAIL(pconfig.get() != 0); TEST_THAT(errMsg.empty()); TEST_THAT(pconfig->KeyExists("TOPlevel")); TEST_THAT(pconfig->GetKeyValue("TOPlevel") == "value"); TEST_THAT(pconfig->KeyExists("MultiValue")); TEST_THAT(pconfig->GetKeyValue("MultiValue") == "single"); TEST_THAT(!pconfig->KeyExists("not exist")); TEST_THAT(pconfig->KeyExists("HasDefaultValue")); TEST_THAT(pconfig->GetKeyValue("HasDefaultValue") == "Lovely default value"); TEST_CHECK_THROWS(pconfig->GetKeyValue("not exist"), CommonException, ConfigNoKey); // list of keys std::vector keylist(pconfig->GetKeyNames()); TEST_THAT(keylist.size() == 3); // will be sorted alphanumerically TEST_THAT(keylist[2] == "TOPlevel" && keylist[1] == "MultiValue" && keylist[0] == "HasDefaultValue"); // list of sub configurations std::vector sublist(pconfig->GetSubConfigurationNames()); TEST_THAT(sublist.size() == 2); TEST_THAT(sublist[0] == "test1"); TEST_THAT(sublist[1] == "ping"); TEST_THAT(pconfig->SubConfigurationExists("test1")); TEST_THAT(pconfig->SubConfigurationExists("ping")); TEST_CHECK_THROWS(pconfig->GetSubConfiguration("nosubconfig"), CommonException, ConfigNoSubConfig); // Get a sub configuration const Configuration &sub1 = pconfig->GetSubConfiguration("test1"); TEST_THAT(sub1.GetKeyValueInt("value") == 12); std::vector sublist2(sub1.GetSubConfigurationNames()); TEST_THAT(sublist2.size() == 4); // And the sub-sub configs const Configuration &sub1_1 = sub1.GetSubConfiguration("subconfig"); TEST_THAT(sub1_1.GetKeyValueInt("carrots") == 0x2356); const Configuration &sub1_2 = sub1.GetSubConfiguration("subconfig2"); TEST_THAT(sub1_2.GetKeyValueInt("carrots") == -243895); const Configuration &sub1_3 = sub1.GetSubConfiguration("subconfig3"); TEST_THAT(sub1_3.GetKeyValueInt("carrots") == 050); TEST_THAT(sub1_3.GetKeyValue("terrible") == "absolutely"); } static const char *file[] = { "testfiles" DIRECTORY_SEPARATOR "config2.txt", // Value missing from root "testfiles" DIRECTORY_SEPARATOR "config3.txt", // Unexpected { "testfiles" DIRECTORY_SEPARATOR "config4.txt", // Missing } "testfiles" DIRECTORY_SEPARATOR "config5.txt", // { expected, but wasn't there "testfiles" DIRECTORY_SEPARATOR "config6.txt", // Duplicate key "testfiles" DIRECTORY_SEPARATOR "config7.txt", // Invalid key (no name) "testfiles" DIRECTORY_SEPARATOR "config8.txt", // Not all sub blocks terminated "testfiles" DIRECTORY_SEPARATOR "config9.txt", // Not valid integer "testfiles" DIRECTORY_SEPARATOR "config9b.txt", // Not valid integer "testfiles" DIRECTORY_SEPARATOR "config9c.txt", // Not valid integer "testfiles" DIRECTORY_SEPARATOR "config9d.txt", // Not valid integer "testfiles" DIRECTORY_SEPARATOR "config10.txt", // Missing key (in subblock) "testfiles" DIRECTORY_SEPARATOR "config11.txt", // Unknown key "testfiles" DIRECTORY_SEPARATOR "config12.txt", // Missing block "testfiles" DIRECTORY_SEPARATOR "config13.txt", // Subconfig (wildcarded) should exist, but missing (ie nothing present) "testfiles" DIRECTORY_SEPARATOR "config16.txt", // bad boolean value 0 }; for(int l = 0; file[l] != 0; ++l) { std::string errMsg; std::auto_ptr pconfig(Configuration::LoadAndVerify(file[l], &verify, errMsg)); TEST_THAT(pconfig.get() == 0); TEST_THAT(!errMsg.empty()); printf("(%s) Error msg is:\n------\n%s------\n", file[l], errMsg.c_str()); } // Check that multivalues happen as expected // (single value in a multivalue already checked) { std::string errMsg; std::auto_ptr pconfig( Configuration::LoadAndVerify( "testfiles" DIRECTORY_SEPARATOR "config14.txt", &verify, errMsg)); TEST_THAT(pconfig.get() != 0); TEST_THAT(errMsg.empty()); TEST_THAT(pconfig->KeyExists("MultiValue")); // values are separated by a specific character std::string expectedvalue("value1"); expectedvalue += Configuration::MultiValueSeparator; expectedvalue += "secondvalue"; TEST_THAT(pconfig->GetKeyValue("MultiValue") == expectedvalue); } // Check boolean values { std::string errMsg; std::auto_ptr pconfig( Configuration::LoadAndVerify( "testfiles" DIRECTORY_SEPARATOR "config15.txt", &verify, errMsg)); TEST_THAT(pconfig.get() != 0); TEST_THAT(errMsg.empty()); TEST_THAT(pconfig->GetKeyValueBool("BoolTrue1") == true); TEST_THAT(pconfig->GetKeyValueBool("BoolTrue2") == true); TEST_THAT(pconfig->GetKeyValueBool("BoolFalse1") == false); TEST_THAT(pconfig->GetKeyValueBool("BoolFalse2") == false); } // Test named locks { NamedLock lock1; // Try and get a lock on a name in a directory which doesn't exist TEST_CHECK_THROWS(lock1.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "non-exist" DIRECTORY_SEPARATOR "lock"), CommonException, OSFileError); // And a more resonable request TEST_THAT(lock1.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "lock1") == true); // Try to lock something using the same lock TEST_CHECK_THROWS( lock1.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "non-exist" DIRECTORY_SEPARATOR "lock2"), CommonException, NamedLockAlreadyLockingSomething); #if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK // And again on that name NamedLock lock2; TEST_THAT(lock2.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "lock1") == false); #endif } { // Check that it unlocked when it went out of scope NamedLock lock3; TEST_THAT(lock3.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "lock1") == true); } { // And unlocking works NamedLock lock4; TEST_CHECK_THROWS(lock4.ReleaseLock(), CommonException, NamedLockNotHeld); TEST_THAT(lock4.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "lock4") == true); lock4.ReleaseLock(); NamedLock lock5; TEST_THAT(lock5.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "lock4") == true); // And can reuse it TEST_THAT(lock4.TryAndGetLock( "testfiles" DIRECTORY_SEPARATOR "lock5") == true); } // Test the ReadGatherStream { #define GATHER_DATA1 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" #define GATHER_DATA2 "ZYZWVUTSRQPOMNOLKJIHGFEDCBA9876543210zyxwvutsrqpomno" // Make two streams MemBlockStream s1(GATHER_DATA1, sizeof(GATHER_DATA1)); MemBlockStream s2(GATHER_DATA2, sizeof(GATHER_DATA2)); // And a gather stream ReadGatherStream gather(false /* no deletion */); // Add the streams int s1_c = gather.AddComponent(&s1); int s2_c = gather.AddComponent(&s2); TEST_THAT(s1_c == 0); TEST_THAT(s2_c == 1); // Set up some blocks gather.AddBlock(s1_c, 11); gather.AddBlock(s1_c, 2); gather.AddBlock(s1_c, 8, true, 2); gather.AddBlock(s2_c, 20); gather.AddBlock(s1_c, 20); gather.AddBlock(s2_c, 25); gather.AddBlock(s1_c, 10, true, 0); #define GATHER_RESULT "0123456789abc23456789ZYZWVUTSRQPOMNOLKJIHabcdefghijklmnopqrstGFEDCBA9876543210zyxwvuts0123456789" // Read them in... char buffer[1024]; unsigned int r = 0; while(r < sizeof(GATHER_RESULT) - 1) { int s = gather.Read(buffer + r, 7); r += s; TEST_THAT(gather.GetPosition() == r); if(r < sizeof(GATHER_RESULT) - 1) { TEST_THAT(gather.StreamDataLeft()); TEST_THAT(static_cast(gather.BytesLeftToRead()) == sizeof(GATHER_RESULT) - 1 - r); } else { TEST_THAT(!gather.StreamDataLeft()); TEST_THAT(gather.BytesLeftToRead() == 0); } } TEST_THAT(r == sizeof(GATHER_RESULT) - 1); TEST_THAT(::memcmp(buffer, GATHER_RESULT, sizeof(GATHER_RESULT) - 1) == 0); } // Test ExcludeList { ExcludeList elist; // Check assumption TEST_THAT(Configuration::MultiValueSeparator == '\x01'); // Add definite entries elist.AddDefiniteEntries(std::string("\x01")); elist.AddDefiniteEntries(std::string("")); elist.AddDefiniteEntries(std::string("Definite1\x01/dir/DefNumberTwo\x01\x01ThingDefThree")); elist.AddDefiniteEntries(std::string("AnotherDef")); TEST_THAT(elist.SizeOfDefiniteList() == 4); // Add regex entries #ifdef HAVE_REGEX_SUPPORT elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$")); elist.AddRegexEntries(std::string("")); TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[:not_valid")), CommonException, BadRegularExpression); TEST_THAT(elist.SizeOfRegexList() == 3); #else TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$")), CommonException, RegexNotSupportedOnThisPlatform); TEST_THAT(elist.SizeOfRegexList() == 0); #endif #ifdef WIN32 #define CASE_SENSITIVE false #else #define CASE_SENSITIVE true #endif // Try some matches! TEST_THAT(elist.IsExcluded(std::string("Definite1")) == true); TEST_THAT(elist.IsExcluded(std::string("/dir/DefNumberTwo")) == true); TEST_THAT(elist.IsExcluded(std::string("ThingDefThree")) == true); TEST_THAT(elist.IsExcluded(std::string("AnotherDef")) == true); TEST_THAT(elist.IsExcluded(std::string("dir/DefNumberTwo")) == false); // Try some case insensitive matches, // that should pass on Win32 and fail elsewhere TEST_THAT(elist.IsExcluded("DEFINITe1") == !CASE_SENSITIVE); TEST_THAT(elist.IsExcluded("/Dir/DefNumberTwo") == !CASE_SENSITIVE); TEST_THAT(elist.IsExcluded("thingdefthree") == !CASE_SENSITIVE); #ifdef HAVE_REGEX_SUPPORT TEST_THAT(elist.IsExcluded(std::string("b.reg")) == true); TEST_THAT(elist.IsExcluded(std::string("B.reg")) == !CASE_SENSITIVE); TEST_THAT(elist.IsExcluded(std::string("b.Reg")) == !CASE_SENSITIVE); TEST_THAT(elist.IsExcluded(std::string("e.reg")) == false); TEST_THAT(elist.IsExcluded(std::string("e.Reg")) == false); TEST_THAT(elist.IsExcluded(std::string("DEfinite1")) == !CASE_SENSITIVE); TEST_THAT(elist.IsExcluded(std::string("DEXCLUDEfinite1")) == true); TEST_THAT(elist.IsExcluded(std::string("DEfinitexclude1")) == !CASE_SENSITIVE); TEST_THAT(elist.IsExcluded(std::string("exclude")) == true); TEST_THAT(elist.IsExcluded(std::string("ExcludE")) == !CASE_SENSITIVE); #endif #undef CASE_SENSITIVE TestLogger logger(Log::WARNING); TEST_THAT(!logger.IsTriggered()); elist.AddDefiniteEntries(std::string("/foo")); TEST_THAT(!logger.IsTriggered()); elist.AddDefiniteEntries(std::string("/foo/")); TEST_THAT(logger.IsTriggered()); logger.Reset(); elist.AddDefiniteEntries(std::string("/foo" DIRECTORY_SEPARATOR)); TEST_THAT(logger.IsTriggered()); logger.Reset(); elist.AddDefiniteEntries(std::string("/foo" DIRECTORY_SEPARATOR "bar\x01/foo")); TEST_THAT(!logger.IsTriggered()); elist.AddDefiniteEntries(std::string("/foo" DIRECTORY_SEPARATOR "bar\x01/foo" DIRECTORY_SEPARATOR)); TEST_THAT(logger.IsTriggered()); } test_conversions(); // test that we can use Archive and CollectInBufferStream // to read and write arbitrary types to a memory buffer { CollectInBufferStream buffer; ASSERT(buffer.GetPosition() == 0); { Archive archive(buffer, 0); ASSERT(buffer.GetPosition() == 0); archive.Write((bool) true); archive.Write((bool) false); archive.Write((int) 0x12345678); archive.Write((int) 0x87654321); archive.Write((int64_t) 0x0badfeedcafebabeLL); archive.Write((uint64_t) 0xfeedfacedeadf00dLL); archive.Write((uint8_t) 0x01); archive.Write((uint8_t) 0xfe); archive.Write(std::string("hello world!")); archive.Write(std::string("goodbye cruel world!")); } CollectInBufferStream buf2; buf2.Write(buffer.GetBuffer(), buffer.GetSize()); TEST_THAT(buf2.GetPosition() == buffer.GetSize()); buf2.SetForReading(); TEST_THAT(buf2.GetPosition() == 0); { Archive archive(buf2, 0); TEST_THAT(buf2.GetPosition() == 0); bool b; archive.Read(b); TEST_THAT(b == true); archive.Read(b); TEST_THAT(b == false); int i; archive.Read(i); TEST_THAT(i == 0x12345678); archive.Read(i); TEST_THAT((unsigned int)i == 0x87654321); uint64_t i64; archive.Read(i64); TEST_THAT(i64 == 0x0badfeedcafebabeLL); archive.Read(i64); TEST_THAT(i64 == 0xfeedfacedeadf00dLL); uint8_t i8; archive.Read(i8); TEST_THAT(i8 == 0x01); archive.Read(i8); TEST_THAT(i8 == 0xfe); std::string s; archive.Read(s); TEST_THAT(s == "hello world!"); archive.Read(s); TEST_THAT(s == "goodbye cruel world!"); TEST_THAT(!buf2.StreamDataLeft()); } } return 0; } boxbackup/test/common/testfiles/0000775000175000017500000000000011652362372017572 5ustar siretartsiretartboxbackup/test/common/testfiles/config9.txt0000664000175000017500000000060410347400657021670 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050X terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config9c.txt0000664000175000017500000000060410347400657022033 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=2430-895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/fdgetlinetest.txt0000664000175000017500000000033210347400657023171 0ustar siretartsiretartFirst line Second line Third # comment # comment sdf hjjk test #coment test#not comment test#not comment #comment nice line fish ping #comment Nothing Nothingboxbackup/test/common/testfiles/config5.txt0000664000175000017500000000060010347400657021660 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config4.txt0000664000175000017500000000060610347400657021665 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } } boxbackup/test/common/testfiles/config2.txt0000664000175000017500000000063710347400657021667 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } # make this value missing # TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config8.txt0000664000175000017500000000060110347400657021664 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts boxbackup/test/common/testfiles/config12.txt0000664000175000017500000000053010347400657021740 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config14.txt0000664000175000017500000000066110347400657021747 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value MultiValue = value1 MultiValue = secondvalue ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config7.txt0000664000175000017500000000063310347400657021670 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 = invalid thing here! terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config1.txt0000664000175000017500000000063010347400657021657 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value MultiValue = single ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config6.txt0000664000175000017500000000063210347400657021666 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something bing= something else carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config13.txt0000664000175000017500000000020210347400657021735 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config9d.txt0000664000175000017500000000060310347400657022033 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =090 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config15.txt0000664000175000017500000000073510347400657021752 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value MultiValue = single ping { carrots=324 string = casrts } BoolTrue1 = true BoolTrue2 = yes BoolFalse1 = fAlse BoolFalse2 = nO boxbackup/test/common/testfiles/config3.txt0000664000175000017500000000060610347400657021664 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may { string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config10.txt0000664000175000017500000000056110347400657021742 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config16.txt0000664000175000017500000000066710347400657021757 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value MultiValue = single ping { carrots=324 string = casrts } BoolTrue1 = not a valid value boxbackup/test/common/testfiles/config11.txt0000664000175000017500000000063010347400657021740 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 NOTEXPECTED= 34234 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/common/testfiles/config9b.txt0000664000175000017500000000060410347400657022032 0ustar siretartsiretarttest1 { value=12 string1 = carrots in may string2 =on the string subconfig { bing= nothing really carrots =0x2356 terrible=lovely } subconfig2 { bing= something carrots=C-243895 terrible=pgin! } subconfig3 { bing= 435 carrots =050 terrible=absolutely } otherthing { string= ping fish =0 } } TOPlevel= value ping { carrots=324 string = casrts } boxbackup/test/bbackupd/0000775000175000017500000000000011652362372016053 5ustar siretartsiretartboxbackup/test/bbackupd/testextra0000664000175000017500000000013210347400657020014 0ustar siretartsiretartmkdir testfiles/0_0 mkdir testfiles/0_1 mkdir testfiles/0_2 mkdir testfiles/bbackupd-data boxbackup/test/bbackupd/testfiles/0000775000175000017500000000000011652362372020055 5ustar siretartsiretartboxbackup/test/bbackupd/testfiles/bbackupd-snapshot.conf.in0000664000175000017500000000232411017224004024722 0ustar siretartsiretart CertificateFile = testfiles/clientCerts.pem PrivateKeyFile = testfiles/clientPrivKey.pem TrustedCAsFile = testfiles/clientTrustedCAs.pem KeysFile = testfiles/bbackupd.keys DataDirectory = testfiles/bbackupd-data StoreHostname = localhost StorePort = 22011 AccountNumber = 0x01234567 AutomaticBackup = no UpdateStoreInterval = 0 MinimumFileAge = 4 MaxUploadWait = 24 DeleteRedundantLocationsAfter = 10 FileTrackingSizeThreshold = 1024 DiffingUploadSizeThreshold = 1024 MaximumDiffingTime = 3 KeepAliveTime = 1 ExtendedLogging = no ExtendedLogFile = testfiles/bbackupd.log CommandSocket = testfiles/bbackupd.sock NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl Server { PidFile = testfiles/bbackupd.pid } BackupLocations { Test1 { Path = testfiles/TestDir1 ExcludeFile = testfiles/TestDir1/excluded_1 ExcludeFile = testfiles/TestDir1/excluded_2 ExcludeFilesRegex = \.excludethis$ ExcludeFilesRegex = EXCLUDE AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis ExcludeDir = testfiles/TestDir1/exclude_dir ExcludeDir = testfiles/TestDir1/exclude_dir_2 ExcludeDirsRegex = not_this_dir AlwaysIncludeDirsRegex = ALWAYSINCLUDE } } boxbackup/test/bbackupd/testfiles/extcheck1.pl.in0000775000175000017500000000165711175045716022712 0ustar siretartsiretart#!@PERL@ use strict; my $flags = $ARGV[0] or ""; unless(open IN,"../../bin/bbackupquery/bbackupquery -Wwarning " . "-c testfiles/bbackupd.conf " . "-l testfiles/query4.log " . "\"compare -ac$flags\" quit 2>&1 |") { print "FAIL: opening compare utility\n"; exit 2; } my $ret = 1; my $seen = 0; while() { next unless m/\S/; print "READ: $_"; if (m/continousupdate/) { unless (/exists/) { print "FAIL: continousupdate line does not match\n"; $ret = 2; } $seen = 1; } elsif (m/^No entry for terminal type/ or m/^using dumb terminal settings/) { # skip these lines, may happen in Debian buildd # with no terminal. } else { unless (/\AWARNING/ or /\ADifferences/ or /might be reason/ or /probably due to file mod/) { print "FAIL: Summary line does not match\n"; $ret = 2; } } } close IN; unless ($seen) { print "FAIL: missing line matching continousupdate\n"; $ret = 2; } exit $ret; boxbackup/test/bbackupd/testfiles/bbackupd-symlink.conf.in0000664000175000017500000000231211017224004024546 0ustar siretartsiretart CertificateFile = testfiles/clientCerts.pem PrivateKeyFile = testfiles/clientPrivKey.pem TrustedCAsFile = testfiles/clientTrustedCAs.pem KeysFile = testfiles/bbackupd.keys DataDirectory = testfiles/bbackupd-data StoreHostname = localhost StorePort = 22011 AccountNumber = 0x01234567 UpdateStoreInterval = 3 MinimumFileAge = 4 MaxUploadWait = 24 DeleteRedundantLocationsAfter = 10 FileTrackingSizeThreshold = 1024 DiffingUploadSizeThreshold = 1024 MaximumDiffingTime = 3 KeepAliveTime = 1 ExtendedLogging = no ExtendedLogFile = testfiles/bbackupd.log CommandSocket = testfiles/bbackupd.sock NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl Server { PidFile = testfiles/bbackupd.pid } BackupLocations { Test1 { Path = testfiles/symlink-to-TestDir1 ExcludeFile = testfiles/TestDir1/excluded_1 ExcludeFile = testfiles/TestDir1/excluded_2 ExcludeFilesRegex = \.excludethis$ ExcludeFilesRegex = EXCLUDE AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis ExcludeDir = testfiles/TestDir1/exclude_dir ExcludeDir = testfiles/TestDir1/exclude_dir_2 ExcludeDirsRegex = not_this_dir AlwaysIncludeDirsRegex = ALWAYSINCLUDE } } boxbackup/test/bbackupd/testfiles/serverPrivKey.pem0000664000175000017500000000156710347400657023410 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4 bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l 4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN 8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48 q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn -----END RSA PRIVATE KEY----- boxbackup/test/bbackupd/testfiles/bbackupd-exclude.conf.in0000664000175000017500000000163311017224004024516 0ustar siretartsiretart CertificateFile = testfiles/clientCerts.pem PrivateKeyFile = testfiles/clientPrivKey.pem TrustedCAsFile = testfiles/clientTrustedCAs.pem KeysFile = testfiles/bbackupd.keys DataDirectory = testfiles/bbackupd-data StoreHostname = localhost StorePort = 22011 AccountNumber = 0x01234567 UpdateStoreInterval = 3 MinimumFileAge = 4 MaxUploadWait = 24 DeleteRedundantLocationsAfter = 10 FileTrackingSizeThreshold = 1024 DiffingUploadSizeThreshold = 1024 MaximumDiffingTime = 3 KeepAliveTime = 1 ExtendedLogging = no ExtendedLogFile = testfiles/bbackupd.log CommandSocket = testfiles/bbackupd.sock NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl Server { PidFile = testfiles/bbackupd.pid } BackupLocations { Test1 { Path = testfiles/TestDir1 ExcludeDir = testfiles/TestDir1/spacetest/d3 ExcludeFile = testfiles/TestDir1/spacetest/f2 } } boxbackup/test/bbackupd/testfiles/accounts.txt0000664000175000017500000000000010347400657022422 0ustar siretartsiretartboxbackup/test/bbackupd/testfiles/clientCerts.pem0000664000175000017500000000114710347400657023041 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92 OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ== -----END CERTIFICATE----- boxbackup/test/bbackupd/testfiles/test3.tgz0000664000175000017500000012763510347363533021663 0ustar siretartsiretart‹Õ„?ì} xSÇ•èØ–À›nˆ!üÿƒÁ`d’†°IŽ%ÙWF–„t¯Ái á!”Õ·K[6/Ý ÙGwÙ>v›:):b’²­Ë£ m²›|-m/1_K·lâG\ôΙ9W’ÍOÛÝÍ~Û·ÜŸ{fΜ9çÌÌ™3s¯îlp”.1û¯¢¢²¢Šòr€EÅåEÉP¿XQEEYE)¤— ]qÑ‚â"Vþy ¥_j@±ù¡Ê&§çžt¿+ÿôÚ@í_h—òçT4gQyYÙ]Úµÿ‚²âÒŠ²²¢Rlÿâò€Ÿ“<ƒ®ÿâí¿ûù”‘Ì 7ÙŒ]NiRv"¿>ô³àµÌ –ˆjˆì2„‡½ùQjdE¥áÈùmŒ…Ÿ2t¾¯<<ÛßZhÑê‡3v$ÓÎXðlvg·:"4‘¾áÑžúÆy`D£Ñýì…Ô˜Ó–Æ¿»+™’¾çfpôÿô\*ëáyHÞc`[ ÓüßÙ­dÏÞ1E;Éx=ÁkÙ±reclv0k¹–±'_÷»×ùSÎtÅà:S¨ü…nW“s‡Ó^èv¼lWâ7¬Îær«~§äwÚ.O‹ž¼ÜæÚl-.{•žRkóx¼ $úôÐÅîWŠª$Õç°)Néöz2R3Tât@ nÏ|°gxgzñl3c?k·Â2¿1j5 ñ‘ÍB½þ˜Z^™Üa`jv±·Y´ÊÕŸ‹¶fÈýX&µÕ e@)l‡”ÖíÖ-¼—Z¥ˆé#s¬yµ¦iO–Ó·Oo(§Ž¦’½·%ÏŠ’aÓG_g,SÆ7¿y>ýÍóÙš«G¦¨#ûVE£pØR§;ˆêSGôÍ4Ó[ÇÈ“ôŠZ’*Ú$*ê1oFˆ Ü…Ôú@ß3£Ka¢b_œpráÌXŒ1¨5…©Y}«£‚Q¥æô#S€ «o|TçÅÓ»p0ôŒvI C_Z´¸»øâzs+#S¦¶Ã&-Ít%ÍtÍÌ[î;@'Ÿƒ?Zð·ÐxÇxãaã¡æéhþmÂOò{I¨zëÓ·îho¥²/#Ýs“ëOD9¡Þ!WF_=µï¹‰FÏê<믄¾K¦ )puAÿ õ&Y¶ LT ɲcû,Ie¸u—S°í;éo¾“ ë›QfÈÓ7äÉEŠšÙ›fÍŽF#{¦£¡‡AJc3ÙõXìê7þ“ËλF&ȾLê3"¿óƒúƘÖá1UK›Š]À2¨g¸>ÿ½S¬û,Ñ)LŸÑð»¾n3ôVîbå8Jä18ÖÓÌÔ÷ŒX…yPÃîRÅ/o&ªø ¿—BÕ'¡øaôœGºà®Çx þ~jàùH7OCwsþƺÂ\©àè³ý ­†Øÿ!ÿôhëƒr^UPTÕК†ùæç¶> Ûô|{’(ñ¾'3TÕô‹FÁ¹Æû0‹ù²µwe,â<¿é)ëÛg¢Âo¦S6X' ¸ÄÔ̘šÔ½}©ÅÝoŸ¹Í $¨1Åðh”OÀl(ÝØ¯št Åv½? ˆ©Ù:i&’O‹Óßî²;%ÕckWokr;É×çˆòÙ•(1 ˜A{^ÅɂûVN LsÜ(Ú¬õ>)†¸–°)}cfñÛû³+¬¦†ÞÜ{Q1Ôôýúߦ·Ï@ãÛªUîö„YÂ9Êå‘Ô€³ ]v1y¹½0­©œçž0ç#‘4ß-)^‘áöÚmî¦z¶z¼Û9 ?Ìm.‡4S’83À ÛmþB¥ÍWèÛ^ð8]I¹–)Š³Í§ð Ökß*ùlÀv¯ß!5ÃĘ/ùÜNÈ²ÝæR$¯_òù€ôT-JfkòúVèTì…m¶€âôð²ÖfsyD|âÆùš¤1S˺pÔËXÈ4ÐcÂ(C»ÑÊ´F0é‘^ìØ'áoªˆ "¦S!Ó‰Hý•PýñÈê®ÐêcW;1§ß­Ô>;*Ø¿TQ‚ý6˜#Ú¢Áþ€2%Øß¡,èÛ"x`À®¾£‹Hc‰´¥˜v‚ÒúŠƒÕü^QÌ÷¡Éµ·VŸ1pAzŒì‚#öUíCž: Ÿ:Kƒ]©Y[ Ë~XÍ9É «¹$ê$bAR¿¬A1c¯×§ÔƒÂGz1`´Á›©JzçŬÎYP[ç'ÊÈà­¥õêЫòÎgíw’ÇqÊø`5 ÆÔÌ$]¦ê•Ã1V޼µ/åꥂ;OÂ4ô"7ð€È•Çåpf×aâX©6KJª$G¯$+©’\è7IJa¡S’ëPG#ÿRyŽà}Ám•Ãqáú“‹–§+™ÚkÐÐ}FíOÒ bn*¦ï^ÊT£öLãê“ë6jÿ0†BWÙâ …N³Û$ìNF. VcL1jO¥b`z‚K^!1z8in×_¯M6bZ?ê67kƒ|¡3Á³ã´RÎã'ü$ˆÛÞ5&øYŠ:*>ÏôBÌ«¯>¾Nׂ۫£çCô˜º~+Â50›éø2íCŽvþ|+E ©¾&àó™ûy¿ìƒ0^–Aa­Sg×/_˜Šóf)ŸœÉRÇè´­Úâx½r´7i+8Ž“FªVÄï¥Z!ÔäI6;ü,mW‚O†v£V·ÒŽ¡—k…U²ƒŸ%¬lÑ^¯[y™@Ðï¦ ê†³qªàú×zí­±z{—iN‰Råñ»l‰‹¶¦VHæ:+Ò*8ÊgƤn6©6ÉÔW–C4 ù#–8­£´nH3ˇ¦òŽþS@2X•Ä`¾±Ë6O _Zú²=¦Ë¯OŵÓÂ=á„ÚùÑ èr8Yýó7>ýÁ£¡ÕWzLÚ·8Ù5ô„=¦ë¨oéÆŽõŸæy#Å n5FL—Í`ÿ-Ë…–)Ú¸ë`ºÇW¶'Î Ò¦ùî™F6Éí (x<ÎíÙév†H#_; ýºj¦áÈö5v- -(´üÜóÆÔ~´L¯¶gw¯$4À‚ÌáÄy`Ï5 q*hLïÌŒ¨¯ô ‹j¿â÷CCüp¹ÞÆÊrèº<Þl®f¯(eoÖ§i¤—`f‹vt¹ÞTÊèpç#/s6Ú²om˜{ã…¦nu~¸ÓÇo{ÕáN…ß^R'b/î=´wAð.îÞ'£_;Û¿Ñ¢½c þ“µ×Š»#¯ÎÈcžíwŸLázHªEõq>EƒJ°þ´5áêò4{ým6ÅåõHp-RÁVy['ƒ™†0¨õúmþÇ2·Ëㄱa+FÔžˆš¡§{[<®§ê:Ý Æ%‘Ýk€wxUpl<šŽNR 1Á¿r.ñšâ¢ aær»-67–õÛì5ãš9̘˜A9 u:eâZP«4¡SYñÂ6±A€].7ÔÖi“*„u@BZ} x3¦NV#é uòoþÚººÀà¡1Ìé¸Ã&dm‰Ëªovµ@óûp³K¾…ç²g‹u†š‰÷ìäaóÈ5SËÜUuïm tü.@ä P¾o%úõšôP]fßcwÐÔ¢.¸ƒ¦SuMq;F~i-度ÆPC&¬,œÚ¦§hOóööýA9¶ï¸¤öå=sFùàö—Ô¾ºIø“/\iP˜",HkWœ;3ƒÝÙæ˜š®ÌÔ;5ïûUäkÝez i|¯ó6®N±°ÐýÆ`¾yœïâÁ|£ÈWÂ7Yïì„Þ?*»£ÞsËî®·è+ò uL­ ù lÞ¯Kæ›<^ÛÖ2ݘs¹P àCÇÄ«ý_¥TíÛgô5Åb’]õûaJ| Ò3? Ø<›øªA ‚F·Õ ~:º§N:4ŸOÆ"†Ð#‘‡ÃuPâr~s¤Þ31‹µKòQTâ±(ù²¯ÄóÇéåGñ.|†ëŠ“PímBu’h™á èxQ^èÚSBºRÇdŒ+"œS‹S›ÑÒfÉáÅÞi½ÓÐ~b ¸ ©#Ù¿'Ê7»<íŠcä‹äv§0—ÛcÞÛ ðÉȯz<¸Òîð‰z¡w:a¡ ëµ¼>êúº;-¸uœÿqÖpkóÀDÓnsƒ‚5…L Ul­;i©Ž¡íкN¬V·…áªÃw×éNØÆ+tXÄ%á+í–IÈï÷ÙqS>QX<Ù.f:îR*qÕzU·#É\`äø”´mqëE£ä»–‘d[b§‡x“RùC:Ùžút£íÖ_Ò3„úôø®À»ÁêÙ|³x!vY¼Õ>(ÒýÌL>LÎ%¶ªè[U…0 LJ뀈é‚Yþf†mœÅâ,fq{’X\ˆ³èƒ»«ï¦âmÝš"ýùS/h¦—V‹xlÈ‹eêÅNˆÅðÒxÊ×QUÇ@¢]SÅÜq¢H$§ŠÕiìµh3Špó¯÷«…<Ú“¿VˆO‹Ð0½¸wÜ­òu܉8‹*d1ëé1å@êrB¦£áiáC¨þxxMzhõ±ˆézÈt"\twS B?ކWݽp–šzoPäi:Y‘~+d:HÂÔ ÖßùôbxçñD‰ÈæÑ1Ü‹–Oâ¾o¸þÄzmwnµŸHfœò]X•%Ól¾M¸!'Tßz/9-­ñlxMnhõÉÐ÷“JƒéÀ6{>ã3Ÿ‡¿fûp¾¾lŒ†‡…L—`}2]×¥‡L„ë2C¦Â¦®éJ¸¯ÖBõ×ä^”0ÑèÏ$–sp4C{¬{zA¾o.˜ôÞ@wzõîØµ•X,­ÇtÊÈ÷s»y«ÔÂõ§Â«»×­ÔŸ_/ç6û]Ý»N^¼«´à£TÞ¾JŽÞ)ó‹ô †¾±Áç™2µsCŽöƒùâÉB­Á®ÉŽõrú$¾ŽE#Ññ!DÅï°×r )ÇÌGÙC½ç ‰ç¾0™ö¥ÔìÏÞ{Q»ûf•bÜ}3CýÎá†sŒÕ¼}»Ä\ÇöÔÎÂ\ÛS—Žj÷Ôe sŽ'æ -òò‡‰|nÑùñw™`Â|žÛ54ÏmNhóЙÐSŽmäXðlnkF+³hòqâKçY‹gmãY™<ëAâK7‹6–°\žg$L‚ð%Z‹K Ű8Cý—si,tfLÒy#Îﺳª‰;«B]x%Ÿ{š‰ùwrV{æò0XpzçŠx÷]ÌS¿9yÌÁ [3[’]ÙéyâA’˜ÛŸ†²ázlÊs†lv8ßËŸÂeòª¯ÍÕn@vY»1àÐx$k¡Qú²÷s…ßä„1µ½¶vžx`¨ökG9ŸœX9”/{@ã­X Í}%à£Ã!„o{ÒZ£h£E÷íº±–Æ•:O7Ö<.ñÇsïd¬ stc-ŸƒÆ‚ØS¸“ìHã5‹6j®x|`ÒÀ|Wîh¾wÄÍ÷,§.‚* Åû:ÂõWÂ&ívÛ˜“°¶ëšsWÛ}iŽn; mwe½6v.ÙîŠ&ÏI¶C Þ€>öVb(ŽÓ‡â{ ¥¶o½cƒï`Xy|&âÊÁ‘/óŠ-x7íË‹lnÀëYiÆí|­‰ˆž‰‰šÙñÙÃu6–ó¶§Œ¯»ÙÌåaäK3Ÿ¸-® îÌfuÌ ¥ââÇ\:ÈE!t¹Ëd?¨Àý‡å|L[4O¼ºá¡ë}•b¬ã{›ñaÜ_cnÐj㤆Ðå¾QÚ¼Ù8Áüó÷ƒ×‘y–`µ›e„¶(•÷Úª˜Q”s¦þ\´"0 þ¯YÆD­ù4^‡@ §Æ`äëZèâ;yIÕ ‹v=„áÆËoŸá: ùvlŸÄµŽÑ¾“ÂËŽ(¾H šs¦ŠÃ;ÓoíEêbÁþ黆¿Û38FÌ‘Æ"¦4ÃÜbÖ^~źÄ_¹ò¸T¿HÑ·­«wðÊp7ó0Ökæ/õ}^}qž «ú÷#—=yDÑGÔKU }ù,ÇSWò;M fkŸ2® ¶­+Šºô—‚€!ÓdSFL ÝÕY(òµˆéZ²%KIfI—9çn2' ÌMólo<¼Ž Z‹êkn|6}I¼BEëÜ=¯…¶«†V«Y|{'à÷ìh¯òÐ=.)f¶%­ëo +ãëñ¹6 ô<âóÒL£>˜L@‚þ™ÉJºXµÏ…ñZÉ-€Oó9¥üìbþì·gâ @•ôòn ˆºs±!£V†¶€o7óW¼ uCç ð_Hûîjx¥a‘q1P·[ŒÈèÈb.B?L5ØÓ×ÍÔGPÁáa™¼‰>z«¯!Ëôpäi®a>‚Yá©ø74õ¾½Ý?ì…´ºÚ½Ýê'¡©«p?)Žó~!›½è8ÞÍÃÝuƒHÚÀ“ÎèI¹ô8O:!’BS7 '•Ì{Ë Þ¡©Ž!¸|‡ºÝœë“ÄõúFä?Ãó«òpǭLj„ÇúÂÿ²Ø»a¹ø«Yyñgãûh ,©5€{ÊÔs”m8ßÖ¦Cšù!=­‘§eBÚâxÚ£T:R¿O-¥ÔqÚOJ©9úµ‡xÓeÐFZ÷Uõè+½³Üž8§)\”x2Œš»ðaôŽ}zߘÚÁ*fChüñd/O V2õJ|̳>ˆ‰øfeqw°úP3ôBômoj‚ULýýµ2*6|Éc½©7øž4DúÚñ!îeÅÇ]õn>Z²zŒ»idh/ò‘îÙ˜‰±üv€ðþ.øÓðy€ìÆöeìÀ‹€çþe€ãþ@?À¿ø(ÀoÜ ðµ-8ÂXþi€‹žøÀïmá=#ÿ]€n€ÿpÀŸ,øK€c~Ðð@¨:˜±éXpÀU§|à,€3|à¾:Ãòñõ¹a ÷ÀG^p3À5ˆþV ÿ"àOÜ°ÙÆßÌ÷\v¸íVù¸ß»Ê=ø4À_8 íPE»Ü…vèA»Øø[4ù¯ƒv±ñŸb䟘ŽvØŒv¸íðA´ À´ À h€…h€ÅhXÚÕ ]îG»œv¸íЇv؈v8à€6´ÀÚà2€ [Ч¢ÎG;܈vhA;ÌÇþp)êÐú,BýV¡þG¢þsQ€sP€ûP€•(tÅ5L¼­ vÄ6cà[‘–_0Þôh¶þá2®šq[¢=°o`ÿeÆåå]û!ú‡oMøÏ_ .VÿFQ´9ÛÉxŸC}x^€ ï…?‚îǶ“LпÙpøa¶¶ ¶ ¿À¦ ¬”ñ~ÁŒ$#èŒvÀ¾ÈV0n ¦PY¼Æ3>>øo Ð“OØWyZ&Ù ‹ñöÆ>ƒýǃ¨ŸM¤:aLb;°ÉŒ·›BºB2ܸ…>Ê`Å‹}Šëc…Õ’I§âYNºÈd“…dÓ%dë’e-•i Þ‹©,#¿@eÛÉ«I×§ˆ6Lmøñ~’t°SOPÛ­d¢Í7‘ÌÈ–à_x›[ˆÆ5ï#S[5‘ «H¦fªËJiëI¦z&úžt û ôwÞ–[‰–Q].’ÁIeU²›ÊØHF:8H·Ý¤#YÙÞK6a$['ñØE:0² #^{©îçH'¼Zb›µ‘ŒÏ’ž&vŒ{ˆüïãûH†ÈxaŸ¿Êû"îˆ(ûîIg¼°mÁ¯ñ1¨FiŒêfÄ ¯wDCdF²1Ò-HÿéÌÈ6Œdb$;#Ùñb¤^a–¸pìòÎÏÄx4‚³É(po)€;lŸ@ø¢Ï%úóÓ>ƒð+ ®g¼Ð- ücä –¿³ÄßDx5ð§üÂR~c‰‰vNî(u€Tߪoá§h@Fˆß)âÿ%ÂþUÂOþ*á×Hÿ¿!ü áOü7“ýNëôTþ<åc‚×û”€Êÿ„ò_ž*ð>ÒçeÒç&á ‘"Ê'{Œ!|3á“ ßKõÏ Ü@xaЍï8ɳœò? ü/žG¸%Eøtý%õMT>}±À[×òî£ò¨üNÊ¿>OàÏSþ8’÷EÂOþe¢œø½’"ôϤœèŸ!ú×wþm¯èö'üá? ü Ñ¿OøåÿŒêû¶CàWIž<Ò÷7”Šò?Õë£öŒ½þ›R¼ÇüLâ?Šð³Tÿ$Â/Q~á/S~ᧈeªàÙ»Ú/U´þKŒ:ʯ¤€a-•ßKüž$Ü@¸Lø*ÂT~1õ=„0_àû ?AísÊ»I¾¿ ügÈ?¼Jøª<ÿ.Ñ¿FøQ»S…}/P{¿£ËGöù!áãÿG½~½ý¨|”ÆË'”ŸKúÅH3é3<Å/ôWÙi"ß@_˜˜&Ê+T~:åï]"ðù„gÏxE¿t£šÊ»©üc„瓼¨¼& ¼1M´çyš€¶ýf¢ÿ"á+NçGüñgse× üË:?z³â¿ëú½ÿá‹©ü7Hž“•?IøòO§ˆþÑ¿Eùe4¾ß¦ü $ßEÂ7ýGDÿÌ"Lù7Hž_~’ð›$¿þfHŠÆ 姨½h<<@ùG©þ™„}áy$ÏJƒàßKüWQþËD¿žøWV |3á;¨½íDêk%ü*ï#úî·Sþ”ÿ,áÇ©ü>ƒè¿gÉ¿D(åÿṄ¿DüoÐ|wX—Ÿò¿nóý1'ôú¨þשüQÒï 埥üwÈ>—É>ß§|…ø¿GåYÀ?Ôõ§üŸ~ŠìýKÝžäOû)åãæ÷—„O1Šú/QýVëŠ'Ö,[ýh-³:ÜvÅͬN¿ŸY½>§n.…Y“ž…@ºâò8VP¸Š H·áñ†Ó5Ù½¾`nmQñ«Õ¹Ã¥Xj[[‡Õá´»ymnoÀg;µÖ€â8}œ ÔÄ!îì[Õ€Û‰éÛý.ÅÙŽ…AJ¯_øyQ«Õáµ¶¸½M6·Õ¡xýªd±9ð'H²bÃÚõÖUZ6X­ˆÖ&£~§ÍíöÚ¹ö6Ÿ€2 ¸Z|~¯½ÍØ:¸»¨è*зm àëÜ(­_ °„UœÂ¢Ú½‚Úƒ¯ ò*|VñøÂêó»h¤8;. ¾G!jÑ_Á¢ÄÇg¥';X†Ú—ÿ,ˆYÛ~W3èÖ¬Û ä Šf¯ß.ÌUØe§}+ÚÀíòl£&Mî~~g³Á%ÚEv¸°oØ×Ê9wðÆJ ákBVýÎj Ô »A×ÒØn Øm`ýG#I:yý\—€GWb»ÍïÙÁ  x«ºŸ¹Ðt;°‰<í.¿XA¢èÄO;ý^fU¯Y·6šÚÄÊÇš„×–AB%Æn³OUœN N0 Ç+’íX‘Wáj)6;4-´•l….ˆ#—qy·MUdÕãÚAòÂXn¶©nÈà5ÇT#uzQ±pBþ«[dˆ?ã…<»ìÝŽýÆ€Ìf¿Ó‰áŽÁátÀÀnvèÎÈNK¶m…èÔšðÍvw\蘚©°•*j2PÚÑDL<’ Òý>»÷劮ŒU¼0ÆÍO àt˜ÿçÅþL?,ÒGÕ‹½ šÆ™ª¸ÜñÅôýëþuÿºÝ¿î_÷¯û×ýëþõÿé…ñpöÆfoùÝ´Ú·­þI5Œ½²œ‰‡lLðæ­Xlêð!Ô`ê ¿)ú|õ8õ8õ8õ8õ˜úœú,@ý¢þ‹P€Å¨?ÀÔ`)ê° õXŽú\€ú¬@ýV¢þ¢þ«P€‹P€‹Q€Õ¨?À%¨?À¥¨?ÀÔà2ÔàrÔÿ·±X-êpêЄú¬Cý®Dý>‚ú|õøÔàc¨?ÀU¨?ÀÕ¨?À5¨?@ñ„6o-¬—[VTIøeÆ{~»T\°P*)**-,*),©”Š+«ŠÊ«Ê‹%‡Óo³9É´Ã'åá»3š«™ýЩÖ×~W‹¬H³ís¤â…••ùðwa)ÿ[–1ˆ²ÞÙëЀämæ¿1©÷¸ÚaÍîR:0¥Öæv5{ý—­@’–¹ÝçüNþÓ“»ý@®$ùeÜ« J  %…E…¥ÅRIIUQeUY±Ô†?—óë* Ö7ôÒåqUH%¥U%•UÅ•·•N”å?g£ÂeTó‚Â’ пª¨¤ª¬hˆñ’Ëò *[¼à÷)œ(«à‡Ó’ë-*±KJ¥âò*Ífó{=T°JbU0¢_«Ïwp=®Gõ'•â9D ù9==\)ö«1=-)}_¥Ø·š¾§R<¯ÊÿéJñ\Ó‡%ùÉ@¥x44ÝU™x.ˆ{¸ú3µÍIé™Iéë’Ò3’Ò—%¥g%¥—TŠç8XïIræW&žW%Ë·+Ѹ+BÍQþ{uƒ¤²úaÁÑqìTU/”Š*ªÊ˪ÊK%k«Û â¯4'ž[ß¿î_÷¯û×ýëþuÿºÝ¿>Ÿ+~þC@m*)ý|êøÎÿ()ÑÏ(]pÿüÿ€kpûЇòÿÎ×=Ïÿ(.rþÇlÿ’ îŸÿñqñó?ðݺlñ®%^¿×ùìÅ;žÿqcOü7󡩈ðó?ðæþƒÎÿÀ´å‡ñ/ÿñi$~þ¦"9žÿ±û¶ó?v9ÿEÁó?vŒàçxÿxÎÿرï.ç8öÅ¿Gœ|þÇ^z­ý:ÿyý§=ÿc˾?¦ó?¸)çù›yãÝ?ÿã¿Öù¼oèç`øw<ÿGÉÐó?°ŠÃù2?Œžóˆ{þÙ·Îÿ@ß{Dáiènž§±®0Wêó8ÿ«ÑÏÿÀ{:ÿÃu‡ó?Dö½ÏÿH¦¹×ùqºßqþG2Ý¿æü^þ>ÿCþÃÎÿX +˜Ë,<ÛíV)Ul­ÇÝ!)¶­Î€T„Ÿò.–lþµ ŸZ0?üÑ_ñ3|ÉN|!XñJÛT§¿ƒ?ÜÀ× îýu¯™É„±£øt¦êáïźš]|Cæ½ÁùɹùR“ª@}ŠÍ ò:\»Íï¸íÉàò]ö{=®§Å׿xظûS•¡åãµHMN» OA5; hvúQõ€Íî÷\»Â¿ë|OÎwâïQñmwcîsÚAw»Ô fBñ‘ýògÄÉð øÀôÝåbŸä²R‹«Ý顃VÐÖÈgàG¤‰åàòŠ_u’Qó¡r~V´‘\è øu—`×ÐÍ“ˆkøGY´—køa¬ô˜® pMc?„{×ÃàtŽM†n™>Š˜úC¦+Wçƒ?v3Ø??k~²=–·/zl cݿǿ-ðÏý{Òþ[ÿíHºßK0 ÿNÒýËS¸òø8¦os¿™~ù9:áeu±W¿_Öc¼ ß—ƒ•øM>Ò›4~/ÁýaéOñÓ'7úÆÒý‘žãçš<Ëý{'òOÞ¤;õÀ1«³ ‚¬ÃGÁûÌ9){>X52%£ÇˆEñ ~–²«OÍÀoBËïOäqâ7ôj¹bíí(¤ÁæÒ¿ùˆß·6È7‘Þ ½8ˆ^îÞšKd¿¨—˜¬¾ÀëÆ78…œŽG¶ŒÐ–9ø7‹úå¢Çù‰ s$~Šš¥ù1Þ¡\Peii·Qei¿²'>”þ3:S| /„O L» â4‡×™2‰óbå˜ ³Q߃¼¼¬H¨•H$Ú»þ‘—îÛK2ío©d.´Š|2^Läk+Å·t‚;¯±¬½¿#ÎÙÇ?*1?C#?’ƒç\ÀRGû¤‰ÎMΖãÙïQ¶”œ},žÝÕDŸÉÆâ ?=bæaØ Åi‹¢ALÒ³UÊžœýB<»²ó“³Ïdz+(»,9;}²žÓ¤7b¬ +GKiõâ#J‚&—Óàꂜ+ÿ½¯ªºöÞI&Éf€Ñ‚ ` …„€!_Ž)â0$f`23Μˆ&œDÆÐ±~Öúb-VÛ¢­’¢K¤ÚŠ”[©´Ö{kÛƒñöåŠ1F段÷:gÎ$Øö}Ÿçþç<Ï™}öÙ_k¯½öÚßûgKðx@»¡kt¨E¡›Óå>¼LfjÉCaìw0VQç–n´&jÊrŸ¯ÔJnǺÙáå—ÈãZŒ{|r­ËYÁÝ̹…9ëXYüJQÔÃH46ýîÿ Ç;ú:Žir–Hí<ƒ˜&ÊÉB’Úäm© º-xëSµëZè + yÐ:­3¸u ^ÒQ³¹¬øq,"=qœÊÔãë´ëS3Øæz*L‘µ–0ÞpRež'‚¿©Ç3‡¡72]{ÐòÔ:]I|G¸@åk]'Ô jj]w¬SoÜÉFW äúÕ8ÕµlZXâî©©qB£º{S*ŒxCzVdFû9ÉÐ~YN+8Ú]FªL¡^ƒùÉ£ü>ûÐV“a›ïöÁþ8B;ñ[TÀǶ"Â2âDëàH¥5²ÆÚ=.ÊõQ¤ÒË”ÁŸò´_¬”©Y3&ç¸v ˜&züèCïlDàšÅV÷¯Zn´C«,××»·° GÉÐî:†‡g—‚`ê;g*¨‹]?—ÒÒwþSïÛÛ˜$®Å;y¸½ „º4yµO|œ0d¡(¸>Áø(‚È KQ¸RñC¯òø•mݪ”$ó\Þ¥à¡Hx¹SÁÓ}ðâRÜ©üÅ®ÌJã/ÕÊètþ²L™lä/åÊÿ/ó”·3øË eÓ þ’£¿›B=é›ëc×·¯›%|þðn¡VxvƒP{ОýðÄRQ¡w3–ŠÊº;/–ŠÚ¹{r,Õp÷u±TÔ·Ý£b©¨X»‡ÆR±ùèN‹¥¢¢ß‹ §„Ç¢¹÷'ÛKÅ•wU’ϟǃÎèlŠØ*oȳ×YWÄ<ð¸ƒLV¿ä©÷hÖTïÅ‚-enÇ¡eh>²Ä`nû9ïº}D·'ð†áUøÄ‘ÿ„¼ü7»ôígŸ•3»R1N$vâ¬âù–÷‘¿¡·Áš·!Ân¶é~/ îŸ^ÕK­û À¦Û!jŠ‹Ìí;u$ì{í ØXdS®dbPžxh ¬kÐ_¨Q³Ò^G3fèÃ,…·u%9Q—©RCW©1ézˆ©Ôd¸^Í7Ò:Ö‰Þ:9eÚ}NzY5j<{‚ Ø%oÌoP~9JËÁ= NFåw£ðöø£„R5/GùŽ_ Ïq8¤åú0jtò‘å$h¦¥Ø5*ñ‹Qy¾tbPǘ8Ðî/‡ò×\B)L0·LJÌËç#…S®¹½·_6'ÄCýI+(¿ñˆq2qªZÄæöc"\ÌoRÞ©Ï~æÀì‹zlnÛ–I ŒTy°91ç¿ÙŸ¯Ž$Zb%P¯³4“²w$*0Nf)”¾‘;ð븸!q¹0rI‹ —àbò€ò·ÔTé¹q*S+t¼B#2–§SeH¼û¸£9ãJø÷‘‘˜ëhôóáô~¸.†D\—Ö‹äÕ<±; ʇƉtâômxjߨl$”Ûؾd- o1ĸ$‡ŽöíC¯ñ¼¨õHŸ§5º½Âj¼ïŸ9DƒÜ”Ç–û„S£ûýDK‘ga²u˜lŠÆeçHuP=2Þ‹&öoŒcâÇTóî÷ja–r‹E« ¯‰‚p•ô‹;W[Z€ÎKû9ќǡómýœÿž©Rf–ÅûX‡óù›„>¾­×©»áf­ÝûA?MøÖPÍé¾~-ÿ¡j &ë?ÿr¨¾E5°EÔ°~$êÕ3CõÍœ¾é?4TUUؾ®óyfáÀTi¦9ð~ý|¢u¹}X¿þÀù!Zàr¿Ì§PSÌíçþYàp¼?Ð5äßììê×X?Dåf0‘-¿Ò¿z~ÈWõîòÏú‰=áìjºhüøñ¬qãº3á‰#k§‹ûFDÎ¥ÑðÉ1=‘¿'fŒÊn“ÆÚãqþ™ðïY•e‡éßâŸÔçE™lRygOœí¦DÞYŸ)Î!c¸Ô¤ÖÞ­Õ÷ŠÚ§|qU>1¾~Áð®…€£-ö²rg-(ÆFvK­Äªœ~¶PÞÀ–Èx¼l™£‰-ôÀ °Jçz¶ÄáeU‰UÜl¥Kf5 èW‚ö_J· üãåÎ:9€w‹ wƯÕoÞ:ñŽ*ïØälâ—Ž/¦‰n޳Èð¾\æö2c½ÓÃh´Ä–É’ÛïiÂK8$'®«À[@®…¶ çÈqxéi#,_7?ÌÝ”ùO“„ù϶4?(fIÖhxÚ|Ô<&MÂù…jP­{—«Ê™î E ÏÐqx’ÿpÕ;mך .Ã9U^Á§QÛjèÇø˜ õe1~=ú24(!í3£Ä‚FešöeÿbQ¾Á¯À5?tÔüÊ…8„Ý;Ý:wš<4Z²ËÒÚ;Mêií½ƒëÜL §œ¤ÞÖ¹LÍ(¿è‘¸ÖÇK?ؤI“2X^^^BßMë©nq‹ÌæBZÐõ Ë=¡Ï¿4·†ìÞÚ딵öV˜Û‡YÕƒÕnšsÛ%­“—.š¶ÍïЪÇ¢z4k_H×`Ø.Ë »ÊŒ?¡°–qÔ¹7à“ò2÷h‰Teö`,Œ…zÒÓBÇÒÂU™ZÓ $ ¥æöüýÉ|>_ vχ’qôÏ;$æÝïhz'±ÍÙˆàæ»’‹+$SñfÉXì4·ïéׯe}©±¯kT Å4°PdWkÑ4µßHÌ_H¼cJdÕñ4dñ'mâl1ª_÷!Å@áüÞ$ÂÐD[x•(™pEï»_›|õ<&' Q©†¶önFylþwNÔõDlÆþOƒäÄÜ~2©Ÿè¸šÛ°+áxOãÅÁêÈàA”žö˜ìFO;Ûtýð¶k€·Rôö`[b×µVõ&º5Ïôs.×bi¨9Œµö˜ä;Œ­H·+ª—ÓÎáH ½·êÁ‰Œ_’g¼á—Në<^JÒ˜lczUgª… yÁ'\ *…§­÷ƒÓøÜJ÷ਰðáBBßïüË)4g: SiPŽd$’õSU½§PKå+O¨î\_ íÿ6ÿdÔ<Œ6Äžš0Õž‡ó³Eúˆs099Cß|Ë«¥õT‚´ŽïGÝHCbÅN%âÔdIHSùÄU¨¥/M®ç+[=Ø•o3¨-ô­ p4VlLUôªjwŽžšø(à ÈM•ACnS~dPG«“ çB >î¦þ…áȃù ƒ¹õÔ7C<)A„ ›‚ì6ï)?Wɘ•À”þ¥Æ”Aƒ€ä½ âL’XKŒ÷?êQ£J›ôCÞñöìüc¼‚Ei\Wêƒô&ò“&óŠ0+Br¼û?ϨǟÔüe>¼)ÑWðkVýJxí:ú$OBŸ¶6ÝÈ|þ%=^Iy“µïA¾&&ÏWfˆ€®6Ýp#˦¼0 `”4·êßò TIï'ÿIB¬¡ùœ+ÒâZf÷0MïQ­¢º¤Ù}hvTÑø:4Ÿ3ÒÜvw_,Æk³M¹Sá³õUZ!–ó¤yÔ,¿çÎWöñy×ø<‹¯QUXÂ' Îá²N4ÂÊ€;Ÿóɨ޲$›r’ ¿^p:|¡ðý–¡Ñ°áþ°-«4 ßo¾Øe¸Ë'æ³IÑH] >†.²2ue|­ ²‘m\'‹ÔU™û*x¥¨ª®Aøñm ÿ–ízWóU/|‰±íÜBKe’pÙ˜¦ÁÛ ‡×ê“%<öÛàlðšøŽ §’–3öT¨°k0ÒÒyxœžw-puèÓr”{mŸa:½Ñ“ˆ!t+ø|¡ºpU­F)‡8Ã= stЅܵÌÔ~ZÅ«"­™¢s3$Si ¼ËÿP·@ÅûyËŒÀ¢]kMá ;+bá7ÛOJÖ]ÃÚNK×Î}_N ºsX·E`o ‚ÿص*¶Ó~Së»Õ@‚¸Ü3 —Ñ~tb[¦š—'Çì| ̆ìã Z͇µñ3Å1ê*=•¤m*Î ÆsEÄS£öp NC¿eX"nqÇ­±öÓ[ ;o%À‡ã²°ÖXp¹ýøOÄeº3º’²µ¹µyf,G–;šg^‰ ¡>xÿùפ„1÷®Ñ ¼©­½Ùò{øWĆhHðì+[ɶà÷œé\â”±ôÝßÓ³EWœæYuûÜþ%½æÝ8Ài6·ã•ÎÿÿtW¶n‹MK¨ßüe?ê 7¬›k¾Œç`QlBcUÍCƒ¯n“¾úz²Ç&'Ëó40‡ßÚµµÌãÜ©Þ`Ž-‚~VpxïbþZæw³ú9sp $1O›?æ2Yë÷«È¼_¹‹«•=)(Æ9Ö5“‚ks­x3Ÿu¾u›¨#,—#ãæÉ¦–:Ô‹e¦ð±Po’´W¥´ÐÝ£T)Ëì0”Äfµ—CÇ56롳÷2Ví:µ*ñÁ…\!àFBBYä2¡,.¡û#ÜÝRsÒh~Je›œdVmÌÂi‘M@Ρ¦+8Wx±e2ÐcxŠÃñ­pÝu#×z6å×%ŒÚv#¶¬ðbótˤÑÅ^·ä†ÑQƒ;X듽’z?WnXï   Ç·*¤5 Fõ¼(ˆ @fÉ™²¾ˆÃôèB®n5>Öo,jL‹ž–òŸ"^ÝBqþ*Û}SA¬Ãk¿Ü\„q愽þñ¸Ž1Éï~£Ê×h)ÅjßúÍ–´Ž` ´NÇÝI…WZNòšsµúÙl ]I6·…¼nËh¿lnû ã2rÅ`nà ÅÐ6CÒ¶!|Èyþe¡KBÛ2Si~ôŠAššß†hÒäWÑ{qb3#‹pÃÐóTŠ4Kö_8þ7Þ HâÓª#4‹5‹ôÜ>Ê¢P¥Ÿ‰ó7<$ ãÔá+81Ö<¼3M—ãcnP^4MÆûÍIL?·…CA“MÁƒ‡‡ò}ÿã)UJÛ?P“öàL¶¡JiÛ¾·qÿù2ŒZÃ\½F®ùùþ ä‚´*q§Ø˜ódc ´u&Ãֿǎ̸Üf3ìŰ]%>¦âÂ^v‰¶´b”c^„ò{ýÃäpÅ'êð{Ð~†ŽöBÏms!ŠÔªK+Ä,k‘ÊÐñð!‚[Y÷ Â.tv Û.Þ¹0¼ìB¨ËP‰ÃƼ‘6ÿ^SV}ÑGæNБe}aÄLÌ’E[DõíXWEfZvÐÅYóæãŽ˜–B,cÌãx’vAZ{¡JÚ ED[_%†¶ð=œÍ¿½lL¥Nö{ܵœÆMÒLìôfú ×’Ëä; q¹QÛŠÍ´=ç¬ßîu&T¤¦@­¸ÃCÿ@{°Ýn¯Ú©§ ߨŸ=©.‡åâ¸g¹O·^×6,sš(5¸ U(¸ËbsÊ¢Ð'YÕÊß¡•±q•ó¶¾Ûª;ŸÂ`Ú6Ƈ!¶&ïâ,ž‹ñå“›\ñ*JDáÅmŘFæw#åß5äœz½'ÙIÆcù\½Z¤n>LŸË÷à„Y¡%Üú¥iþ:GKLFE×[šÃ¯çÄw7 BO•%Öå·¬´ÚV,^¾²¢<ƒe$ºÕû<ßæŒÄõýä ¾ôH!çX †¡ âý~+‚˨»^æ~¢¸õªcyRxYŒ1| ¶¦ÄR0hLìö W¸ŽÜ¤’:Aä¡»,ªº½¯¹¥[d¸Î³‚")…~“Œ'L6&? f ÀGàƒˆÜƒÊm·7ÆïU±çB€èç[žUD=g¸ìd½ŠȆØÄß°™%ºŠAuçÿ?ßô­öÉÖ:7GtçÛfµc t^B“ÁÃ(P—Í߯c›QT9 aP,xeC¹ýECÎo_ÿ"D±J¹T¥­ ¦ó½Åz |}±&]³±“ÌeíEÄHù‹†Ž­I…Ç‚#ïc(9´Å”.'•…Ž À¹÷ªNã}dã‘Ý‘UUcíÕr«âãxkø Ò¼{0¼àhÁI5Û†EF¢êŠ6¼´°>l½LžcrfL¶Üªü$•¦¥ºÇª5„Ëpñµð!õG¯¾¿ÑÈ{ wÔæÛTpùUœëžÄû–(Ä ýl5 öMqøŸ™¬b~‘,™…n]eé2D“h‰¿çpÅ—i«§˜ÝªeyŠX¥ø"Iγx9•Jš-àlt;7÷ßN=àGû«ÕÕ~ŒK‹ZÝ Ø–­{EäÌP‹‰á8Ÿë£ú¨n¦–p3õMKõÅf]Š“ÈÍb•2ÔcØf õ¤4ß y«ìÛ‚V1O¼1)6SpüùÁZm÷ÀŸ &®uü3y·HÍþJ”k$º_þ„¼×«• |ðº&{ñ1žjÊ`ñÍÞ|"ÂU}Æø7<­SC¹ºzI·I]£ ¥ÊÎËÀÚà â6ç ¯Éº:*°”UžãÒ^Ugö2=ÓÇ-C¦§÷¹—8Ÿ¼m8´óÍCÄÔí ÔÇÂYŽsƒU/ÒˆmƒÀ“´´»÷_¦H»¢‡“Å$fUç·t‰Tt.[÷‚¨Î½Mç>©3ˆî×éÝßL ò‰eZ…~®¼^«Ò³Dœ×^œùࡵÈU^M½ 9ý ./X0À'”J@ÇlÑÀ$¶½-ÐE±D ‡"Kr^¿ ­Ë¼Š1Ò´PK“§paì̾EŸ Dš£L|”ÏÙˆžYó¤NCðŽhä:èÁ¤#êµ)Iš ÿÉò|ÛœÐ|,™$)‹}•ˆâe1kÝ} _…£,K¡©çP½?açI‡ }‰ú¨F,Hãa¡ösæ6,à])ÅS%û·»RÊŠóäÏoî7i·4ãJá»–ÖÂ<É] ¯©ð*_Æ?ß²ð;Ýw@Ü|(?¾;caÍáíMÅy’b7Ì=&ý§®åŸðÙP<ÆÐâXÎNd€· @D÷1èøGë<& ^†ÎiøY~¬à(N‹ÞP0oúĉ§óÆŠwðBËæQ*q¦E€¹ò©‰›2ócœ°„“C]Éáy‘dè hcìˆA‹ªFÆ 9oSÕHÎt®ÊD·Ë"Z~eÛêh”+çPñ&•íÅ#.ÃH–ÑÞ)å¥+Ztïçé:K”©äH•12<œÙ¹¨RD¼õ¹¯ÐhOæñ–(7tòA,r AÐuç?ô‡PtÙ¨½rØt©Á?Ï°ÝÆl3û7Ãsà—=v»l Ï>ˇÅ=®Þ[qÀ:?ׂv¨ÍâÚW»Œ+ Ôë¢Ú¸Ý5 >¸>ÄlCž˜ ç–ó´æ/&Ý€‘.X#ì£é›Z¤ÙèlâJáÉo.‹Î-¶(?Ê€gá²ÓÔAF>?…$ —¡‚޽ ß]”©ðõ?L4|ªðló‹|³tÏpÓš ³e#¶|-ølÒ9-ÞH%ÍVÞÔñÿxšî Ÿ¿lãÚ_…èYٚ˧$: 8`)•F[¤ÒsÖFÎön¾M¾_ÌK1æ•ÓTBûŸ¦øãÀ¯ÁÎ"«6VÑ÷.­jeüÊöm |ù7û*(º ÂKŽ ðù]úB+"¨`=A«´z"Ç’âl¦àAÕÆ$Qp³9ã,…Ç¥o¡=÷1.Ò.×JpÎÎ:IPr Z¾ýœü<_±ãœ~è±q‹Pvp›µ=F3xV¥íq‘ —m‘–’~ƒv!êUö# ‘ :Á…õLT—w§ÆI…NøÕõ+ T}ŽùÀË"No!v(Ÿf€4xå©VÎOâ|Ž*Óï…=ÔÒÇä,ÑwFC—rõT~Γ*ëà8ô9-ѸnŽGÛÃÔ[o«m‚mßgj-Æ5$èàU%põÇSUç·â1ž<¾0à'ÿ@î ›Vê“@©Rãön(‘Mÿªƒ; ïÚ}r VŒ‡tñèÖpp^¸ýÜÖæš*<åe´)¤Ë‹«ð©^Yü ?ièÌ®Ñ÷.Tcë¡$êI%5/Ä×äáV#†wÜe ¸lMï¶bg~Öä\ijuIlÊÐ0 fˆêÖh2Ê|^È„ìÔ²±Ø+A”ýR‘µ__·VïWÛïÚi¼MOîå$÷¯Lë•Â^ù0ÝÑÞåÙ¸ ?4¿X¬F‹¼?Ô§NŠ|2ç…šÓ¯ƒE°»cÑv€uã™À$i ^ž¢Wl®ï܆*ÿGSxï°Ãð ½Bë±U)üUܯQfì°ÅÂUÆð©ö£Íƒ")'XF,¥{0JûÆ eYv\WCgzy ®|^OÓx<ß)#ÆŸËÆE81— ŬMgÿ.4>39½àôùgà'šÅ>u5y&PXƒÃ¯j± üÅdÝêò(N¾)6#Ù¸­J‘§¨MÇá]íSñj)s[ZñréºîÜb(£â&É„mÖ!>o8;zˆï£™=”$F‡øèq,?g=nµº¾}/OÔr¾žÓŸ ¦NçƒWåÂdl†:¾ïŠ©3Á$.·¯ØcÃÅíS°kÝB}´I—+žƒ±$tgÿÖ–ý¬%Clǵ\>¿yòx÷¾dm¬-ÙA¸÷ø2÷þ&ü¨âÞ/åb4ìšÑ«âÞ£w ÷¾ù[x;Ϥ¸÷c48ûþøŽDÜûVÂwoxä[[ñº–{o+ÇvÎÅó°w"î}«À—¸•c ç>Ù*pÙŸmøê?óÄ7oøñ‡[.ú 0ŸD|w0Œøî­ü\.×G\úZ9–w.N,»ÁþW°?xï`îA¼÷VŽáœ{¥•ðÞ· œù¡Û~ü(0‹t„¿ì÷Ì—÷};ÇÜÎýæv¿ÌˆûæÃˆû¾]àÁWoçXà¹wlçX͹õÛ9¶s®Ìû‘?xßòÌo#¶sLëÜÝÛÎýÃ`>‚|óÈ—í38÷ÇÛ^=ÞËТωØ×(3€x刑0Žˆ­ð›˘ ¬p¼l±ºñmÄüÆ]ùxjú»ð¼Êò-ä7Œà¾ò0‘è@yÄÞÙä0Ëë\Ð`¨>{=ñ§ÝP*)Ï‹)7Qš?!žl ZˆÆ[‰‡8c‹eü}¢ÕNeƒ×Û•=XÖ?£4lDS ¥Á(M„êÄ2^EyC^¢¬ÕRž7Ëel'Ñpñ±ÐQf=T6») óœQX¼hq5ÙQfKëÀŠëAÊžnXÇf;Òæ"žm&š;ee e e ee?ñj…zÍeQ\ŒòrÑ*Oñ‡2ßDeÂX"þûV¢ý¢¹™òÜFqÀ–Ë̽D#ž7ºû]âAˆÂ¼D´?Oqâ¦àm”N+<“ßNŠû ò¥¸÷¯¾Me€ƒ3”µgX"Î;ÎÞ£¬áéCZce4¤ãaŸ¢8ñ¸Îýô󀨿Ë⊃/Ÿ£oø{”ÌÇÈü™˜‡ˆö'éa”&”ÜCvÌËÓôào™˜—2!Óx7Ú³ôýdî'ó92H&òôEJ›Q^R?e¢.1¢áùÅßKdþ”ÌŸ‘ù2™¯yÌN2Næ!2_%ó5ÿ¡æË„¾DÜÛ1„?Œì.ÂÁEýÂqÉÿxr÷¯vÔkˆ |”Ž+åéÒA÷Mò¿Ÿp€}åa_Bñ/%½‚âË¢A‰üßF¸ÌÈÞCö .=+òŒîžRñm'Ň{<ñ÷¥g¢ôž"÷Û(½)üÛ£â|DwœÓÃ_…ÿñãrŸKñ †'œîn5¿…}Nvã­Âž’D¸÷Â>”ì·SyŒ&ûΛ…}¼ê¾Dا‘ûUø+!»g¼°ßœ$è³Qþ¾Eîg »“Üo"w)‰p´)ÍI„ÓMîòßFüx˜ü¯$~î!»ìûÉ6ñçg”¾e²°ÿ‚ìÏý§ÈÎ;üþ@áK(¼Š{o"A»Hô½MôaÚgšèÏ û DψdÂÕ&ÿȽüOMé½Fò<ì—(ü¢d‘~ᘯ"û} …}=Ù£ó‰ŸdÿêS»êNò»›ì³(ÿOPzêñ¿}¹”þrÇ#’øSqé—’ý-²Ÿ¢ðg(ük”¿?}Å÷J_½k¶›Â«WS}Fü:CüJ#œôGoöádϲ ûت/~Ùq\ˆ¿ò_B8(³ÉžMƒd?5CØK gý,¥¿„Ü¥¥Â^¥âØSþÖ’{9Åç¡ô=2Å÷!Å×Bî%Ä¿d¿ì©ñQ}zœìQy?Cé·Qú?¢ð’¼¾BögÈ~„Â[I~OÝO8ç§Ôø¨|Ϊ¸óÿŸ(¾l¢ïo~.u`.ûÛjù‘ÝFéÇ=‹Â›÷¼g°_CîåÄŸ©dÿÅ7ƒìÍß<²ï${9Åg0HlÙ‹‰_5do»QØk Lû᫇p×/Q~%ÂyWXßMé©“VaН•ê×£ä_!~šìŸý9ŠÿŠÿ€.}#Äû Åo¤ü¼Fñï$}ÞEö•5Â~šüŸ!~¾Oñ×Qùý™ÜÏÿ>¦ð%UÂþ)ч·,ã/-U¸çSýš*Âû)þk ‡þvŠßJþ[©¾L!÷ûÈ=pê{¨ÃU˜*ÆnÊÂ^Bîꦷ%dW;fUŸDñ­¡ô²³…}=Ù3s„ÝMö\ùÕðÄï-äÎðÛFöûÈÞFöb*ÏÝþA ÿ0¹¿MúsÑ«® üÜ/Ñ@âe ßLô¿Fî?#þ#w¹¿Iü>KíÛPüFŠÿþÒ/ ÙÏPýý?_9Å÷¹J¿:M#ú+…Õ”&â7Qü#Ò„ÿLò?5MÐSGåÿM ­ö ²J„ýV ÿñ«†Ü-$Nrÿ„ÜÈÞLö Ù?"{…¿éZ*/²öj~È~Ùñú>ü=Bñ=Nùy’Ü?"ܬg(ÿÊÿOÈýõ¿^!÷,rÿÅ÷Ñ÷K²?Cñÿšìo“û™4Ñß½ôÅ9r/VÛ?ŠÿúïûÇä~=¹Jô yü’ìû'{z:éOj¿ÍéD/Å—•.âCñ#ÿ+' ûõdï#ý˜Gv×taŸEñ¡øæ‘ÝJv»½|õò…Ë—!Î{-·óýµ.G€ãÈ3³w7Ø}ŠÝðîuãp׺ÜÎÚZ_ƒÀ»G˜y~)¬ïZgö2‡Çã«eö ¼>(¹%Yâ˜ðdG Ž‡P·Ûýn»îÔµ½–Âq´z{ÜÐÐd¯sÖz8™Zž/Iâ™m9È£Úx4Þ:õ\¸Ýí…HÝÞ A'¸Õ!´ŸDÛîìx¹{#âÊÛq·ßî„‘‹ ?ì.Gí&¾÷O$CÎv{ϾÁã[ïðØë$C‹x¹ò–ö¥‹«VÚí­Ç‰7Ëq—2½ ÐTÛàgv¯V²EJxTþÖ‹ìx|Ü^á×ßÄì6wÀ'KüÔ¹Ý_[+X®§§VÐ9•|¯äâ•.¸y½\éP_ˆj½ØBIùœÓ>à» é* ø›°¼„ýè€E³…äDȦÏ!Ùqò6B…œÔ!XŸó…Q ਋-¨PÑ:üΙÃt¹æJ ré÷Ý|O1ˆŠÊ/^2{‡yuÔaÆ$MÕº$£jÒ^eL$‹3WÓõ~Çõ4•,øÁ„ì˜õÚMö`ƒ#èMèE­($Ôë³C2‚Ýñ%{QDëe·GT%`”;ˆHõ¾@2±õ™—ÙEÖ’ªMƒÈ‰/°©Ñrܤ’Íå–txvÂóFˆ±ã`çUxÂóSx~Ïóðüž}ð<Ï÷ày„@Vÿæ9xÞƒç·ð¼Ï[ðt‘;Æÿßðü™ìWÀ¼ ÏèÆÌð †'žbxfÁ“Ï ðL‚Ç Ï:xÖÀ³žåð”ÃSO-¹«ï~xQ禷£{œGAy}€¸4¹T0=Îô‚Vü‚9Eù3úÉþô©x™Ì¤I“¦NÏ`eôž!þ²5·œ 6ß|çÝ>AóŸàäîÀàMÈ–é&iS¢$ß2–¯¹®g»“>±{·w£D‡–(™mŠTHÊ—Øž×}Ø—ùä­*€$x“íîœîsÂD– …ªB¡ÁL&c׋D*[Ê?Ÿ?‡q>|þúü»‚}£%UÙ@<„îŸCú¿o€?ð‰áó+|>Âç |áÓƒOûÍw Ôã!¿Nÿ'‹áùv}»¾]ß®o×·ëÛõíú3\§N¿tÃŽ®twÝÛ^ä|í:Úv{scã»6ønÛ›mù›®N~oomtÖ·76Ú[ßaÊVwë»ö×&¤êšã&?Tyæø áÊÿ“^ËKÌ:s}+:W–ÿ"9ñ¨­èœ¼î.. lá´»…^÷zwg³÷º·hYGùW·çÛõ´«Øÿ]ÔÅwR|Í:°ÿo×öÿÍÍ­nÒÿ7;ìÿëëßúÿÿõ¼DG{¢ @±+ .çÒö˜†!%,—Éš'Lm3õ¶³ÖP÷A3掙Î} AÂÀ'DÉœ;ž×Sôé S±øÌŽÏÙÛÝÕ¬VËÒ ¶÷YýhµÿŲzJžQðÝé‡7Œ{ôûÒ#öùÊR1ÄføŽÅÓñ¥“™3ÜÙy¼TiÃy8rC­§ ËE­Øc °zIÍ«FOQ–•eöc0rÇ®3b'Î,vèÀÜî_å‡Z—îÈaüµ-Œ\ð$€`Ìn$ÍÇàdŒSöƒë_°Ýxê­ÎÜ8n9£ù>eæ—S‚¼èlNÎó„ÍB×cÛDÃ3vå¹¾AŒ3Œè€§éÌ£—ÏÐ"mó3ÏAþ³¦8òj{îzÞ {\‰m|˜ßa»W£!Ý£–=r<Z†Ôµìù>Û=ÇÑt>)e"¾<ñG”~s õzWh{®or¨ÙÞjvøz3üÞ†ÿØš©; íØ¡c<Çö(§MF‘mĸw|`×—/®ï²Ý Ç?<·½áMà·†Át?_OÌK³ÝivŸ!BÔ䑯ù¢1 J |lW ×`ü !‹í3¦=úí3]UÅo àÁ°ÉA B2Dúçy‹[T¶Ã0â q\ãi$ºzôþÍ|hD­·>}øÔ\Ò?Z»¥>4›Gç:/e²èÜÇPð^ÉðpUÖ ƒÍ}o$ªè)x•( Ș:h¢FW»'ÕO°ü¸¾®™ þ‹œ\•Ž ƒnß Þ]àèÔ`;m'íDx„¶æáѱ0ºÓÄ-EVÙžœ’¢H±B’ï\x³43M×üT¶¯µGºÕb =-A™2¿×Ö² Œnäw÷Ìñ" »‘ÃÞlæPôêª5ÕØDŸÈ\ä'©Aš›è‚P…4=áy ]¬ji[–i;a.À¢YüÍwVw+¬ÃŠj˜¦fšCKjéϧyíàR2öåæÁ‡vsûãšj¥¹x% ó£ía hKñ3;œÌÑ^ïpy5Èš‘%Iu\3e%jXÄ~õF{%h˜,Ƽ¤Á¾gím¸Œ^¡¯¯¡ë}uÀØö=ööÄ/‰<â—S]7‘Ö^µÚÖÃU )55TÊÀsrѹÕòœÜ×D8äPÄ¡Y'V­þ7³bƒ:-@.Ò.Ð*j!ˆÏÐài?Q»Ñ8*1Û‰UŠY§BO½gie âÅ4,ï%ÛemC{Pð¹"û{T†-ÿòË/ÈFâ°Ôœ9raa{¸ÙTçîu‡~Üj¼œvoä­©ßb-÷¬Ö MÀUš•¨˜<–ŠIBÅ„¨˜ÔQAÕ, ÃJÜ(hÉL­#À©½øç^{bSý ÜغJ°9ø§\ÉC-Áç¢u€ï¡ÒCZæãTáqÁ&<€ÆõƒD0>ç‘ ÊREû8†a7ÔõfÄÀgZc›ŽÁ,øêÊ}‘GÖ'+¥Î“"!ÅŽÃK^,.™Ußiw×±~øÞÈáªeˆê›Ð¾Nر»p°~åGHùb¦N‰êLJQ ‰êÅèp¢ZìLJÃéH¸†Iªì÷±ïaœKr˜ i=ÍÈÉåV¸ä€&©\âÿ£M.ç.ÿtù“~°ÿq–kÐ<×8§êr®Z¼4,§.ÃßÔÂÛtªÓy+÷Xôn=EÑáÅ›cÍ0Šš‡ô¥å‹™r«4?D ¨a|ÌfI“+ÐRÎÉ®Džá®BRrÑÕ)aX<|ñ™pÀ(Uq ¾Œfõûš5‘°ð/À—ª œ”h¦ÖèeÎ(@U„yÓ¯ƒA¡xnVÓÓ^ŒÁ˜Ò?4z9Êñå@’õÍZXìÁů7Ô0»šÑ¿ƒÉOep sË®ÜÜ_8áÐÕl ag‡À[Gw ÓÂѸ ßH<­%pœåì8˜A²ahJÍðí’§#fäUC" ?H;zójðî5Χ¨„Ä: ŒòþÓX“ê|¦©„!Mª÷¬pð÷3-¹T|Ý…[$Ê6‰Xÿ@ri¾—‰¼4t\Ú^‰1ø”_…È“ÿ‘ilmhýô– Ü×TL“ôÂl›/ÂhøÍ³3öy\ /’™WÕ7¼ÍBEŸð¢ý!¶óúDÝ&¤2|ÀÛ—³YòÄo:å…jÒõ+´7½G*ä— ´×ÿV¥…˜W©~yNmú*›à±—´*]³B®ÄÉ]+UܬE¿R•줶a)|ÐP':2MÕöYÚ2PØÒpD×çÏ·Fº¦ u ¸v MK}¬´ïßå$V Òú¤ûÆpæà––E rþ£JiΉFµ£åº /å¥=|‚´±f!îáUÜHcQÞNQ¼•k˜Mió^¡/ò‹.™Ä[ ÷2(­&ˆ\O7j ")ÅQZ‰Dïî°â¢«”…’mÚbÊ/I>ô[’i+ÇϾqFXRbLƜȾaÁ‰Â« © —;Pmªüéj'Ê*Ç•¢7*7½¢rjaŽ«viêw¯€\Z4b»»Wo_¦lc‹`¤½;i‡ëT¦ ܹëË[w$Îv-÷kwð”Ò6\æ{K©,³‡o?]Ú¡‹O’CU²ƒbŒPr@;Vy̵Ðaó÷ÏŽ^Ÿ˜€~¼aÃsÇŽ£bFÇ1ÿÒñ]A‚1ûžï0<î#2”ôæ#ÄÕ$ ЦX´“Û8\%#¶:rC¶JN•ÕSx¥X•iæ^ –3!ò¸[VÌ£TàŠlB„¥(Øur ƒlº$Ī$ß9 ¯®q7çJo6›¬Ù¼fÍ+ø†ÄîC¸á€øþÇá4n”ø»í³×Îû†ä£Yß7ìïs>;šOpƒ“½ÆìmpÉ^:C”"Rx_A#_[õ.R^Já&ßôÐô•¹ Ó†Ä#ÙJ æ!—Xó¾:»2ùòš zD ˜ …5êW´*†%±_gkd⦧™Ôåd}/7¯+$gˆˆ±¢Ë§«q)ăuÔ1+ljû¾Oí«’Xzû=B˜Ñ“¤0I¥0 Ha’“Bºˆ(näo`ZLÏPUÉAùê‚à)Ù¬ƒ££Q2Á3ôX Gö(y2ï`àHIì‰Áñ,ݪäÛ¤ÂP  œ~Ú…™ ‰ë£&m¶˜|ÅÕD=5I–ùž^ÞäcX¯1ÚéíƒV ú¿Ñ†Õ+BH*˜ëÓÐÆÀ‡œÈ Å«v¢< (Rd+I.šÎn…ßÛgý¾0ê¦x•&vîM±mH|ã›ô¢(§–ÄE KÆî¯CaO§©¢K‹5‡óO£Ö‹«Q²¹­¯Ð¡…”øIî¼´¡&ˆhËò"ï}ÓP0Úä`¼ço@‰vœè~á FID–e1 ÙˆœH¼ý‚F"„Ã1Ö€·à‚Gš-êÜŠ,ÔåÛûeê±ËÉâßɼ(í°zHVäx ~úÉéËw?ŸJ+Uê]Þ_½4©[r¦Ä'•xúàC »± 5¿Ýç{·7Vâðâßá9÷ÈkàPäNÎ)•+GÊÁž Zr ¤—Ôƒ…P5ø¿=}ÿ¬¨íÉÏϦ¤(nÜÕ˜N%I‡¸”]H¶€WÖÑ௉Îáö÷Ù3Ú ¾MQ–ßQ´‰’®dMÆ«’eþ"²aÙ0‡Œ”`OtÞJ(Ί(ΞŒbVD1«oþI6ª„uÕÎÁäaSmFO”ÜÝÌgÅÙ(z¿) yIäIB¡w¦dÝ=r†ÐÓ]ßTσy?Gö v}HÀ¾‹qŽØYiz :ïÛFª=|wŸ­om¶º›Ð÷r›K)¶FýoívÍ2E'µ¬´»£ü&“ÇÔd[PÉL)¾ØkÓ›³• ZVšv„–~=‹ÄüÀÿ.$nâOpÀÌâŠAOa’í©ÈaY—¯Ë0Ü„.„!8”çÃ`–ÄúH`^‚[ìÇ B|މ¶ÌÿøÔÂTÕééå¨l¢¿±<á˜e›yp.Î|!­nvUt×01‚RïŒ|Cy0Å…@õÒC`¿Ü#ãÊ+æažãOâs] ÆÖ$ÐYñ±*ƒ+IQOà³Ð{¢Êb Rˆ¶L˜&­A­­ Ý¿œ÷IŸ{…Oqq‹ÃŸàÇwÅÊE”%†ŽùŒUõ‡ 3E¾ŠJÍxºC“*÷d„##ü˜ÔI¼^ ß¼ÔWß&.Í´–`ÆS ˜p‚Y~÷'Ù(;}tüë÷KKKï#Õ¥›¢c vÍ:ë5öøwù¿ßL˜(Ü’œ %öã·0ßnY0Km7øºyP£îE¡ /‚¹7’Ô¥H†^©÷åŸXh]Îs)¬ms·"“¤š§}YEb`KTgVŸú-kyYx™þz$9ÉÃl†¾û›ß ¤þÅ ´ö$ª¤Qv+Xüét<ùW<ÏÛäö6þ+Œ§b¶±MWy¬­Í“bFË™ X—7YÇ¢ër“™Q§’ÒÜ\Zb¯IBÆZÇ,K»Ä!뜟œŒXø‹MjÉrq2'‡dçËÍìá…Þ8×'?Ø«Õr"Ù˜Üãáü6‰³&ñÐLÎóq¨o5ø°z[Áx;wð ;qÞ«ã¬Ø¯h ­^£å.”íZ#-ÅU/cÓºØRÌЙØÎG¬$Osàó ¡¡KeÈE ‘Ñ ¥*ÕÁš@¤{¥0­Å¤ÌJ QEƒ‚iËŽßJãxq_SP’,ùŒÏ™~áÜDbˆ– ¥-Y¢7Æfj“ŒZål€¬¯™._ÅÉÇŠ§¸2${)l•á3F ÆÓ¤ýÍë2åô‡½(“w[(Öq|ÙyÁøˆJߥè¶ÌÀs¯óŽ …oã©â˜Ü–Ò±¦Ì«ýW¯ÙT;?ÂÞîãÚCa'9!±%L»å¼I­Ò}±W`ÉDg¢o·«¢ EÈb{‡S7¦·÷Òz<=O—Ш™X4â `xN9ߌ!‚¨gs|€#)û\ê²L;Å ãÄIxh_º–kÈÒq>Ú*ñ(Rö§ «XáÏãpI:TïtasR?+SšÛ¼ºZAFŵ&x½ø¬x£xT¼Y<«Þ’s>£ÜOÆ5DæF6FzælN—ûGzkõÀ°ôÖ­ eïþë cà “,Ú¶[Eª|©¤_ô@x?Z³m1'!EÖ’jõ ÀiôAù| I™¯óxk¡ú‡Ô£êÆÚGc¹ßW;Ë“"ÀÁ*dµ²œÆ'õºßÿÒHëåg|È“sqCž™ã³šâü]FB¤)ý5k¿díí1+_µWõþȰ˜Ú9 ªëÉhIÏ‘šžöY¹ý2£û}ø&y¹ÇUšÈÓxƒ¥Á@QöpLí°·G?¾R”ô(0¤qhû‘‡‡²PT}G„o—¢¡Ÿl,Åqò_oßýtr|¢(É}ÀHäè#|‡ÎÈ>¸¹£Ó]Ò‚/_¼xüÓéñ»·2 nÄl¨/Ž='¥§ ãjÊçóܽ´=<'%²ÅžmŽa¹M40kÑÅŽsÃÆv„ÇøÄ綯ˆ h^GŽ7n)JƒÚÒ`Ð!€²È‰1$™Ò`¤wBÎ)Nû•ëQ82 ű„š”Wl_À(4ƒ)Å<ó08¬ß=õî᫳Øüp1"éÅîûéý«—Ç/ŽN_í+Ê[gBgç@ÑÈñ#'9¹=ÓV(Øöùƒg4 ŠÀ!¶#Åf#¨Ðõ‡qúÀ‡‰±bn̦ö ̈ ¸e¢yŒ#vvƒn´¼=Ûg¡BÊ<âM¢wÄ`BðØ€'J1Æ\‚Ûì 5òìÆù_‘,;¢I Âó_ìRL^ÇØ7ZôŒ 'Ì>¼¡XD>q/ñ´ 4[4{fc(+ˆQj}Úv%i{Dø›?bóÓÖu²†v §áÜAÃ;8 âó“À°“d \ó¸''BðƒXÁhú¹mL ÈæGVÒÑ ªIq - À8Ì9tЛóuWУ=l=XD"Gœ~µ¿æÑ¤:Ìœ1|ü!8,ÑÍ”ÖÍ„F@Ÿ^ˆ³¼héÁŽÁJžÍAž0Sœ9þ(R@h(±ä{IïʪJµØñ» ÈzèŒÐa¤ Õ" L”ÅuɧÁ4hxΦظ´§Ù³™çr‹ 9 åÝLÏÏ2þhEô¹>Y¶8 JJô {exòták4AøW…*‹5‘14™Û®SS¼\[dQÿ’x&uâ@΀¿AkŽÁ¸¶Çlo„ SÞYÎpžŸ7饲H¡à·2fò)óËÕcz‡Zj_МU%ÞBêa¦—¼ÊhðyÜ Kœ„Oñ‡œ¡Ó©®Ä„ƒ‚ðì&e§¿äÎÙ¤Õ„7?ü{OÜV•Ý“?ˆ¢lò $ä0±ƒü;6¡‘ígG³¶$ô'$^ñ,=G’eÉ•žœ¸$! d‹#ÌêíÐílf‡ÎìPf–Ù¦ÛÝ4!YXÚɲ¦Ðmf7tÄ(³u»ð@õœslÇ SÊìnQ"¿wï=_÷œóÎ=÷¾§ûìèˆô 8V0€Ö¨âc[•!~|ï"Ž%,À“X}qÔÑ¢ ä±hпhd)Š#,ΘŽ#lw"‡äìQ{£‡ê¢1\çÚ!ÙK@°=# ’·Ëá©Þ>vB ( 8¾N©ûåqµdš¯cßÂä}çýEôóæ_„Ù´°)Gf3¢íóx«ý€9Ά7ü é&¾×K­8Ä÷wÍ«¢FÆÙe/#Á?¢!°6?½iÿÝŸÞÔE‡‹ ËÜÍ˼„™Ì82 Æ Ý—v£÷qFq.è*,r*Áªd3Y‡ŽÙd Iþ^)Ž~ ,íuàл[f5MÊã¾Xp”M&ø%‚PÐwýX,Wp$¦ P»ÄÂq±Î„¿pq 5 ¹‰œ ¿?žÏ&ØJÊ¡åX{±5ÅQ|¬uqìhØ_É fë|3B‘[ ´ÂÝÆEh¡®1Œik|V‚‹` ½•L4€8ëƒËχ;q†SpÅ!6o£5çÍlTe·”´`!‹µñ$’ÒGL.Y[um‹ ÖÍA¤ç'ça¡£cò¹çC8×ÌÓ ¤ŽRþ0îú±²$I´ôºìl1 ª} õ÷l‡û9û¿ûñ!<þÅòhlliÜÖÚzÓ÷?ÀGhܶ­eÛ–Ææf|DcÓ–¦m[„Ö/VŒ…?ÿÏ÷_Øþ ƒá¡úÀÅcñýÿ·¶¶4ìßBïÿhinüjÿÿ/ãÓ°Y,¼õƒ¬^xo_3¾I´i+¾s²y[[Ó6qR@\n§W~ˆ›L ›Mq; Ÿ ÂØ\‡[ɘ5àDDôG1äâ­FX&ÐK@àò"JP‰2â^CÚ´cÇ6ÑT`|uÄ¢cѸøð(wÆãE­OD‚uy·Ê¯÷+ ú‚oe” &,´‡©¦œ˜SÂ8&b,!ÇŽ³1ËÌ #ðc&Ra“ŸÌ—íb [ŸP1ƒáïðrÛÂj»/ñÓRa© Þˆ¢¶áySýÑhµËDw$(ùœ(ÈVåÁè˜B?Ú¦®"¤} _²Ã›´Ž‘gKÝ+– ˜úÂ2Lfb¨#±y¾ ÀP§Mè§M÷# ËNÉõQj!kFƒŒQŒÒð=‚IÌâÅçgúnPç¶Ô“oÈ~¼G¤L±€ nî@êÓÄ8š=¡Ý6NÄ£Cê!0‹4LFÃòøœž@Ɖ +þƒ´ÔÆ10™¦u§ò]j”a¸4Æ`2:Ê® ½ß“ü-õ”ŒÓŠ!ŸUÉ ðÊ˶)—G)éV"þ(®YB+ÎpºÅùÆ5µBÏÇ´¤»¨ƒš³kK>|î¯:yD·§]XîÝV—è²w»û-NI„s‡Ó¾ÇÚ%u‰û Ò&{·Ý)>þ¸ÅÍ›6‰[|÷Q¦-¹\¢Ý‰t¬}Ž^+`§Åæ¶J.³hµuözº¬¶³Øáq‹6»[ìµöYÝæ¶›‰ú|4$fïû$gçn¨±tX{­î}ĵÛê¶!ÇnÇ":,N·µÓÓkqŠÓaw¬Ð….««³×b퓺HõVð¥=’Í-ºv[z{õê@ KG¯ÄHB§º¬N©Ó¢ó3F£4²ôšE—Cê´â‰´WÙ-Î}fÐ€Ø “:éQA£Øeé³ôH.±¦XHk®@ݧԇòA·]ž—Ûêö¸%±ÇnïBíBòêÜcí”\íb¯õÝËŽ$X—Åm!ö@”Ø)ËJj²ÚÜ’Óé¡g­¸ÛÞZI-€ÝEú´Û°ÏÌ $»s’F}ÆÍbÿn ê¨BèžÛiAŸÜNk§[,Ýv'u®Ð_Ñ&õôZ{$[§„v$ÔouIµ`#« ¬Ä l=Ôw4 ȧóÓL­Ý¢¥kåçð`v—•»©¯s7×>Z¿Ád2ùãrxH–ãíááÐBŸåÐÜŽ… <ÜŽSœ@‡Äph8.†ð`2ÝÂßAˆÞŽÞnïn¯é~ö°a¾ ãf~ Ô-Êp]†çˆ8¬Œó˜ðÂoÝ*Ò2!y&E%§€EƒÄÖˆ2q™vq‰óZ6#´Áwwô6˜Ù;1HSáü-€›pà]Ó¨¸­YÇ®_ŽaŒl»!°¥QÝÙx¯D|±qþœIi‚""X(‰–ù™ ÌH'‡x´4á FÔ-Í^UtíoØßܺu ©»ê:¢‡"®ƒqì'!`vÙÌÁ bÅ´ÊëS·“8Nù¨-¿FavW£#´Ÿ&èe)b æÀÔ¸ÜãÉR­Ý‹ÏãÐg5lŸ3±¶¨Y9< Cf£ÂŒf ?ø”[-Ó<Ý:*`E˜ij¸°âf³Xè_q¡¶}²_ù_ º“ÇžSÔ'Úœ†Qß®ãÔ´Õ«Þ„‘0Ý"ÿ‰]Ç*PÅ{vy?Ë+—[ñs ŒÐ`Åu9,pK:¾A/¿~çanŸcµ"ѸÝ*^Ñ7è»%†‹1G·Â|!Ap¢A Å @¸È\ÐUƒ©PÈû ÄEiÆûe5‹˜OÔŸo¦m!% ý–¯Í™ÿB¬¿ï‹æñyÖqÞߨÔܺ¥ù«õŸ/á³ýðÁñ/ŽÇâïÝÂí¯½ÿ•­ÿmmmújýçKøÿ†a™ ÂIU¡NÔ{&>H^¯èßãšL”M+KÝvájÉd×ö²3Ó[!õõ²“ï©%/͆\ÏA8óp“ $/U<ŸX:± Ù%é]Tûýéf(Ý‘N§O Ï”äʱ®ã4þ=¾]PO}ò@1öË7J„)jCð)ézAJºÿOžW«’—fX“yM >ÉëU¹V¥?·5s pd¾½Aöí}ô?3\<›ƒÏEzµd88ˆwpÂþúxT°EE:1 Ýr0 É4ýržµdÕ²ÒñƒA_›VÓ)GpÎ="j5Ð_LmlƒÄÙ‰Ø|>&â0Qü€Aú4ƒ>SGSå¯A??m‡à_€NÊ3»@â3ßbjLÎæÛS=Æäá2!Q5U~¾‰YåÃ_– B:d Ì"NI¨,c,´ƒ!dÈ|öž‹!qRºêȵ"­Pi&Cµ@rá ¡5’;9æ•Ï ˜—fJºúׂ˩«†.\6^¸\…‡¡Ë ‰eÙÞtNAíÌC­(‚z[H,Í>€ÙZ¸KctPÇh?c4Uþb’À›Ð®wdÜ.1ÍÞ­¬Î帄De¶/Í౨îZ~™*³«Ò}@7žÅ‹!»,}¯‹liºé|Ó;NGHàª, •§¤L©t­Tºî ËýàoÀŸLò7`¼Èxhå—ßuˆuÀ7Br­x•–ãµ^êà¾WŽ,E,n» ‹_}R`ñ :'v¾è§1ržyΦʿÿ8PD>ó*Õa¸9 ßsÈ+EJ^ŸÝ*Ëý”Ç'kè¾À.U’±Ú*ÅvÛ×…îÈZ»O'J?žOULìD6Ù•é4÷Óxžr£U™ŸÝ%“Êåý_÷¾y1Í⦑7ƒv*J.Q‘KúJ¶¤éü›€™A€]e±$¦ÁˆÍ…[Q µË h×…èÜ\¢J­@PNӥį‚>Ü›XƒP{ óX¿–áW|G] ø“Ò ô^Hu:×à’³=k@«Ùw;HíÈ\Y 5ey9\)Éxj ¢éÍSUÛÿ%Q2qáÄ;jÙ®ì‚ÿíó"_nóCÖ¼¥¥uë¶í;ðâ˜óÁ;7å0Ò O åW|i­½~Dþ¡õ|D¹îþ­\†Æ‡=´§:ëzF¢:.ˆÕ¾j¿'Õq“‡«fߊK–ýìŽ>ôË Uk¯NÞXߨ#3¿(Ç`‡¼)i¦šÓ¨ñìa)9Ûxd89»ãØ#ÉYY]šœõ«5ÙÇÒéÓ³àÖÇÃ%|'X6å‚»ý| Ês„4“ÝT Ý‘ÎÑ霺AkÖèw 4}ú}tøóW&¤é¬Ê™ï–a`¿¸ í)O…Œâȼ²Fª+O†@´ÓïV­d€É•OOx*’G†'Ë3c€Üô^JªHm?þ‚jM­º €•ç–´K3‰ÖÊs= ¡®¨†€%œê*10ýäútqÆè'€ Ú:¡ñØ@NHlâä²÷âõ¬£aË‘ ÆÄÀ4ªëÒ &we¨ÈÎŒŒ o ¤%*4IËV£½¯£õSÒ4$žÒµ”gz‡ÇXùœh ‘õ}¶Nô]mzD9ZN£FŽüy™¾=å¹:é6å&Q_¯—íJ¶ ‰'.Âôðötªì/HÌcp|ŽÇàXÇoÀñ8fRÊgŸƒ2„<óŸ6ÒtÖü—p„Þüm8–Àñ•FÌ2ó÷àX Çsp„ Ëü—ÂñŸà¸ Ž?ƒ#ØÈüs8‚rÌxiƒÈÏþ;”!Q5ÿ Žà?f ôÀžýÊ&(-¬GZ¸8ò– ¬+wð/„4ðe6Ý.çsl˜¶ wòºûá[ËëAf²Uä!TÀ÷6ø‚c"/”ƒhßÃÛVpØå¼m§y¯[ÅaD.Ûz.Ëþ½›Ó~ˆã¼®šó¸—×ÝÇûXËilÔµ!Ï9 ¶žÃ<Àû¶‰Ÿ œöfŽSÃÛ~næm—Iàuuü+pÚÚu¨-¡~ÊAyëÖ°2êÁå—xûj^~—×éè”qùÿåÕ¾hÓ÷ùì»ùQŸçånÿ6‡·sú—8ý~^žáe/‡?Àå ð²‘+ò NÿYNÿ/Ÿàå?ãô®qzÅËÏóòßpzœÞë¼ý}Þþc^þg^þ)§ÿ0§ÿo¼ý¼=£Ñ㊚æíÏòöYÞ®­}ÆÛüÝh`í×Ö³r¥µŸàíkyù:/ãØŽòçò4„üíÓ¦+#í]ÿEŽo50{á ~ ìZ;QÍÊ~­} +p|•ã'xù;¼|„ÃÏpø$o›·Oðò^þ&ïïókYùOxy†ûÇ‹æ¯/ðþáôopú/søÜ~¯òöYÞîõví³Yú¬‚×ö©aÁ«Äb‚—i/>ñc î¶Eeø8¸ë„ןÇ[(aÂÅÝ8ïAEU"ct$PèE¢×ëzñ÷ørØëW£±8Vu¹íNo¯Õåöz±Ø©/Æ9ŽúoœÌœœÙdv=Îæ°s˜LÎ2aB f°aHŽÃädÙ0»›IÈL;òæ°‰7QÀ“Þªºõ^¿¯$3„=}ÁºýÞ­[U·nݺŸwë^Ä/Ô€t¼†Ñ! ³z‘R…^Yzxƒè ¸HA©J¡J¡J¡J¡Jáà ´ë)KØlãùÓ'6ÿ¾ ó“~˜ô¿,‹÷¸ñ LòiõÞ}_\îÜ»ü—ËÿœãŸB\ñ .‡ø/ƒø ˆ+!~ âË ~ âåâ*ˆÿâjˆÿâˆÿâË!þSˆW@ü$ÄW@|â•?q-ÄŸ†xÄ)ˆWCŒëôWÏEçü’ß$ï—-èý²½_<í²»½£¥µ£Õûç{¿à:×·Üúü´4ÇÜn«ÿ̦Fÿƒ»¸†P ¥P ¥P ¥P ¥P ¥P ¥P ¥ðÛ fÿ½Á´ÛóÁýAaáó?Ü›7¹uçlnç¿´´–ü?>„ÐÜ(«u.ôw³ÓÿØA¹;%û3ádžåáx:—ëÓú±},:1žHdš‚‰©q-O$,ãjˆßßÀÉTbÝ~Ý[¶lξ€|ÛŒG†Ôˆ•Àäñ¤Ý$ÿò‘9Dff>:g„0:ìã}¢ž„a:)ä8,„1k'j,é°ýY!ë/娌U×Í8³Åj¡$±½x@}$“Iv47ïß¿¿‰›©‰æ†õ,Ý¡#iy½)ÿzª¡õ&ÒëEi’0? K²„I‰ãi$t*õ½KNÆÂt˜\þšlb {q¬Ùíº’«õßÚ„ÅY”1ÝÉ+x[@¸(õšŽ‹óY´÷¸2Œ7Ù‹*‰¦X¶ê™+†Â©.µÌç(˜-s½ÙÖPàQžÔ0÷ƒÝI'ðÄ‹ÁÐô‘Iþµrq¥\ô¸¬oèãÞ®ë¹3ˆ…žÁc@|ïóäû3k.õäÄâpøŒìpôŒ¹€||Έ_²ÒüùüK9šF. âø]Mƒùõ§Ó\ÊÑ4˜×ñtš¥MC"p:f)GÓÐdK8f‘£iø£ÅO§ù ÆPÚa2–ÖMç+$&Rd$ÔÙäâÀЋ'€ÞŸÈ ëQÜN™–„M‹}¥Ã6¢Ê̆h€™™Œ˜± v›‹'ëìôvv{}Êð=]~·GÙ© c~eù®t:Ö<M4Eî6½<ÐÚ²ß"r:µG &cÙ4þ« Àsøåõ]ëå?Pt¨©†ó¡vèJ%76â®?e+ÓÏX8.žÂq%V°€V/ÃãZÕƒ,”'Zð tö÷‚f5ÒUêQ!þA·mk@nŒf\5Õtª˜H”ñt^A ]Ê3Ô5âQP³A/ÁŒúôXvرѳõ„´³:–̾jaþôB,‰±Iw‚•"Kñh¦“ñ*/HÆŸœ´7|P™ÒÁúE¤bª±t FL3#Н´ŠÃ‹Ï)ÎàqIø+®7!ƒ~\•Šw×°2Ð-7N…ÜsjÏn6­7¡X"ÇKcôýò7¼Ã‹ÀEç9ð$Ðè[ŽEßT X¯êHÒíYW&‰ ”¨ÝÂ+€6Rƒ‘Stüà%4¢H‡3ïƒa«Ž9U?V»È°„ f²I+;Eýqh%j…h¤h™òC»ô ²ëË ïEøa§çM)üò)ðèÁ–nÊ Ákà5Ý^Pù¼ ébÏè`ŸÎ…nX8YèìRp ²œ¿³Ç»£³w10ñ7˜XB³iBãopÁƒ ñÉÔµ×@(TïŒ -/^Lç°ú†ïñÞ2OÒ ahëá!S`Ò)ÿHg×=ÊPu ˜°šç Ý]¯G,~‹[˜‹Dô4TVAîGãÑBÔÊbÐyM-A¡•¢ýƒ\:ÍǾŒ9…î¿~GßÜ8–tÉ:ÙX!Ç“õxºÜ8nTŸ Û^¯3¸-r ïGiš*QØóXÈ hɧ\íðöÂ8Ê4Šï†Q»X{1])‡—Ñõ;ÙLB³Š·å'r|FþÔÞp*Õ”Œ5É0©{ÐbÇTÆOÂk›fÈâ$öÇ{êÔÞ~\>ÔaÄÓÙñ½T6.–I¡]%–h .)L«šü{ÑéƒV¿ÒÚz#ËF½"ݤžú×£^AðMúó™B2¿—š')’»e£ 7Àîn¥Ç×çìîG×/·,Ztfñ´-=Ëè`Qñ,˜¥¨«³ZW_·(Â&Gè.Ÿ·sÄË%muä»xDéöô ÐÍŽ 0B[É(‹ÖçéV`Þ„hh»3è`ßãÛâÅ%Ú$Tu;W„ JÂDP·#è@ç=^„} Î‚'PÿN=°³ê zwwúYÅ<ÎEF38܆@é\• 2-B;×(As'#`«Šš„`Õ½«Ðí«Óí\SˆÊ¹ ê@”ß¹ªüÞ‘ÑaÖ't–>ÕÎã øvj@{Ôófê Ðvr"˜$wy}}=÷ë¨ngy†é…L œ;G¸F|£]#£>Á‹Õvø°åµVÈps°h«y5CYDåSKd³HÊ'’Q #0T3y»iôCCxÛ*óA3ÝÕÙß×­ Žö÷+¾ÞQ\íq0@&hU3†h1ÇÞA–]Ôlõì[ìOkÝz0‹nûh<×¹Ã/X´µ?Py}8vÓ0ÚÙ!,Ôµg5B GÓkRZ°MÅ"0ë™Цnu€=}ýªJYM{8x¬FE[,¡)>OÐÐ «Ùìl‹–I€÷ô vöëàmªÊl9ôe´©²ÑÁ{‡v*Ý}½^ÿˆà­od瀽aBxÿèðð•W5ÒÐLØ<©+;†¥CêzÎïÅ9¿­`óý¿íþü¿È÷ÿ›7»u÷¿´Ñý[6–îÿø0ôbb¹WT½ªéê>ˆœmW›Ú°^ÆÓt/ w†‹ocøMx0œII˜Qà7'D£q‹Ž¥ð 7^uÌwZÑ+á^RGBñ*PÝ+üÂ4b üÈK“$§OþbÞ Rô­”o]sÜ@‡¾àu¯1q‰9@fé–[1õÒÿsáô+*6øº6¹dŸ¿“¾qÄ"tÄ…W¬»äp&Øä"L[éc&]©†9°t·¤ã£áC;"âµsí£º‰”W纩$^`¡m k ©\áÁp2SÜgЋ‹AG¢S‹|»@Raô´Oëôäö´Kܪ™–ÓY¼¯¦¯Ex±!Í{ Ä•„8Ÿ…j¡€é;@š`½Å‹”3&½¥Ýt"åpéÕ´xU§¸„\½Pš¥È×Ññ}ü‘7H‰«vñAÕ?$S<ùÏêÓ>¤’"ã_-Àû˦Âi<¯'Æ£ R)qû|6)'R¢úL{'ðf[ìKqF-4Ø\®ÂåÖDÿÝ<ó>vΔîÔù½ºSÇaG¢ß,Õ+yîIÔMhýè†äÛ ˆoWÛ_,<.îxŠÆšJdi=wŒh­—Ñ…±Dd0Tcd8ŽÑâ[Ç êÕ?`q&²j É~íŽÆCxq»vM)t=ˆ8 ö¬@íî#}J…ã ‚ݦ)èHÒº+Ñëé‚wÑúUQ¤:)’½ÄŠpøÀ»€9_¯u» íò€II—|ÿÐè`¯qs 6 ÷ ýN·v¨û:Ä®€%míà[…Äž ›­ˆjI»9–´•C\d»›ãR·rЖ§‹†~[9xm*y_[9löqP/q+‡ý>ÚhbÚÊ¡Ž[igwš†~•ÆJ4ôÄöŸÌŽa{ÅËɵcfe0Äb£uï:3 š\2Aqx9n[—sƒ:F›` gIã WñtHxû5ò…·Çâ¾ÈŠè8 œr0Ó ÉC«Ó;8*çr¿jÚC ÿPüP,½w<÷ŽGBá]K9=9¾7"ïMÇÆ#{Óã‘´l»ï£Í~ÛG›u×G w[·}Øî +‰´‡7ƒèß§NEð bkŽüÁŸú$‹o!aÀ¡aï ²ÿý~ewßàFNºFIÈ`ë7zP~b² JãMèÉ€ŠYÚ-YŒ(zŸß? r€Æ£@«ÄÖnHö÷õzq=®gH㤹±Æ«]ã¤àg˜"ÜÙµ£«¦;:NgèÕ@w«úhN¥Å5÷„.˜ŽßfLW/Ñ|S‰¦ÓÙpJ5SÒ4–PÒ™šê?¨©¦Í'ø9Dn0[kªÅ×Q°½¸“@ÀÃëCN2Øê@”¾!§”(LUrÔÕÕÕܸèN'µ¡¹éò'bÇR˜­\ZÅB¬!!Ê@l’ ß?•ÎÑxªqr à¼È? ·È{ˆ´-2hWŒÐ²·€0U.H17Ót š5Õü1IæEÜë³$”¡-"cê§^d ?öêjE§zºÏó' ÓÇp\>µ³¯ÛÞHåXµN5Ûòï®YÜàÃ5‘H-PB8˜×eékøV8™‹©Xú;QÄÅÈÈ™*Lèùû†‘ef!Qê¡h%¡K‘¢h&%C5@†©Ê&M¦BbHloÐdRmÔ|#Ò._?¢ ¦b*œÛ gU˜êF]»ñUK›!Òx\<“6ëo÷Võ;º˜®‰¬xyTÝ·G …¾9…í£˜YñîòöƒÁ§Žgb?â€.fjjO™ÙŸm$ª 3f¨¡¸!XÇ6műh&i$4Ô©…,D 0ÙóÖ-$C{·t²”‚ƒ0&:ÙÚpœ÷‰_zstÖëêFjæQœuReV[øÑLϰ¨Š{†4ó÷Åöï™þ/"³Kÿ!;}w2÷[—¬Fµž ‰²ëeijѲ±EÛ)¿Ö5ª%ßœµ™†4ïG›—¬¡ô•ÌQAY˦Ü[¤ðC+®LD3ÂWF‘E¼°3AãòÄxM5-|â’ŒfÅèRK󾏸¢5mq#éÖÃç7¨mE ¿«[, ;†ºï¯v[^töõW{jªiG!^ÈH}Õ_(‹û~pY¯ÈÄŸ4ú@ÈP8FÂ!TZå“ÙÞá§ Q͸ºª˜y a¨|…ËèÅ«K›Õ%S¡¼6+#âbeÌ}»ª?·»Hn±p`‰1+ºëÛýíòx4 5±5ÖQ5îÇËMûºnPémUJ±Üš…+‡2é{&™šµ^³`ú,jÓ”µd'Jñ×…¡‰ âÐpd´J!a=ÑÎd­ZÙç–fdŸrÏ©jÆÎøAPi¨˜p,ÖIVìF¦Y+( ´²P±­9¡7“Ô=)¦œ&•¦ìÝ^Ûéí&éuÒfsãÈ^{H)ŒÕÑ™úq!œ®OGªëñïwkcù›œöYtK!èåhšiÖäzhJblÒ†êáÅwÓ¾KyÛ6‹™YP‘iU¸d|Zg”¬€—€’s8¡´¶Äý ¨Ò%ˆ@ŸÝ‰6zD-/q·Ôn8Wme}ÒµO0*Ò‚™T Þ-¢•®}.ìBìÔ¦ˆx!´ö*ìja´&¼Xn mÔ™­¾aÊm·Ù²ÒÖР-©øýýÞNÚªåòkëFs:¶H&~œÀêÉɇ<¦B.Üi"`‡áõÀpä¬RߨPßРî)¶Á*0=X£ú”h·R79¦dÚ¬B77ô xµOã±ÀDÚp§¶ ;â½-Ì–fp¨Ëëñc’Ç’uF)›¬™vvö bR»%‰Öt)Ím ‰KDÍœ¨j 4'îèìôQ¾v+R\»ô´´j­(®Îa¢Ø"6£ˆ:Ô/ùÉ@&:E3ùÓ$}}¦¥P½ bQ ¦TU–ÉZ`H¨z [±p ¢¶À°”B·@±¸Ò7Air7Vƒ Š+@_jTúJYÐæ-4—x XÝ¿@«tH#(—ÑQ…Ý‹ÌÎ@!—ÑHx‘¨©«´ƒCJϰÒ9ÜG›úÛŠ{úÛ [ú9±̒ѯÀŒ ¸Uó+Ê&ëõ0šz¯ƒhÚ€éi.\QÎ+rfHÇõ&[”Ý‹é&H‹BŠ1è¨.V Jç¡ C9ºý´Ñ²+qåäöà ¢å¢šƒéú[­2…×K´Xì=ŠR.ÍÑ'5þÅv„Ãlæü&g1Èó=¬öSŒ>ô9tÚ‚¹­:„Ù¢ª úÝZ܃ô+NØ`CéŒF â•{ SJ–Œ¸¦"6œŠ_äÇšÃ(huÝÅZ]}‹‡÷›E7xg Áí"ÏB±ãòy…OÝCfBô¾è|YS-Úq4¡«Û²ÃX~«¬¯J¼@§øLuéxT7«êjƒjC±4}被å°»'Ä M8»ÃØëaÍ®d”!W¨$‚¿$ ¬±)ŽQƒH öjà*~ËPiÙª¥ÈÁõµ‡ëõúK‹è(ÖSÍ‹u*nmªgÆŠ%5Û_}9µº%ª»ïÀ½žJÊØ¾ŠIº¶¯‡75}«­¡ue=M'¿áb©mzý¢Ä,ŸkÌí2:àì ¯­PRê¦ËNŸÏ :÷Ú›*ÝÝÞ&B;c;áRM1¨ pŽ·XYD«á“ª–‡}m3)Ö}X6¬^,ج Ø!סvÊ¿”BÑ5ÁºªáTH²NK®´qÝà é"øjŒã4d¶^¨5þ^ªfëaqr¨´f[ÉŽO ™œqᆮW_{„²¾;=IM‘B4.hÐ  §fƨUg ›žôè뤤ž&áù[ਭ²—¦±Po¡s<–$nÛÊ”ôTt* $Ô¸F;ÎÂî›:4'ÍâÕÒtmfA"F5¦mîÅö¨Ó{Í& 8ó6Ü¡³ûƒ”B I…ÙiWƒ~5Žáˆ9À0šWp“k½VáöÊXÌ‚{’­Ðÿ߸:·]ЧsÛ¥;:·)=ÊŽÍ› #îžQmÕvŽ˜*ØnÌ? pæe£fÝ+…iT´•«s–—Eúzúº„²Å Òìë·õ‚4BÑ£¦‰ÔTlÜ!à¢YÙùCáºF4Êæå/÷6 wM;/=#œÎ¥¹u0äÌÖÓѦ.£Y]up^;«Û© §W·k½Þ‘–šß΋¶‰KÉê«“¥ Nýöiu°4’ª¥LœÛIpÚ ´Nzn§ê÷ã&`¯N©­.ÎE` ¡Õ„ 3´=‹ksN¬Ô²Wó¢þÅmè^\lP&ßQvTçNeg§ײÄçS…vጻÉ\½Z®Á¡rχAvß'¼Ýö÷©UKCrr#ÆmÙT×6ÆÁÎéy£¹¢}ªB{É¡Æü`¦†{•~ï`ïÈN[C¢å18¿škѧú—ýhíìê' ]y5“coHÍ^Õº6a1¦Eßh¡BÛA΀‰ZõžÆ3+²OáUY­.ŠxͪǰF?a€sÀ É kÕ˜˜–QÑA%‡½ Â6Û]ŸŽÏjûæá³) Ò5›,‚íéÃíø´÷ X±oÕUüè`7}j@`kãàbg3Ð9ÒµÓë7ôh6V”riÖǯ *Ú‰ [QM-V­­(°¬5kmZCê7;6Ý¢4¼Àý몿¥“ó™úKâ½hW,€O×=<Á­‡)‹–Ûc­[”ÍÚ„5o¶|K`_ß.4–hGº‡¼~ U¦©*=V(ÊBïvoéÔ}êØ]oœ‘L*˜Í! œÅÁl²š‚bo¡—´U2£ƒè>¢Œ )P-¦Ñ—Í0Ã’aÀ; À€Û~°aíÇ£6ؤÚ,~ýZ)-ƒ€"¨¦Æöãï?÷U^·UzpC3²Èù¸«±´7ËðA…OE¸MíÓ4ÿÿ@2•H&Ò¿ èÿßêìÿ¤–¶Í›ÀênÚ„¿[ h-ùÿá‘Ï”]Žw5JuÅw²î÷èÌO¦Ï¯Ø½Ë,[qì+f/{ñ‡åǺÛ+NH­’4ûPÅ‘·2Ÿ˜~y~²ÙŸ].I'2nIš~¹îÈÉlõÌø0·<·Þ~SÚO+s¹ÜQéñòB%¾Ûqÿ>Ò.eª}·é©¿R.¦4?í½ˆZ0ë½ÿ9™©›~ùBß䟑ˆÎôùºB+²²»°9¿Îå&Iºÿ¾{¿ýȽ©²SÏ œjΦSͱè^áÞ 5¥Ò`B¦5RO Ã͸A«âõŽ@¦øÑ`‡ú¦KøGM’ê( ÞÙ!g“!tŠ°Ò©‘(’ÇH89Hž.ç졪ӕOy$é' íIø/B?*óÛãŸ÷ˆâÍ²í³½UÓ*¤lÝéÊg<¢VÎý¸B’r“5‘yÌS>Y‘¯\Xe“eùßüË“ò1ï‡ ­ˆkrY>OoåMûoš¼&R¾ŠsžýM1çË"ç¬÷‡ÿU’ …ÌÕã/ž©zñLFãÛ./Ë^>ןËÁIÊtiPW ^—²Õs·A07M®Ž\«šÐz@:]™ó |¸è€¨®œ{ø I*“á¤xpÞ, TˤlíÜ@NÀãcfûð——@íÜÕ9?d¯zÃÜå¹g±]Ì-˹Oºßð OJ,ÊòÉÊYo~™÷íeÞóÃTsp‘WàO~úß òž ÊkÁÊCÜ¡ `üߊøý–E±g^úÕK¶òδÏÕär¾Kåv1ÐÚ™³&gV;Ô}…^{ä[ðwvàí™à ×›2€Dלû,þΜÕIvgXgÈ “ì•s~]’îβ}­êÅ×ê0ªýÆÈ3¤®ž»ø ÁÅ›lͱG ÐBçêr¹cÞ„‚¾ ÞÔJÒéBáÜW?⼓j¬Þ;å¹JÄwÆ «'—²ùe7¢ ø š}+ܪ÷¾WT ï{Üü΃­{tc’,C¡[Id5¶õeì{•HbØ@â2?{·HâGô[žÙöÈ~-ç‰/¯ӕÿþ¾È"Ÿø2½Csó4ü{iÍR¡¦OÎÎ(ü#Û§¾É›#ÛURÓ‚Ô®Ée˜.cú “+#5=¨ce7þ>½bf’™»*—ã~ÏJ…d]þû×JÒ±ð™R^=•v³Š“A:µ€¥]QÈVôÙ¹r÷ÉWOÙÀ\@€í±<—£Î™á®,âÚ^ X¯vøîb![§‚®@PÆé§öEƒa9×sÙÖ¯ù뎼‘Y ùy/Cé¥Ù%¸üÙÞk@kDwp=œ?»ÞTh|øg½UG÷¬p¿z´®ýÙò™¿‘©Ø>÷@ÿxõT~WÇ@ÇTÇp‡4Ð98Ü9²SRðî‡Ð˜„Q4Ý¿â yCZ‡ŽHœ#mHwàQ´a:Äñ_* ÝõkÄû ØOí©šÍ®È?\#zc £ "šÇè…Ós¿Ä9}òÅéù¦ÚϾ …[?›» jPÿo­éÙéß­¿?¬-ðïx4ïŹ›r9þ}aîJí÷<êSþDú‹‘3P}»üù/_£vÚµGÞîøëPóÇ6}¦áÔ‘·þ¨2Ÿ%è /þ¸ÜŸÿ€žø–G+]mB‚6qï5(Ç—/‚íÎß3¿@Œ[¥ÌµØ>æ  ó– :§* !{núÐ<+´e³Þùá]ù‹<6:tAÊ^ƒ#¦ï¢ü¿¬¡BÁÌU ~À¿-Àç>  ‚Ãù[‰ÑŠÈÇl[N©î· ×µ³÷UAC%ÃTHWíÊ?)²Î^ä-àcÛIždwäŸ!odþ@¡B@ù¼ %ñ̼Ùð΋óå³W“îzLÊTº \*_ä"R­^£/âw€³Ýùo_ DºyCZJ‘êÒ©5™D‚6=ÁI€î>zÞÕ ƒÂ]ØnfVÌ^}HÍŒ^œ9’®P˜9vþyµöð,#yÏOvÌÁwÃùÿTf޼ó – ÌÕ`Z âùP”iBybáfÿá}‘¿º *ÄŽ}é¼ðçãˆàKçŸSû÷È<ﯘJ¢ûj®÷ÚÃËËÐ΢ÜP>ÇþnþAø=’êüdÍîü*4»Þ¨4¿Èò?»Š’Î}…:CH^vçï\³_Ï‹"Lß-Õ~L…)löç¿+1ÎÌ8¼Ùy±}°Í¼s‰G«›Ó•±.½Rˆ2ÛTÇ4ksQ–ìwæªø×#š0Ê``µ;ãUªެ#:KBž!á»qhA Ž[+±êÈF£rF^Gnp%–çØ9°¢ƒ0Ù«£Ë÷¾wød¦çèe{ ‡O‚EžæÚ{f~õ5éâߢÕþùWõÝc G½ï½ëj÷²ØˆE>}¥ZºW.ƒÌ?rŸ<~“ió¡Yè­˜û8Ž]ì­}þ¦ c³ÇÏÔþÍÙã€å£Ýue·–Õ>’ûÀ¶fú×´¯ræÌ\mîø;RûB÷Îò¹òÇßœ½øxùÑu=‡ßÊT>ò^Mö/_Y.qÇÇ!‹.JxÉ<©¸üÀ]dÉ÷À¯9ÈDð×ÿÚ>H‡×<ÐÔÔ´§FÌáª@€‘§né?µ$øøj2FÐ:÷­F3ÞΛ§ñ&<X±mÉÁD*•E§ IËFc!I®f¼8—™>T%e—ŸöVU­#ͬY‡“¶ªáü««‹F'‹ýçìh•/²v° «Qù«ZÅœætå ø…OÏQ[¸83ðι›Ëiúw,]6“=?=_V{x¼yäáå…šÌõh(‘Läz@˜ÿ«Dƒ˜Ã6ýJ÷ò‚4Wžë=Z{ÚùƒwµÏC÷clCUaUˆúɾ Oè#¿¾¥öð ø1s(?{c%¼ùÙ=ù£W~£öðgáKˆ°nŒ1[c+§º~¡>‰-1›å2éx•è7f~Ñ3‹\nvt~võÌè<ïëP¹æ ŸªÛŸX¥¶øš[Ec¢Ýù¡2Ê ¯gæ_pCYÿv9ZÓW.>%ýâ+µGã˜ùjäRðèÍãŒú¦Úç%do³WÎì-eý>Ö~]>C¾™SHð-POèFó¾ü¾ìª2õM_‡·¹ÓË$¡ÏÏv®˜éªÀ¿kgî_1wÛÁžüÜîñ ˜?§Ã|%ãóz´+U´!Zov~¶‡ðö Þ‡Vœöæ%ÕvEÚI«òÃù½ˆè4â)dŸœÝsÅ7úŽovà½ùÞbZí‘—h–uavÏþ¶:Ëxò*?2±˜(Ó1{µ6æR‹ìÖ vkUvÿª–æ§»ª±c]U3]+óÇQw`üFúù&h‹F«N˜ŒQ€ÈÍ9aÓu»ò™ZµÊ+gÞœ«ÎQwJ6'ÿÀC¯ž‚A›ùöh›5R5þQqÓ¶*râfh¯¯9µ"!ÄúËÌÝLk_]ä˜þ4¦?T%J{=óQyÓr+‹=ú¿În>òËìß3¼T5·úxÇ5Ã3W'Åhý,½™n—²oëd{ü¾Ä€ûäô¶‡qlYõ͇IMsÓ/UlŸî²¿šÁQæŠÜlÅ—¼pöžììb]뙄¹š^†UÓÛ^¦!FíéÊ—¹ÏÌŽàñIãæÚñÿ!Ä`ç\Ÿ¦›.ìw6HÒhvaÀú¶–[ þ¤Câúˆáv6—Áû?ƒ‡»àù+Coïú*Ä·Cü7ƒÙr=1ŒQ\¯@\ñw †‰“ëûÃPÙõ?!†"¸þb0X®ŸA ½ž ‡„ -Oüž/‡çß@ -Þud†þеbhÜ®5w@¼bÈ⺠â7AÜ1z)|‡ù‘&–Sªäu»Û8†²¢\©A¢²H7À¿vN¯â¥¿Ë8 2X>äQª•ÄÚÌ+ÕÄ+áBÃr•à•c jå Ú ÿ”çZÆÙļ­cœæù:†½žqÜÌ8\œ÷†ÙÆ0·3Ïë¹,2㼅ˬ–e§ÝÊ8oãÎÿ:ã{€á¿Çôƒü|Ÿ÷òó­Œ/ÍùdþæglÿþÓ{„éý?àç¿d|ÿ×ùù~þ;Æ7ÂøÎrzÓ“ÓC7‹çæôoqþŸòó—ùùç&y–• ~2ŒïŠ2ÿ„*ÿ2_]S¿Ÿ¿wxÞÀð1†oæô·oÏ›9ý0§ŒŸÏós¯JŸåq§ÿ§ãDÏo”ñ·0þOéÒ«à÷þ2ѶIa!Ì2¾§ß“üü2?±LØœw¸¡œPóó¨îi†Ï0ü3LÿÌ âù9~¾kx>Åù+8ÿkü¬ŽÏ2¾Ï3¾åç×ùùóó \ÿ—Ÿæô÷˜ÞÚµâÓðù$×?_`}®-0.Æ·¦\´¿'XÞ7– þ®bþÖ3üU ßÈøžbýópú™Ÿ-œ¿Žó+J7^ÿÑ×%)¡X0“”p*%)‰d&á»D2—pw* àÊD6‚<.„áÑŒÊNMÄM±1ÂK¤Ãn3 Ç÷Q ø$Üy˜IìE€0n8Dd¡„2KŒbJ(“H¥ñU÷ÈOéïó( >véCp*)∠AP €¤P6IDÇã©°(C2gÆ!S&Kì§”L`ŒHàÁµxweŽ#:ÂJÍ||Ï,Ó¼ªÈ¦lÄ cBx*FɯXR€& Azé=UòÀ!¤û©ø W‘‡q3ª„îâÑTk!ƒTh&‘Ðh<:A5OHJ$úL ¸WIOÒ%ˆ£´ P£T‘Š& ¿Cá±ìVJúà@Y…q.RŒã\ g@$iR‘@j‚Ú4tRù;Äðˆ›9ÙáR(…R(} ñ?Îa–°ÙâMîã? °!øwú´ga`ýµ â=Žýs0€Þ‰“ YŒµpœš;÷n¿Ãåò?çø§—Aü#ˆË!þÄË ~âå¿qÄ/A\ñ7!¾⿆xÄñÿ9Ä+!þSˆk!~â:ˆB¼ âÇ ^ ñ§!¾âÄWA< ñˆÇ ¾âO@¼bÄ×Büqˆ¯ƒxÄ×CÜñ { õV¼y‡¿»CÆMA×>ÙÝ´Eö´´llnñ4{Úew{GKkG«d BÙ{ )ߊs߯ÁØa{ý- úë‚x½B{› /YØH7ÕT£ƒ/<©§ÂÆ£t Tæ ¾é Ä¢ã‰T<h’í¯\rö¡È?wª\„v,‚‹àÞ,{Üž¶ŽMå©h z-µº²SÇY7rÖ-Íî6(@ÇÆ¶Ž[L¥—{Šs‡R(…R(…R(…R(…R(…R(…R(…R(…R(…R(…R(…R(…R(…Rø`Ãÿxí'˜0boxbackup/test/bbackupd/testfiles/bbackupd-temploc.conf0000664000175000017500000000211511071520167024130 0ustar siretartsiretart CertificateFile = testfiles/clientCerts.pem PrivateKeyFile = testfiles/clientPrivKey.pem TrustedCAsFile = testfiles/clientTrustedCAs.pem KeysFile = testfiles/bbackupd.keys DataDirectory = testfiles/bbackupd-data StoreHostname = localhost StorePort = 22011 AccountNumber = 0x01234567 UpdateStoreInterval = 3 MinimumFileAge = 4 MaxUploadWait = 24 FileTrackingSizeThreshold = 1024 DiffingUploadSizeThreshold = 1024 MaximumDiffingTime = 3 KeepAliveTime = 1 ExtendedLogging = no ExtendedLogFile = testfiles/bbackupd.log CommandSocket = testfiles/bbackupd.sock Server { PidFile = testfiles/bbackupd.pid } BackupLocations { Test1 { Path = testfiles/TestDir1 ExcludeFile = testfiles/TestDir1/excluded_1 ExcludeFile = testfiles/TestDir1/excluded_2 ExcludeFilesRegex = \.excludethis$ ExcludeFilesRegex = EXCLUDE AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis ExcludeDir = testfiles/TestDir1/exclude_dir ExcludeDir = testfiles/TestDir1/exclude_dir_2 ExcludeDirsRegex = not_this_dir AlwaysIncludeDirsRegex = ALWAYSINCLUDE } Test2 { Path = testfiles/TestDir2 } } boxbackup/test/bbackupd/testfiles/clientTrustedCAs.pem0000664000175000017500000000112710347400657024000 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt SlL3iQzVXlF6NAhkzS54fQ== -----END CERTIFICATE----- boxbackup/test/bbackupd/testfiles/bbackupd.conf.in0000664000175000017500000000227710775523641023117 0ustar siretartsiretart CertificateFile = testfiles/clientCerts.pem PrivateKeyFile = testfiles/clientPrivKey.pem TrustedCAsFile = testfiles/clientTrustedCAs.pem KeysFile = testfiles/bbackupd.keys DataDirectory = testfiles/bbackupd-data StoreHostname = localhost StorePort = 22011 AccountNumber = 0x01234567 UpdateStoreInterval = 3 MinimumFileAge = 4 MaxUploadWait = 24 DeleteRedundantLocationsAfter = 10 FileTrackingSizeThreshold = 1024 DiffingUploadSizeThreshold = 1024 MaximumDiffingTime = 3 KeepAliveTime = 1 ExtendedLogging = no ExtendedLogFile = testfiles/bbackupd.log CommandSocket = testfiles/bbackupd.sock NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl Server { PidFile = testfiles/bbackupd.pid } BackupLocations { Test1 { Path = testfiles/TestDir1 ExcludeFile = testfiles/TestDir1/excluded_1 ExcludeFile = testfiles/TestDir1/excluded_2 ExcludeFilesRegex = \.excludethis$ ExcludeFilesRegex = EXCLUDE AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis ExcludeDir = testfiles/TestDir1/exclude_dir ExcludeDir = testfiles/TestDir1/exclude_dir_2 ExcludeDirsRegex = not_this_dir AlwaysIncludeDirsRegex = ALWAYSINCLUDE } } boxbackup/test/bbackupd/testfiles/test_base.tgz0000664000175000017500000003514610347363533022565 0ustar siretartsiretart‹o%Ã?ìÛT[º(`<@î ni ‘ÆÁ!@pH 84Nãîî®Á=Hpww‡`» r“Ì›;sNæÍ™yëœ3ï½›õêê®êU»Výkï]ýUýŒ0|l@èëÈÌþýò¯äà`²³°°±³~[ÏÁ d‡ý Çckm£mõµI=³ú»ßÚþÿh02ÙèYÛ0ÿ¡m™@v6–’зü³A¬¬,,Ì_×3YY¾®ûCêÅÿðüË>Êú ~Æ,þÒÿYþÐ6€Ì,ÿÊøÿ—þÏÆþ­ÿ3`Xþø¡é|ÿgdRüz’…¬þÀóüïÏÿÌ@6ÐÏùÿOˆ¿åŸIŸ™¬mó´ñmþç`cûßäŸù¯ùge±²1Y¾÷ŽŸóÿŸ}·<C8¡8܃„dç¯Óà*aذ2ŒØö­šÏTŸa‰N#L¿^x3•ÛÚËv}b¤ÔO.: €…OÇ7x1ŽÁwøèÙe:ôHá0à^[­š'‡ì0j/XOä½À+¤SF™š“Ý<:9» ïçv@GØè¸ÈÕ¾b œ†"¢Fûønù. Êç´•Ï!¡q¡\ ëvòfðçXIµãœË)¼ô~í5OiîÔ°ñålÛ`ÍR¹'%ÓT³Òí'9ªºie­ZÄí-¹gYÍ¢]¥ðš4).ï­‰XÔÐe­á,Æ(\š»òfQÝåI©s졯æç¢hm­á•£ ¯Êp)T‚Ñêúªª5´pÅt›6.Zå7åZf¸g%§öŸS7b¢¸m:]±¨}v#H~T¾å}wÇÞZˆ@!r ”OÅåÅ;é˜9̻Ʉ|akRy>Qƒ®³º–! ¦xŒþ¡ÇïÆæxLª,s¡õUnQþ~U<ßJÕrû‘·=ë%z´HZÙÍ#ÕÅ=Ç,){X[±-"ûÕüªè->_tKºãN:ç2ò9cb}ƒóSáîÛ“\‚¡ìÁt®÷Ëúìj:þμ'´õ³!i³Ópê´n}²¯\!¡ÀTWø{2íîNˆënÅ}:uÂ3‰Uà_ÄF–z A9ÞÔ :Øšª´¹Yè¦cz;„£mäM„edŸÞYÒÕXû=V¦ÌjÈ©Å÷*žØ#º·Í²;m/3‘¸î²ª‹´8о6[Ïèñ~ÀnF»ÔG!b›g!„S¿ØËb ûA†·Å- 9Gr¢Bˆ›ý bô €Ýè[£DÒ L­ºÜàÐ2^ÂÎÿRW&]¹]h˜ÃNõ™HS_£æ¤öªY_¤ÅÌK–Ëäi­4ƒÑj¤?"ŽZ¢E¥lM®Ž†¢¨yø, ²ÍÒßLm„ÄQ‚÷´N£¥¯§•¨_nÌB§A^ü…«v>œ)nœiô×÷ðe«b×Ú0%2áîqtïð³ë.”p?M"½W,¶ÍSމ1ÖóxÙËñ²±K]ÙôV•˜Ôç :öøíÙõÉøø,Uë_‘Ï ˜õ6Û|sj«z˜Ux9ÃÝSÂâ)ÞÕÅþệ/öéަŠß´Jš?u|Ä–¬)û\þ ®‹~ƒ==Ì>"2ùÆôªø•²!Ò8õlo^âCBìDê‡øëAÏûÝ¯ÎµÄ —h£Oiùa#d•§ÀµŽo_œÌ_Ê¥Ãø£¼Z¢\´î9ÂV¼`¨è{Þ[9]±Á‹p§Ò6()lØïß`©B¬%ÑƒŠ®[Cêˆx­ë~2œB§h0Þº¥ÞcSaÐAºn¡ë3G•läQ^U¾Œ;Ìsò-Ýå#êÔ6 öa+xì3 Õq?»…›Rº›Gk Rß2«&_™òOó¦=$nÜ"(kÝ®¦{8’Ô!‘ÆUwe™†ç½…üÊsî¤÷9a;’&·y¬®.5òA‡SI~ }ú“Wº vd’ÑW5*t„Ì÷h¤­Åó„$Ü Tf1Ñï>CCOÉ;®¤˜b·2l ŠëÜ šü$b”IùQ}ט!åoÞÜâÅÇÌRvjí®KTz¬¬_÷m,Ì2[¡ƒA×KpŸ.lŒ×@$Ì¥kQBR¸õû.(eSÉùÁ© žYB FM,&"žd€Ç-EζÎÎö¡»Òí!I75ƒç‚3wºAÓúHf&šÊ)ïŒh6â´|vΗ©?­¨;䩘žh†Øà-J8ßñAÉ0Æ-%$·•ÊÈu.šF $‹àî^f>;¹SlÒ†wd]çÔM¨=µ¾÷2V`¢óàk\]…W˜ræð³%…l˜ÿV@ýòë,sZû#ðZÔ2ŽõÌ øÑ»&N‰×˜¼øç™sØÔáN±ç<³ªUðXèXœ…”hÛDzý°{¯…Îׇµ‘#TùŒa(MaCîHÏx©¢.Ç„Úadƒävï‘[É9àYÃb?ìô†ˆ£Îî>-d¯$C¥6-¹f‰¡³á†ÎecÄ=ÿ¼‚DOmu,OÿP‡®@Ý€ü~kp@ø~Ÿ6’v˜ mo¹(·Ð}ëùb²TóÚ û1J“ZÚç|,ž@‹ß #ÖRa›<ø,žb“ìb•ãXß>VÐ>>Ê.šäæG)ײ)lÕ˜“4Óþ™²‡zba#>œŽ>6V÷*vìŒÊ'7ý{ð Rôñ8µ D³C¥)úq ÐLŽõCŸð”jà€S— ±*¶“Ì‘ÆÞäÜVÿüjp[næÑH uWy»—ócÁ7âHHëbÐuÅ-"yíb‹×Ÿ¢’WŸ¯‹>aI5Ë!u{>~gW2e¼y!º´|ù… eš¾CÕÕµp–Ò7å`k}Ÿ¸¹æ®¬hþ \Ž ‘žÈ_ã(EÄöRÑunŃæú~$SÆpˆ{´¼µyl /JƱ‘ámJ±"¡j‘%Dpñv½~‡Ýñm‡íuÚÌÏ®>mÇ8K>[Cøs?+¶:ï¾ûV[1¢oéô~°/æhº†_Ù š\„+yO)6¾Ôã]ÿ@èó糦+ýƒò‚½…ºÃìÈ̃Yb5Rܼ …¬˜Y8HxEŒ"š)oXC;V@Ñ™_p\ߊ>¹IãÛÕâ·cRÑ^„¢´Ë–éf ˆ?~@Ûr—7òÖcO‡ì²¬©\c»iÛ¯êðì£/²‹Ï0Qz9ˆ 9^¤U@')·äÛØYiïƒ@Ìz5YEá<4鳉¯©T¥Rû¼—›@lèdû£ZãXWa-sÿóq~¨<¼ÊËŒ™’ûÑÎêórsyO¬ÚúÊ„ÜF ½‹œc¢ qÜôL-úõÇÔwY'{.A ¯jlÝÀÏÍfM‚6½&ûµ(;n7& 2ð~Ìc´O]ùöOÃ|2 ¼Ù…´‚eˆ]H™Šð4e‡.ë±kÈO¯Œ»ð2‹„´{5U„Úž—™UøÍÆñM\˜,¬j-H"¶Œú×˜Ž…\׿à¦F#=ÝÑÊÕšžàW¿É t"¿w…žfãŒmêèCÖlL㟒šœ½ÝÀÅÌT»~›Ü¤šd™™žw»(¥ÔhTZ0ÎÑÙU˜A‹âIœÍŒ*4ŽAœ×Á¼}|¨{¯A¼ÿP^}FOWÿ˜¼r*ø t:„—Lçe‘ —G… R¶k—[íf‡2Ïòxþ%wUû¤ómßûù~~Äé"´âËW‰™ôÑP8Á3|Ñâ\ÅÇG¤EÁ%‹| h®|ÑÎ J3 "ï7â9D2&¨h¢ß÷‘lUÝ$¥”¨/óî1aIŸ‰§ˆ4tרëÁâHÄÐÇ9Ðô âsÑ.Ö7g§Èš5€p%´~Çïöp‡p»k)FËU«Ò!P—›Á; Zˆà‹›@öFCh¾ä¡\žHæ9E"äÇZ¹áW6©zV†+éZb†f°Å-Cu*FÃZõq àvTGeï$1˜çL/þ†ÜBðå¡å=êš>’Ï—mX(YîE™f69ïæ\9†©ÓÒ!5Ód˜œâoÌ=½¾œ&®"™¸äL&‡šuè}nÁ/öÏÐ{+u½£\JõÈU--ϼյö,[ò­ä¡«–/8~ÓØ÷#I¡ê‹9Ù5 ÁwÜ«pK۽éšÒÀæíÇÝlDÖ1ꦅ[ìMJa©¸_8Ãí2´…&2çWDÍ5fû²~jˆÐ Ïl{x¼’©ÐGôÀ° Îp–ÃòîÝäkM¾ƒÂÚÍà ®|;Â^* :Õš¸óµ&ÁK1¬z5Ú i4^aƒõzqe¼;Fþë>LO¹ZUc Þëà—SöÄŠ=7Ä€1×Ì)Ä>îèŠÄâÂÂÎ'¨>žz6ÉSS/cêÐB\žycI¡R2=©DQÊFxZ¡ ýé>ƒMæ¡åÓ•´Û~]›>ԣܪf%™.ŠòÖšZ>:‹3µ ••ËhãoF¬Ú*; }”¾zÐJû.ôîÊÂ%–›­ŠcE_à1N2™œœÔ&%ZõÎɼkµF+|©2óMg[ ŒË„¯-ì>õh±y|‹î‹Z(©ã 2U¹Ó;LÖ>ï\µëûÂ)Eêù &dvlHwýFDÓ{v|jUØþ~Kb¼‘ùä„sáÓgGÙÐÖÆNóåNEô]—`Xb¹¨)Þ­hôÎûu®ücóOíýÕ¹ç\3‘¹OÂ!ê¼”,¬Ì@O¡i„ Ø„ñK™¡C§«–ÓˆÔ Ìë€Ç±Ý!¢«‘õs‚O ¦òÓ½HÜ;06‹Y퉆¾¸´–^/¤¶¶Ü²7¢uÑß§0îõq]a®ÑFd4V>qB$xR/í¾ëcvA˜nÆ%~m“fÅ5p@Ø¥R)ÉôÐñ%g´ßÂå¦u17/!Aøä#–¾eµx{¹{bµ9E\0“ã"dx,fqdf{íÝÕ!Ç"¥x'?I&¬ÀØ‘A>ÑxÎKŽÒC8Ö]N9¿C1­ ¨4ì›Ço]P&¹öž¦ØmªÏ·´Þížæ`ÃTˆ©z”ƒðzI,»è¾Ôª_T’r‘0­©Ò Õúkúj&S¦»Í™ÂE—FØ&ÿ ¨i ªk<›O†ÒxÄw¿‘o ÓL©t„ÌÖ[+eÿa¯ÒËaj~xÒW0ù啇s6ý Q¨I'¤J NÒÞÌFK¬WrŸÝWc¡æãŒÕT¯+ä&?º· zú %ÁwØ.çûâ…ÛO)m•¡}¡°^ðTá}$¯„Ç¥)QX°k^·³ì½?Ø´Gt^#ˆZ$¿þ´£Î[“$ ¬ÿ@JÅv•ðCôz¼ü»8ßÅ7V²“ÞV× #Žæ¢Z˜kŸtÍ:Cžü´ìV¡£·ýâ/4tÓ•\÷ =à™ßúz#À”ˆ;¼¬C§w”ð\ —ÒÐV4.­Ž¼€Î!¤·¨lÎçó¯©ä£ ž÷Uè«Âjp@úô§–³DîPØÃž>ßQ¾†yqsQ§çú°ÁÎ쨊€äU ËšTÔì‘üxÌ÷¦dsVßP¯»ålöê„Ïx[Ĩk¯!5.Ç“þŠ"^b–aæ9YïHj½ªüj  Ô÷)¿¦¨'¶^œX„*9%èM©a_Cg-tF>Çw’5IÈ4|è~Sá}uöº8wn*lR}ÉM¯•@nXØT½$j ϲŽZÑã¹ äâK!X «ùò†Š¢Š·ÉÔ8)S¾òëq‹°.ü¾$uáR'Ï÷ª®bç?þ‹þÇÆÁ daýÅX8Xúߟ¿È?ØZÿëËš‹“Qüûµñ[þÇý·ÿ}ýú-ÿ¬lÌ?ýïOˆÐœÐù.L®’ƒšÄ]ÔÌd…¢½ìèè_È`¦­}†£Býã)mk⸻eÅ#ôR`?¥å‰E- ¬aÈòÔ°ìÜå7¤ Öø‰mP=XâD*ÈŒëGc×i„w+¯üP:ŽG>¨ø!LÐf wôìú2$9&{?dX„ÇV.ÕÉ|•O±ð‘ Ý2ØœT[çÉ”57— Êʧ¡ÁºÁÂsÔE¬’(ôÜ1ìuèý(ƒ™( ÝÌÁ›w׈–º¡ƒÕʃ-ªJî²ø?Ê`XívÁ(*û`”ƒ>µFŒv^Í >4yNÝ(ÿ…qÜ ­¹‰óñKÃVô6’ä¦àèÏEùû85‹+«9d‚ /ðÅi" ¢™ß”AK9HøÌÎP“ª¤"PÌúæú<°[Êqô,M¥;›Ò+o@à,ãdH9S–”‰òÅ:ª€oÉ2ˆ³8¡gÍÕûg¢„`#Ù´z%¿ Ý$õÕ#çèuàá¬ßë“•‚e”ËÓ¹ƒÑtšbŽ$´õ>¡ŽTG;}åüÔÐQd÷Ì凤‰±UÛ2ˆý™Ï Û ×!)œ8ÚW幑w2Ópø¨žXâ׿-ƒZý‰ý£0ø>~”¦q‡Ã>‰/Ó³aòˆy„ªdž ì»4~¸'N&ûyâÝt£ BŽÎq®OžÔþ¦°9wmмÖiôÀÍ¥„ áúü)%6q$Æ2ÃÃÏa©ðcBßdp¬²ò*‡ŸØiŠxIÊ0y°c~÷j!ÝGâûGü¤"µ|OCâxú™fR>?8$*…¬ÊY'LtÞýß”Áf(ú6ú½õµ%°ƒÞil"š%›Ð^}“A†q-màpÞ‚¡‡Ñæ¡2+ª¡V$ ií]ã -ñeä'ÜIoÜö]àƒ»Xu-Ei§•–eP¼ê²ÐÛ\A–.´î´›õI«#Ú 6k7¼GÂÙ7d#SFÙ+åR÷ ª…Ã(¦÷Ô¿MÆ#äYZÖà§ûÜÈxuဠü.ƒ¡ O}³Ä7è|D­y1ã‘âÛ:Å·IšPó|dc9ô–j]..\²>Ö7ñŽùN‘÷6¤±ÿm<#5I]C?¢Ÿ_ß̾¸ª>¼ö#$V~¼r_8‘D]ÑØ “Ýœ©â|œõÂ*}‰{œ´]? ¹÷ÖæÐmŃò–{FgŸö·dðܤ›UQQ˜“N½­ÊïÕ„3·…ylØaQ·èRtб!ƒ¤„Gn‘>Ìß2¨ÒÔÑ™:”¿ã%‹&r³:å7æZäŠô]×fl]Þu‡ØzVu2°²¹Ö¥ HZ£ŸBÚ~K÷S7T¦{38¦£vŒ´š-½[»ÒŒîo—¥)Ã%ÍÚZÀÉlO´Èš_… Ç„çc¢Tqìö¾·å‰‹UÝmž§úÀ…%¨ñNs¨!<[q³4‹D@_4å» >(BÄDÆ¢¼sUê:ÂëÔ¬ê‘E—¤Î/´!šµjär‘¸þrm§¨ÝønþÎTží2¦ ót¿ÿ› "¬—³pÒ¡Öù\,z+P’´LÍŽ‚çÒ£¸ßvÏGt$sXwiSÛ!®ôÀT0BÛ¡¼t{Ÿ6òýF­GD2CþûL'LÚ¦Šœ~6ãf†ï2h3Z„à$ý®3¬ ê¥}›úFb±Ëí31g0<ßeýDì nwW3/":9{µ›»³j06ÿ}Oï§C[<ÂSݳ~DëfÊ'RŸ‡ÕÓ¸w+¤èL9«ª©¤Qw¬ì} mb^†&‘ÇÄF ûZO¥áPTºôÃá>D>öŒšñ ŠÛÑ{¹êå(K½.UÜ,"H/øÞO°œ‘¦D›"Ýâñ´JYo¢`„ŽêÉD¼Oç±ZÊ2øþi·t$ZNž=‘×HqrIrÚ‚ÖŒ{¤t&\ºà´Í`¾Ö¬R‘ ¾2ëÈÇcQqzÃÐ ®9óB-Ð’KË*ΫÐZW„ÇPl«Þo'Ž0|¹;òáqÏs¸,¤ŽEξMè,"r{Ѩè2(Õë÷$^‡›w ZâAt˜ãƶGÿ¥>›²É»¢5,Ì!Ûwç$b}KöŒYy½3qƤùrÒp5p}”8âõzñk»Ë._«‘sô4ãéawŸŒJNÔ]ø®âLiÆÐg'ûK‡Shh–Ÿ3-ƒJ•žzAIÕ:IÉªæ” ï÷ú1äL8ñ† Ì b÷[ ²Ÿ!ZC†%’ÍBï…>q:M7ëêÓÁµ5"D‰‰Ý+*½Ý[Ávšû´ÉÝ„Ëc'f¥ÕU%ïÔ0Q§jLÈnXCó´ÊJêD¸ðäö6ñ¥êNË2ÈD§Û<ñºìñX]x;Ž5,r\Þiè—5YûEN¥Nb#¶‘6¤éIÉæŽŽéEåÒ½ %%nòúdЗ¾÷º+Ò¨ûÑm5Û™ÂXÏì…G\M¤¯“<ˆ9Û¯Ñóâs£î.ý©#à%¯š“€±ÕªMÅwÒÖ?|ßx£é‹[¯Sáw·ï0åü`Õò7´Ö¡ÎfX—¦õ…ËþÀÒ&w\tŨP ŸÅ¦Ù‰O\[{7†IóÆÿÞ7Ô™XרˆyX¼öÈØ©âŽUˆlaÏü$X™÷ƒªØÔìÙšAÑÈ Î?—AcŽ…èÌû±ÝM9‡Zˆw>0) ¬pVHè|xFkÄQù0 ·\5CØ™SŽ“·}±³¢wëpOzÀ§!‡ŸãÅñ²£WÜâW28æîæš›€²˜¬³§:âæ@‰»5r^žù Ë À¯¿ñ©xWøMÙ{ÚX ³n2µ#Ñ2½×gºÿøüBï@–À¸4Ãê^Sô®|Rãåª"É2ØðÂÙbm¸ s±¸4@‹“ýeÓYvÍç5ç2xË=‘,–q³(q;$ÔÅÄê{ ¢¤Ô–Vb 6˜4S°ðoUQ¦EíýQIðT¨{¹ëŸ®Žyó¿äÆŠ 4I<>X?Eƒðkä@DÏ=Ù¶nB‹»4™_¼ò|4ôšúˆ³Ò%nødÅtš5·¨F»1€J¬ _¹ŠJyp‡ý²Î*ütl!‘í7ePQÉ”®ƒ0>L„ª ¦ã7+ªÞËu›“¸(±¨Ñþ• Æ¡ Ç[ml†&.\Xë¯Þì}ªdÛa7ÌÆ‰‚Ο;¥Û@ªM ƒ?qö«·ïÜΙãw;ô/§Ì ´¿2||8±‰a’z b!¢ƒÊ)%’FaZyåèù¼ Y}ÞY8—9çiñ¶3$’YXEË[LößdÎGÇÇ ’‚Ë%µ_zÄ[?ñÜ>“û†DÉY¨iMOƒ{þ9¼ˆœ½°ŸÆ[:wüLx5âРVÖì+ƒÓ×Ô~Þ¿ ØÕ„Îrð’ÆÇù™ŽFYšKZÅ@âtŠ£”—Bÿž : ò–Iº™ª¹‰ ]Öd épç'ER/*å )ÃLX¬ÙzÉÁ%æ¢ø?a‚{þP×§àkÇ©øêýí¿U³ÖB‹} !27:óÕ*‚õõ×;ÒÕzܺ牽7âûŸøTE¥Ñ2çævU9Sx'Izs&»eƒWÄ)• ¾ðû§jï.<Ÿ”à%”\¹Uõñ§áŸ€’žI—•E™w±¤íô&Î9•Ãú7kàÅ~ªf~°­’ˆÈðI\=Å9Io<&™cba󜸇I^ƒVïÎÑÍÁëãRvÁú¦hqù‹‰”ÿ͇ŠƒóLX¬ŽñæÍx‹2“ì«Uœªh ` ôQwE<éë­Æí[üAÖW—c’p)¤ðr•Œ€ã)tÈÏÛO…#%G͘¤¢ÎÑYæŽì¬;h%¿vA{!/O5L~ú‚+¸BÀK+dT7§!~8uETeÕ9i–`A‘²pöÂMÀ“)1£,­„ ÁÊÀ‡-õëÁäÜÄ&àkl¢À¥—b¼2–ýþÓXòÿaüÒÿtôtí~ï6þ-ÿeÿî, Ÿþû§Ä?Ê?£…•¹¾‘©ÞïÔÆ?÷_ Èñ-ÿ@ÄÁñÝÿÙX™Yú”2zf‚ Âܤ`s›¿f`GÊÌÈJʲ0Ù™€\¤@vnfÐש9L*â`AJ‰BBAjmÈdbmHjdfdc¤mjä¤mcdn†‚"+ ø‚—ò…Œ”“Ž‘÷_Þ¬¿¿ÛZ[1ý÷‡¿­25×Õ6eúÕ׿m6ІèYs3¢è9X˜[Ù~Û?é·Ý“*ŠÈKý¬`ù?¿ëÿÖŽS#3“ßÿ.à¿ZÿÁÌÂÎÊÊ ú~ÿ‡…†¬ÿuù»Î/ãxÿÿ»üÿagû7ƶ¯Ãý÷ü³€˜Ùßæ 33ˆåçøÿ'ÄÏçÿ>ÿÿóùÿßóù°¾µ Ølm¨o 6Ö7ÿ¬øYð³àg=Àÿýõÿék‘ŸñçÇ/®ÿ¹8YÙÁÖú¿sÿüúŸ…í—õ¿ßëÿYØX~ÖÿþAŒÕÖÞßVU8JŠ"æžE£Ñ>Ï[I ib:S*%^D7½È‰õ¸ÖMJ¶(º!…Úw_@šÒ±áøÚâ<ê’üqfwÕÄÔµH‹òëvÚ”=­¹õ¶kl…OŽ |]v¤…î ªÒ_Ԍ̄Ø%Òñí÷d»¨R/ó.kÀ~”ˆ^­´ËH¸°“YŒ!×ð“]Õý_íUP] w†q ¸»»»C°Á]ÁÝ !¸îîîîînÁá`mÿ¦Ó™v¦2_;Óö<·ûb_¬›½ßyÖ»š¬EûK¬âßïóÊž>> \¥Ò¿ì;ˆùE0×hÍ#a—.pÌ–Wói SSlfòÏ ¦ös˜Y`VMz86Ö7;¯ZGsÌÿÑÖz€3E?³ÆPþù±Ðì‰7…­µ8ñÖøÌ…Ù°‘ËV ½g=T0eŸH¯D ›?Äëx“Žn#Ô¶»«kÇèdexsªë…2ÿ®Î`Ña‘ž„µA¥Eç¶_”x¯Lÿ‘Hý6pÆ)oj(H¦F YÁqL2¬s¢ lˆæ”5Y€6ÛnÏ?õ(.Q­%p͘GÀ…¶§Ü:¿*³“‚¾Ô(íµmýYÇ/-ZÿSÙ¨u“« ±dÅÏc-cÒhÌóËú”2Œcà…zÜs(@æ…ZReê}Sø³Q¾C ×ã zó"¥Ô­þ˜}ŠÙú‰cµþXatÉŠä8âEÐþ#ôWŸ² PâÈ#‹cAméþ8XÄþÌþÈ×s=¿Áòà”±av|VMO»”IÎVñû1ŒÏlÉŒúéfcÐÿø¿Ûi®\óÂËÅâÍ»K~“ÀõòU,¥Ì ØÝ¼-Šß$,PTøÔ–ûiñÜ=ùξ‡÷‹²·à¯(V:ne"yWô‡Ú®A ¤ !\¶èG'–Ò½Vf‚ñ{.$yµrTUºéÜz~ü9‚õžÄ±jòÛÓü °pÏêV¨Êàp®Åž^­““mw wíä§E=Ú‰ë6$]Q< —ÉìáÜTæ§mLmhÉv­cû¦}ñ«˜:Cá·Æ®œzLYæ3îxnèR(édÄ>ÙôŸQvA4 h^ ¾*I´Y܋ꢈnôaà˜êµPÄMÉçªýœ™;ÊÂÇúm½…õâ¬ïÓãÍŠbùö^‚É4ñ„×-Ïb68ìÈEÛ]5>¤:ÞïsË(Õû.è?löJŽRºæ/æpC¹9Exõ3R'©Ø¿qµPÚÜòS¿£’|\0AG‰ìØZ«ŠôPÊÇÈ9ÑøÔùªv!åg õPGóªóiR‹;ÄXýšŽb[0å#¾˜“”Ô¥V¨`ý%…N—jçw¾gK°Äü•[t ¥²«Ñ:<͹{søűÆv3ˆ¹=€#:Óx‚Žàņ+úe|µ˜wõèD }”Ž´PápËúN›º¤«úq3×Ù“Q*“Ñû˜doÿü+5 ÊÐF,ât'ÙøûðÔ«‡o¤m=?÷P©ÊŒ)%%SÑ#áåþ{ѽL5“SÏ{H‰)¤=Æ3¼-ü)fæ»?&(焽qÛ&â‹<ÒnõRÀ¢‰®ªQÀ鲞ŽÑFD*ïBÉm½t ÇQÐ¥’-†ž®r½"©Ñc{~šS‡ÑœHˆ˜lý@lýyÍ7 A‘Z µRqÔ¯ÑÖPLý¢H<ÄŠ»¡X*÷5%“ß³:aìÙ}Bîé@âÉÁŠéàªb´=Bºï-©#ælJÛåV¹€l@"ËKW#‡ÛE OžJLt÷c8Ê"õØuM7†ëÆþŠí52I\soåÐ bQ@TÏ<ß`1÷oßÞ&øÆ ™<ÓÔ]³.Íâîß`Lú{âႸN¥Ü¹÷# Þ…Žˆ‰–âuY•éÈÓŸ˜jÓ§‡E`¦K¦:Ü9°¯0Iª5÷µä}ØÿRýƒ…ø8T%@c©Ó…ýƒ¤*÷ÖÍðF²¸0áyú–ªðÚl¹ÇÁ+ù k}õBÅtçE§¹ý·ðnšæqÑ÷+”î¿ ‹Šõû¿`nj³8u4ÔÍ¢žºº½#YVo`[¹Í£ðÐÍìè`ÃÈ|Üß`æ Éöb‹XXÕ˜†`úX^à]Dˆv‘Øßhù’ˆ¬¨Ì·eZX\úî1ØyV:à¶ú`: ˆí—¾¿xþn1Ô?ŒÍIœ»¨ ˜–øq=Hûž0f®QAÒS|ÏN¼ñBÐ Ë„}&ÓàÑ…èÃSÀð=!#øÆ—ÚãÞ?\‰Å®„¼¯MÝ…ìk:Rë6ŠsðþÖ/òž¬—º)‹Ðì¤XÅ¿gùCò?HþÉÿ ý¼’÷Aò¾ÿóyß?õ?#‡%Ï—yíóÜd#¾ŒOÒuš Wýôø–Êe¸ÊÁ~µf¶JxxŠ  ¥»Øû†"À§z?O¢ÆÏ^LÄŒ¢0ܽµ¾­¤a’êÝÏOUg¼ŠÆ“²µg¹¾ qH úEû‡é´í„}¾k¨É—àvÀ•ç „&(s·ïr]ÙðDœë3ŸèI­q-«Ú_©Á×ĺ^Fc­ƒÓù)!l€²ä³¬÷˜hQhŽ&»3Ï-ÈPîÎEZåi¸Òÿa+_³j@xJ¥6ÕÿÊ]ð!¡ jpä:UïQû ›ÖLÌ î¦~Øe×vãÏTuì@WBoL¹“Ûnׯ&F|éf™„-™ U*Â<ÍÕØáŽ%øôÂ¥âbóÅ\¼¤20Sl2å˜ctÇö œ€\è˜(G»õ¸Ê¡Ñæ,Nð3P ðêÞüší wQÞü‘ÝTD¥YG_&>Ð]ˆ‰Mf#©‡Ù©À™€)¹XÖvˆ¤îÀ„sÊ9!Žà1ð`/…®íY¶{"ê(÷“8¿—qC®wþ9êœ3CD¾tííÀù®QH"ß¹OêCØ2ômi¯¼s†Ú´Ò¸—ajcѶGžŽ£j –×9–=•^[þx‘­÷ +…l°ö+[„x'`ãCÔǸžòÕ}ª@oÓ—ýÓ6ŸÏë}¶Ž‚çƒ-š+Lfìb]eGå8ËŠŽ‹ž{òeâ)?*|¥Vúð]ÃC[»Ç ›0n?Íí[_Dð »wÓ^æiQÊä Æßf¹äÞB:ßúR²iéù?O]0‹’„1üŠÓÃ`¹bPÙSÁþö§ß‘×±Geï—Q°G¦Êo¨JÖ„³ÝŸ†Aà­ü4¾Ô–Yž$Z8‰'E"žv¥ÇTܺ]ÇõÉnļc¼ð¶ß&m€¸ðÎ\ÀÚ#©(+öÒn¨—ò À<¥È¸dm¥WïK·Ÿ Úº®¾ºmü4 )Ô!ˆ»Èx½¯ñ3”µØq×ÒhËâ.9E¡–û»ž›ËÌ4P»æ#ñã™ÖžEvm¼t¬º&`!ŽÔCÈqãZŠÌS÷`i«—•|øC™›=R“?!ïHÕóÜõⵕ^Cùý)ò½hQoWçh‹%<Õ—Î%,\ ž,c_è$Ì lëbI (=Ø× >ÏKE;}¹ÒñÙ9<ÖS|= ½:»Rü•C–™Ó[oYÁ1+w<»÷Ði©Nå½ôC‘á ÛòÒ6GqƒAÙC™üå¸ÄP•H‹±'_jÈiÀúy,Ö÷½ÆE«QÒq j²§Ït•äL(~ßÏÈã6öàÞÛWÎ>'WQxDÖ®X3»ET¬<³˜ä…*]NÛ„„,!êMûéÐ#&ç ûù; Ó,¶ôRAÅŠkk4p„掔LSZ‘šL«N¥‘ÐŽþ›†`¿77 „&±KJJŽ”eOá(Q[à÷™š@Ùù'._”Xyr£( ¯ìžòºi·©Äîx‡‰%—çñëÇ8|lü ™”N•N^T~‘k±‘9°ŽVƒXlÛÔ1>¾2–*e‰Ç¼m•Ž”6 ¶Là ûZ¡oXi»ÚÈùÀM°ª¾ÒY?¹{<(` â?Åí:•ÛÛ”·X”ßjÀ'ú-bLG¯‚že)óò7Y¸½^ñ¸ W_4T°°my+½tŠAMfTÙv„ƒ!þbr. Ïß Û’7*<ŸÞÊúâ®ñ÷6öˆ;4 úxB ñ†1¦LHéOYlâaNÔ"ÆQŽ£øÄè€C* ¯Æáž„j¨}i—ùrùj‹y8%<ŠåÞÝsð‘‰X„ì&Ü´T{FK`s0¬“n´j‘Ùì1® ø™² 2ì^ӃƢU ßKu=™Rìâ£Ù`¢öÛLÀ­‰oÖ{ºŒ=­¤~Áï.&2Ѝ1û;¾Ȇ!’nmii×›Wž´9¯äcÃ*.À½V”ÁÐ0qðLB=´iíwV4úzíðÕ¨áá—{3ç®G)B°‰Û 2駮櫊i’#U;cŽqïs¢é˜SŽv>ûïðÚ;Üc 6«•¨ãOQÌiÜÆ·/wÎ~àRwo\^™;ÙàªUp`CkzvY%è²Â϶Øß$!ú=鮣—Ío=0Ì;€ˆ¶åŒFÁ¿š­wíQÆ´·_‹ô!ɽ]&EªŽq8i½¥iaÒîëÓôÞ0p&´8:\©u|u¤Lm*=ºòlMÆ;×¶S|rƒL¡ÑEžv‘º;Ì,_&zZpsü8vrœÜSÚ£ô*ûƒ³ŠIPuÛTm¾Y`‰ÜlþáþÜ»¹‘ùf_XoUÿ\ Œv‹uÌ–±j€«;ôý9¤ÿ Òÿéÿ‚ôAú¿ ý_þ¯ÿý_çÀë:¾ÛàøR î!l9qŠñócÚ{ÐŽg[°dß }ÖÄlePµÈo~Ò—S—/?£v<Í;Õ[J#Oø"ßÍÎ%ÃÉ‹i÷ÀÄaèýÈAÛôeÑy6*‚ Y¤±–Ýþ0À%âæòsÀ—ÂŽ³…(ºè)5x¸†­à%a²¾L¾uÏábÀ¾MýåwC6ÚM>Œ9æ“ –1Îÿá8†‘õdS½CQ[+§ÍÔAÂ}EôôÔݱõ3›}ß\eÙ;à ]™, €¼ë˜ˆÏ¡—.¤rû/(»ž24lL¥UÙ’Óýv”*zÐÛ… ¥äD™õ œc î·Œ=G^ÅŽ÷XœƒR’)μ•°/i­Ãñ–¥NyÍdjuTÖ‚–'oˆÎÉ»X§ƒ.dî…ØMÀK9EË_kúô¬ëÒ¡g:ÝnóRŸ°N¯ Ö‡æD·Ãœ¼È%%$¹ñ¿Ë,Iéæ"Tçº&Ë,ÀvÕ  ä™3sô¼<íÅÕ“&šzyF:óÄDdª–Xê¡¢¹pˆµœV„…Òm.îjY•rZ€á•ë3kÔ MfÄö'L²›ípqªÄLüLîÍô™Ñ§³¢?ñû¥`n% ·~vÙ˜yI mÊüì6Ô¯’ÕÑü­Š„shb•(U’­ÅXFµ¾p:~Lmƒó Š€ÜÍJ*ƒqD˜¸Éì¨ìX.ëÇôιêO'¹xLÓDÉ.¶«Õ­ØéËÞ9GÕµÓ}Š.†uþftÚÇù}‡IÛíjJê›3Û€€xÞ½à;å«ÚÚ"ÜÅVe5uê§CMáµ[ÐûæÑàAjü,ΑeŽŒkå™e÷öNÙJèl×kÎisÖu ì;×~TNøœFûæÍçpmù½vüTvkb»/úë„8ƒ6 y¦¼Mû±ÇFuô2à]¸Ü‡úeO«ÄiXC‹i5õ³"B:Û®žÇydÚx¦M>f­/²Ì§¾… ‚˜ÜCªð«wÚ^[FƒÖ؈èûÊá4ÉX²†-:ÇΖ݀ªã¿î-ºÜÙ]Yb*ûŒhƒáü¶—kOO ?'Tú¿­¬jµÓ0Šùöy€ª˜K–êqýWã "ía˜&;a æÍAŸB˜õ4ç©èIÈbá”…&êºñ>¾€ë¤¥‰—: s˵¦ÈÏ=W…±&ÐÜP].tO$4E9›+i©£‚‹fθ#Á«X3†ëCráú/û&åãO˜'ÀC,&Ú'®ˆ"“ð¹ñí«qÏî‹Ter¼j;>r¥NôšÖCÎ%He\¡åõvv¶<´¿²!×Î T°Ô[¿š¡ï ã¨ýÀwªyRÚh52Ûá)LÄ4PTRqpAÅðA¤ä—ÉB2›Úü£Ø@À›Ër´ãxNTyŽÅGf‡"4k ‰|¥îŒ}±C„ä.wØòD޼Ïi 5Qɘïd®•k-Å[§E¾ëv4 V-ò›G`|^÷Ôq2$t,CWŸ 1Û9!àÞE/Òf¦Á>`s®—ŠÒ‡“‚,Ìñ_Äüâ±ØÕ ßv¶ë|¾€?Øxîutžoe—9E`¢D†+W® ìÑÆ Ùõ2H_¥à˜‚ èS’Í¥'û„øÙgD}•P+O­OœΚÝèeЕ½?ª½çœšÕååÃ Ë ß‚]N[o¾å°‹dJO…ä…\1Â&WŠ“¬ÈiRrBµv©k}9Ì¿›eìÿ5Ùê#8ÄÎKªƒx¤_ANŸªIŒýÒ_ÓÆUøîsÇU)‘™‰d˜jÉ}óå­…@Î:ÉL¡H–†’Ú”ñȘ“äLv)É,ý…làk§)Ua!ýŒö‹Â>Ô‡É?ôȂС“mŒ­ÁýcH·€çT¢v 7yê4î[ÂWå(«ÒÈŽÔ£a>)ƒmÓ$lºñÀ÷öϹ(/¯Î‚Šë[ô7&¡*/N)?W·Ä¨N¿”YÒéÜçd³&ܦpq ÖK[mºÓg¡m¨Ú«ˆ&Èt¾¡ÆKñIwÜŽ!Õò ùÈnyì·–fJ^ü¼×Þ÷ph¾\Ù” “êm³6'nž‡‡ÑÞº›¬Qv»S,SD_îö:ÝÏNýµëâê(áTH}?³ÅOÃ&!Í,mš³Ù)²;ÍÞ¶ZÝ nËŠ´…ÑŽªÁ#¶`Eóœ0‡>Å<,+𮶪:®×Óà—RÒî˜È_1¯J,Cѱ|$â±_7Û†uÊÚÿŠu¯2¾×Aãn.A¦¨é°‰ö“Á¿ZжÐÂfÊërP²…3]pLò]{bâùXN`šTÄu8Ê×Q"Âä T?hH‘³ÚÀx‡´YëÀ1 žx#÷/BÙ³ !3¡¼tÉrÉ÷yó0°–€ã'nÃ#° ùð {¶?4°f¦S‰„Ñ‚±B™#È ø‘=Ap˜\Úm‰OÌ¢›ô¡ÿ.Ž¥“/qɽ´•¬|ýÍ;*Û¹W1z4ÃM,suŸòš>¦]~ËFÔh—*a™˜Ö»û ÿ4t÷Y3^8‚–.j3-dÛÌ&Êõ͖Эơ”ø^C²uÓ㚯р›¶S‰§ø{ŒWMÉä=Ö.nÅà©rÍa•|ýgÁueù—÷çÌ‚AƒþÞG¯ÜýÚ~e¾I…IoΜ€P {¼u³“íòÀ¿¼ÿÂÍÃjñW»ŸÿÄ¿³ÿÅ÷·ß¿ïñðýýþÏ?>ç†øŸÿ@üOˆÿ ñ?!þ'Äÿ„øŸÿâþoñ?u þ'Äÿ„øŸÿâBüOˆÿù_ð?ÿþÿÿ¯ûŸþú[àÿ©þ'î¿ßÿàåe‡â´øìääú /‘üü?Ïþõü¹þòwügæÏÍù·ùópðr@qþ·]¤ù@æÿÏûÿŸ8ÿúáCýÇû?98¹øøØ9þ–ÿòòrCú?!@€ @€ @€þ üÕÅ J boxbackup/test/bbackupd/testfiles/notifyscript.pl.in0000775000175000017500000000047111017224314023545 0ustar siretartsiretart#!@TARGET_PERL@ my $f = 'testfiles/notifyran.'.$ARGV[0].'.'; if (-e 'testfiles/notifyscript.tag') { open FILE, '< testfiles/notifyscript.tag' or die $!; my $tag = ; chomp $tag; $f .= "$tag."; close FILE; } my $n = 1; while(-e $f.$n) { $n ++; } open FL,'>'.$f.$n; print FL localtime(); close FL; boxbackup/test/bbackupd/testfiles/bbackupd.keys0000664000175000017500000000200010323670556022515 0ustar siretartsiretartËM¦rùmä<¶†ª¿sâF!×ÌKju]A7¦‡"Ò`âO[òäx˜lÏ@s_4͆u–í¢>Ú¦À…‘·1É Z^bihŠ©à"éÃ¥•%itjê~³Iî¦ov¹n Ë:øÀŸÉM\Ø1U>Í‘†ÍŽÅEl¸ðì·Ò­fÂs…x3» ³ÁRWžÁí7Œv9ªboxbackup/test/bbackupd/testfiles/serverCerts.pem0000664000175000017500000000114310347400657023065 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92 cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/ myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2 SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1 HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t -----END CERTIFICATE----- boxbackup/test/bbackupd/testfiles/clientPrivKey.pem0000664000175000017500000000156710347400657023360 0ustar siretartsiretart-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G 9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp 81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9 qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8 eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26 Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw= -----END RSA PRIVATE KEY----- boxbackup/test/bbackupd/testfiles/serverTrustedCAs.pem0000664000175000017500000000112710347400657024030 0ustar siretartsiretart-----BEGIN CERTIFICATE----- MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt SlL3iQzVXlF6NAhkzS54fQ== -----END CERTIFICATE----- boxbackup/test/bbackupd/testfiles/spacetest1.tgz0000664000175000017500000000044010347363533022655 0ustar siretartsiretart‹³Bô?í˜ÑjÃ0 EýG‘dIþžnMGY²ÿ_XÎÝÃVÉ0¸çÅàdrÐñz9=ÏÛ¼n)"¥b¶¯ÄŨ]•²o)‹å}Ÿ™U’žéÆÛº^÷’OóË·ïýôüŸ²þ§…£j¹ê#þÉ?üköDQjÿÿDÕèó/WÿJð?€Oÿç¸þïÉÿlÕ¿#ÿÐúŸ–R££ÿoþ‹fôÿîýkH®üw¿ú7øAã?nþ÷ä¿hÍCþ ñþ©Ë?fÌÿ´þ§sPþ÷ø—šÿžá_üOKÀGï™ÿÕ¿ˆ æÿÿU£ïþ¯þÿîÿFÐø/Q5~sÿ«¦ðð¼9ãÊõ(boxbackup/test/bbackupd/testfiles/syncallowscript.pl.in0000775000175000017500000000116010612671673024262 0ustar siretartsiretart#!@TARGET_PERL@ use strict; use warnings; my $control_file = 'testfiles/syncallowscript.control'; if (! -r $control_file) { print "now\n"; exit 0; } my $control_state; open CONTROL, "< $control_file" or die "$control_file: $!"; $control_state = ; defined $control_state or die "$control_file: read failed: $!"; close CONTROL; my $marker_file_root = 'testfiles/syncallowscript.notifyran.'; my $n = 1; my $marker_file; while($marker_file = $marker_file_root.$n and -e $marker_file) { $n ++; } open FL,'>'.$marker_file or die "$marker_file: $!"; print FL localtime(); close FL; print $control_state; exit 0; boxbackup/test/bbackupd/testfiles/spacetest2.tgz0000664000175000017500000000031310347363533022655 0ustar siretartsiretart‹5Hô?spacetest2.taríÔM Â0†á¬½L&1?^ÀØ TÛ.EL½¿Á"•ŠÖ…QÄ÷˜f |I‡z×ömêucuT bD‚sJDLôr»ŒWc.9³4!×M>ϵ"ÓLœR_sËm»zoîüGUëMu]‹oƒKcþƒnVEzˆ8¹äùQþEÆü{;ä?å‹L3ñçùŸ¼¿îâû{Ìþÿ÷ïomˆüÿ/8Œåé(boxbackup/test/bbackupd/testfiles/extcheck2.pl.in0000775000175000017500000000152311017275716022703 0ustar siretartsiretart#!@PERL@ use strict; my $flags = $ARGV[0] or ""; unless(open IN,"../../bin/bbackupquery/bbackupquery -Wwarning " . "-c testfiles/bbackupd.conf " . "-l testfiles/query4.log " . "\"compare -ac$flags\" quit 2>&1 |") { print "Couldn't open compare utility\n"; exit 2; } my $ret = 1; while() { next unless m/\S/; print "READ: $_"; if (m/continousupdate/) { unless (m/contents/ or m/attributes/) { print "FAIL: continuousupdate line does not match\n"; $ret = 2; } } elsif (m/^No entry for terminal type/ or m/^using dumb terminal settings/) { # skip these lines, may happen in Debian buildd # with no terminal. } else { unless (/\AWARNING/ or /\ADifferences/ or /might be reason/ or /probably due to file mod/) { print "FAIL: summary line does not match\n"; $ret = 2; } } } close IN; exit $ret; boxbackup/test/bbackupd/testfiles/testexclude.tgz0000664000175000017500000000057110347363533023137 0ustar siretartsiretart‹¦4@í˜]KÃ0†û‹\¾s=Ü.„áõª8Y¡¶°¶8üõ&Í6q~-.ÍÕûܤ¤“òôœžfiÚnVnh6"„¢¥´#¡Z7:öãpMí=IÓšÚyjá™sS{ú¶{ÜØ+SÿºîumL•bCiYîüOÌö©ê “å&vŒýE¥]©œε‚ÿ|ç?gqcù§Cþs…üOÂÁÛ¯'FˆÉÜŸï¿?îõ{Gäÿ½÷'`ߘÊÏÿsý´Ù~ƒÏk³A°~ Ý»eÌšv‹uøáf.9›¬¹o#6 ƒ_¸ï3¦x#Àˆ…<âá=wöâйãFqèÞnb7ð™å;lqæú, 6¡ÍiäÖõ­ð‘-‚pu؃/YÒß`#–Uภ׶G‡Y!gk®Ü8æ[‡Á½ëÀ—xiÅð?xëÐE§ Ñ«|á_Q„ºc•ã¶ö¸¡ý…¤²ñÀö|ÔìŸ2;Oô²Ûy’±¨SnÔ¶€ÖPÿ{p0Ɇʵr mæE„ë‹R3]½FþA.ÆìÜ05sfªìr2ˆÛPõÚ@5Îpýhbg†Ú:¦Bä HÏ8Ãïç3C#9jcSÕõÙÔÔ&㮽š|A³Ê ÕŠ2ŸŒiÛ ³‰~ƒxQ¤’ût¥Â¸Ž"&©) p`f!*ÈÓÌì—ÕË‘v©Ž*ÎNÑ'ÍP‰E× „ÑñO PžÑöQwÀ›øš1âi˜iL~4CD#áÁ. MšIpp%€nÿ­r(ò«Jï¶ÃÇuÜ…ÖzéÚ™˜,ã,xºÂÄM°ѣɭÇ?Ñ"„Ý·ƒU ìø‰È‘Áfº+RæÆ‰ à5ã_–El‚݃½½ƒ-ÛKnAœfï=2þB`§» ¹õ™YÌvC{ãAÔu8xŽÃ}ûbvüÀé XÑ9ÆÃ±ÃÐÑ!û³åS"¹/(}ï.¾`ó¹½ö6þ·Ç¿@>à³7ƒ7ì׽﫻 8¯Te¨êó©z=¿Ú»\‡©Î½Àrh #´5q¦u–]]¤ò[2ùG§ëßüS¼ÿ}¶£î·¾î¾ÿõúÇø=ÿ;ê¾½ÿý>pÿKtŽ.??mƒÀ0dFÌ×Kø5fW§a(ˆ–ôåO·îÝ"D0 d"“E  ¯E¶‘ñß×»æë]óõ®ùz×|½k¾Þ5_wÍ×»æë]ó…wÍâÍîÃÀèöàr÷= º>/ÃA™ýGy·n°¿ü©0øåäð}þõR“T>¨7smØèǵ8Ü+_+?‹3ò…µñ@>@F¤26H5ƽ&[•ˆê…2™sð3½ˆ2;×À±“x5ãÈ 2?RÇ—æë– PÀt㔸ù  <\>ÎTŸ‹ðî»”˜®­(zB‡2:é:"/{páPŒ¦É§)­su>kƒÉPÍ\ò3£E®>óÇù˜Nþç8(BZŽ3_„.lß{D^êÆÅZîEü™´¬ÈþjZ´Vj„zm0À Ç£uG ÜG}ÈAH’*Iý¹qø¥{˜3´Kvøåô? RƒDÝí×½ŸÁ¨i¾í¸þ"8ÛSŒqw>˜ª9ÇpÁªY^,'0²^Bdl£!ž5€-GX&=Àé"7¿eÍãôYÙ<2È;0‡9Û+¬dí•e‹Ñ>k[›xY ¥45ÕCNE]~•Á‡ù䢙Zå…z®\¶êèOÎéˆlãìÙ¨Üê×½†×Ýù­u7‡Œ¨ Ïì7â6dG~™® טÃgWžÎ!_»';@¹ŠF~$XÄWZ´Õ@´Ì@DË0Øà•žÀÖíuD‚iÐÆÌ›©ÊÚäM «{ËÛÀþÒµDP1E2 ÂZ+Æ´p+Õ-H÷IÑ&D×£Ù“Œ?WMy ìVZ…Ùb¼FíœÕφ^õd`Ç<]§)'©'8‘›E™4*Í ã`éSê-QA*`Ø.$5ÅÈ9ËDo‡'àÌÀ0Vkþ·®çÆpw°Ã ïÿ׉ˆqs½Ä2+ƪW€8ªe†+HÉ™B²Š±Ý+Ћj†+hmü5\?æë¾cÅ«,¯¡É4t°ÊÑZJ¥uÅñy*[ž=ˆÄ+AÝr4™úq:hS¼®¶mw §N‡!Ö!º?rï|È0fy¿!ýÀDÀw ¹NCÆ•ÔQëE9¤šO¸dU,æA«yó»9î§ŒfÎæŽ ës+“D•¦«N£¦¤³î×Ñ”‡ÁÒ)©Ž¶÷BýÑšzþÁ>£m0a=ÝãÓγ‡?+O`I–u{<—ý$»-r×/1U‰®@¯Y^`[8)c•nZ,ÞÆdˆ‚ob+ŒÅ‹ÛÓîOH÷ׯE<0¦ão³x£Ý…¸J;(œ-]Së³&´äÅRdÒU§ðî{‹´±ôh>ÛŒf0e·[sv™˜(8dæŒPF—²–w„u!lWôrô7.†à6:”¢¯©sàS£‰.é˜M§çÖ³™?må»;̰êk‘úûífѪ|VeÍí9éÉõlÖ’HõYÈLv‘á.‰ K¾+ÐU@²Ì8EæiMó ©ÔœH’‘rôu2QX„ xY°‰³§úõµWeÈÕì¢ëP<“ãç1úµüá#9Œ®÷è¾Ûüd>0†%þò¬.ÖVh­vå;•< 4YïɰˆâƒÜ<ì<ƒ«‚DEFJä·0T º÷Ä| ®úQZ µ ,aØ, 1Zõ Ê̼B0ÄC!3ã¯pl'õm¹$-ŒøS6‡ê›Âñ2_ƒÛw˜¸}¶ñI6•;Á[8c•ór+æÍ,âlöµ%’¥‘(Ó°­ôR9§ÉZR¸-H³Ä[ü6þÁªŒåãY–@i”(â¹çä- ÑðÚì\½ÔÆŒI2|¹bÈÊ,3W£‚¯u"ªJ‚Ü;`VñýÔö5d‡î$û™‡áþÚÛgŠÿˆäü;N¥0+Ë¥ º¨ŸXBXðɈ›Éë¿,.Ë`Xz¯n|Q4V(Û”*_¹‰ü«a„T¥ÕhR6‹oSmľ|óÄ.䈀§á“Ÿ‹ùTÑ •Ü“ ©{xT#p8œ_èš:ŽnÆÊµJà¥ç½µààÑ\Òë?Él¬•ÒëÅÜ’Ñd ŒÀ»´¡ØÂq-ô@WS•;=©ƒ¤ßLÍù°§ Ð÷µ —êc¥DY¿5­7œ«cB+@OëAÇš)ñýP „¤MBÐn½"P&‚–Þᦠ™ÇR´^ð¹ç#¸ÞtÆê§©bHëÕo}ŠapÚǃG ¬W¥™n¡ë5JÐò°õª"—¬vw° ÇÎVÝzM! çGÐØ½ª ÕœM¥=`½ôñ]Ujv½ÁŸ’w홡\ª´F §ãD° I~Tuíâ&Cõ=[Ñ®‰³26¥L}60gºà¥;tô<ˆÖò]’€*Y«Ž¼¡J¢Ò“åÀJ’ÒÅ4 Ô„+`²HRöC)|¥ÊtpÓÊHÎdzÑh®è—3¬ª @èÄ2&TêSŒt¬¬@·Í²_ŠCzêÝY°’më”Ï)ç†`±2þ@¥ê æn)ƪ8$€…¹náÊAHÂÑõšŒ6\¡X–v–¬ÐmðBÑF‰I•C.O¸ŒxÊA%-¥¡+¾¼ aN–UÅ–t‘¿ÐÆÊ(_¡ªbäÈî±Be³ñ‡ñäÓx>Ô.UüiæÕuu`Bxc6Nt4Þ$Hƒ›Èð”TœTw|ü_müŸŠþþ·þv÷½?9z_êÿ8ĿߖêÏkÿ‡(÷ªO @^ 2mj³8yÿ;ÍÔÿUÿQEd}ß…( ´ìÏXðäbÑ?ÃÊVwµöø¶¤?S+8¬¬6hy ‡¥u†QÀ,*f‚‹®¨Æž€1#ÛZÃkŽMÊ¡çÞ†Øá_!EnTþÚ®%j„üÀ— g áXäO× º–q³ZrøƒµòT-¹£9d½ö‘u „JEv „#ª©½lýw/X®h6ÑǦ Õ¸zK+ZvØëœylïwÓ³ÿ‚µá¸õ€Ã’!ü™k´@D²v2mª(0€ò²ñ†—¹,b5\Ú ÂàR¸¢}ñ/6_ÇÛæ˜%\\±Ï#ŠÐ'ÊAY¾ (ä+˹níä-¾Ø!B¢½¤šø-¼hD‰dO '¹¢ZP Ø´Èi í–i y‘ÝÚ-5:€¬¤Êµ“5Óhl<Ñܹ÷`IÁ”"Úâ’n×K¬*Eþk+Œ£äGbHf?µJ¼å6-¤'CÆ‚ê—x6¸âZØX€5¼+†.Þ¬Y õzgšOEœxgHZ寙dß©èÿE›§¾¢sê›öLýkõKe[ž¾ª_ªÔ)õ¶I¥õß’­gµIñgt¤Ð ¶5 ñ78ì Ý;¸òÓÐõð$ù™#k;°÷?îœGMVTø÷çnmx8ã™G”!Ì£x¯ñ«lÀ׬-`Ò’ü¤sDÀÃðou2(wŸ¢ôî7Lš3ˆ\u£ÑHûR°Ï%qêqiÔƒ5¥ÍœÉ}ÐÓ'ÄêQZÞÝY¦££PÝÔ vª‚"òa‡ì¯Û~‘"2ð‰°T“@8+wRÜøEš]As¯!_ü­)hpêI»ÊPä€ÈXòŠÙ—´­dL¯®©$TßWR«Ö-$.îºVµuš=ü‡5‹…9RA¸CB8¸Ÿ§Ò[lj¹ãâñûŠxÛWŽœ%„ó\›>!Ë-Ì.Qf¡vx‰ó)vóJ[µ`ÁÊAÙD´`åˆD·ÞJeÒÈ[~é@!Jìái䬵±Ã®íŒßƒøùÝÆD:ð!£¢ýÏ’÷ßâ&–Âå/ù°ýGñ?8]üÇ‹À?¶‹çêø£:‚€OÇùUð€8à „û×]rþÅ«‘h6’±œ6D;T†í¹hŒ*X&Y$8êjW„°b«kŠ%WH†j®2²ôhŽ©$u±–û²ûÿåîXo×6¹¹‹wIRf£ÄOvcY†…*vž Ž…ÿðÔW°ýofÿOÈì%âÿ­ÊÞë„,Ï­ÛCÞ¬WŽØ{V–fy®2þôhLv ÞÕ[3¥4_cÍ϶Pz»Uk ‚l©kb€¾ Åç n,þ]|Þ‰,2 P¶,öô8´¤Q ,Þù]à‹žT{Ü¿‹—…®EÐö\¤ß…¾Eœ8Ÿ o íî8l*Ú¨ÑÛ£Zé(†t‚Î>ÒŸ³Áz|X`óˆQö-{É4ÚŠFP”†T3>3Mfùd™šÏp…Çyš•$B…È'îø¼ƒž¸Óê·‰ý¼íÜ`Ù%‰k²v:]GIñµ EÎÅqpÌŒv ΢~ÆR,³/=-Ï>9r/g“…Šÿ& ŠÁýŒdsíÐ ð2gëkµ)ÔŽ¼iwGs¿Ut‘É”–U»R‡$=…Š8ŠÎ—Õ½"RìlHäžö(þo{ßÚœF’,z¿¢8?¢wcl$ñÒÃÖzîA€lbhYöÌ™ ZÐH¬04XÒîÌýí7_õê’g¼söìˆpÈÐ]•U••••™•™• '¹Lÿn£eù\BÍä*–Ò8hjÒfÖÏyY'ÔW¤ÞÛ·1:ŠvAÓSødxºF:H!À/)5Ò@Æ×ÂÆ¨Ñ/@]=­!ЧÐÞÓ:ŽÅcó„ŽâŠWfùÏ&¼õ`0\-§ðìªÌÎã’D6ð&°É$œ/Ø\·{s–HoøæOJìÊA.§M*½^«Q%«n§ç¤FIÐé„#Eú“V,KÁ9Yp;Ê£‡8 @98ZAÊQIöe.›Ë)_ਠ鿷T,ˆþˆ tŽš“–"N&¨‡ôvO›§ } 0žúWn2n·ßøBFá>ÊiÚZ£Ûïá«Rì̽©Ä+½¯6Ûøê0öŠ,µô®Úö4¢Ö¢/¥PƒÑ—ÇÍvµKõã@ÑvÁíéw=ÄN­zF-Ø™QôFÏ[™øÀ‘ΔÉÀic‘à0 B#o.ÌÆÊRm Ç¡rTÇÊ–]¤ÇJ º]ìGJi¼»Ó)%`ÏF¬5š{R6FN$¬…œ1ñº Xù±oX•)¯Y‚Ê»& âåÝŽþP=RVÚvgpr6¨ž5U$¯òÅ?p\ñå%ÇüÚñ‰Å¨”¬jyÄeGëERô°iÓŽØÐfB1»ÍÍ•n‘‹–Lµ7%‚¬?RÈR-É2èZ}jYJï! ¡›K¥†ëÙ•z•®#ExåbêÒÁ¬ýVO&G« ŠÙ£hê/¯ò:@gyeÇ?³x@ù<"õ#A^\Pô»¬þÊÒ‡]⬧!¬6Q¡;˜Y"Öc[œpÁŽ®Â•ÓF³#Ö©XK±ŠhSa7gøNÂH Ñ&MF¬´²¾Í‚»(jgNT#îEJî‰~§ÛàX&PíçË@¢ç&hr+Ãëx2·f!qì ËyöTÂÈËBZ}:“ŸÂAÒA0£ibh­K5öÉdæOSJF`ÖÜ‹í²Ñ0ª0™ h$Ü¿";¸•0—‚!Éd7gª­D²4YV8¡ Úë탥Ghçikƒ±NÁÖª^ªJÁbó_{œÚB÷Dr×åSzo·²t×—ye­}»|déÇy Ù•ãÙ$âñ¾fÔ »¾ÁXì¸&º.'£ûôvm¡¤9µÔåôt” $UY z2O„u&|"¯X1 µÇ¸,‚Õð¼T€Šó¨ôK*¥mЧ^ˆØ’€[ Óê?eÐ.—âV´Awzò„„®Ý# è#ð¶\9 ;+)›ð+G—F¹G,8J™Ü?ÄÇLæÒ䜎sø¸M¾É={;ádË´0™qôƒBùíÖ¢õt„õ„Ohßè`IeH©'ýÛâïäIŠcHårÚ°[XÃ^ ÆWÙ ¼ÜC‘BÆ:µŽ—tæƒÈ‹(zõ;kÍllÄ%cr^75’A‡7Q`$AÖÛÐï.`-!Y"HÁ’”` Ë@‚mmôíå%ŠDÒs. ÒÔx€®«Y=áÉÄhª §q¼ô¿MˆòÁ—D(|y€òÁàdp¼_¸ÊM™¤JUì¢ ú‡S.j6:‘ncX$³Æ¶´ÄƒªÜòhiž4k?‹^Œî¶£ÝR$`4AM¤¥’Æèçe•ÇhÊÕúºå¨ùKAE³LŠ®sËY¡È{ŠaÏ#ÝbÊŒQ´Ê5¬0ÔbÚœ`ÕbZ×Þ5úÁZ/)úÕ”DS²òˆGFÊ©³Ïx`¤[bG“â"­bqÅ4Äi ´…½bÚô÷е·au<4ÙÖãñ‡XˆY˜³öb!ɦ[j%ùѸà 6 *ó™Ð”9x_í¡-‹Oä @8ˆ¶^]«ÝéSX=ÙÍïõdŒwÕÔ’HNá¿èlMsÀ’‚•Ëщî*‚nÔ9‡XØT§ýN²‘'2]Ç ZÎbWÅ…šø×$~£â›`®f9ÉŒ4 m­‰351ÍLL퀃ô‚ü¬¢žqÆ¢„܈UVÏ…%=)ëÆ÷B¹˜°âeãs j Hò¬„À¼9‰év­~f’—G7a(Øn”eQ¹ÓóV¿‰Nöä{`&ÊÇi5Rþ¼]§£,_V8v–Ô¥D„È,‘Ô;¡†&32•¥8\Øáò±M½«dw›9c3'H` É;¤J +¨ÄYÙ-lLÇ1sÞÆ A¿3€i‰H_ bF¬Âiãtw²°‘Pº‡)2„¥&бx|=ʘ`Šj2Nܘ}Ù«4Üb{Nqg%r]Ùjbë-&>¨’üË”«ü»d ˆÄÿ/ýÙèk‡ÿ?ÿ"M9vÿcé T|Žÿÿ >&þ§^æÿ9úÿ9úÿ9úÿ9úÿ9úÿ9úÿ9úÿ9úÿ9úÿ9úÿ9úÿ9úÿŸýßEÛr,ø_žÚáù«°×Í!ûéáÿcB”Dé×;½œUñ޹-AsÃûωîB¥j"ʵŠð¢ÃìË0F9I~Îþ—ë±ï®osG •}yù° Â\4¯y´¬N¶^äÔÉ£zî6´• aä~ Rˆi{ëÑGðh¸F~­CÓüÏMá),ØNýH#ñ¸=¨7ŽÏß)beÂÓT‡Ép…Äm¼Yñ-A_ÎùHdµà½Ä7˜>þ†~\E XÉÑÝ‚ÙÐI¶Ñ~< £’ð'”Œƒ*YBA"ìÒs™ ¾$KÚà74£Iø4è4…y žRÇ4¤›í¤‚˜>¥\>F¦OtÒ=žL'w?>È“|ëßs‡ZìšXMÏ?¢âø|@·»XàÃÉ߃$/Q]l2\ˇAp5ì¹IîW×±Û4MÛ'P$V=ò>­‘Dx uµwÀc|Éûé§´÷å’Êúψ.ƒ`CDð½Ïaß…Íä•êiÞkÑÿÔ¼ ?ùßí{!´ð$× ,ûežý„ÁX(‹D¥¨D=ЯãOýFO9Hl<¦âxªÑ~LJ¿óù>¤çÚÛÞö®…|Üj‰SŸÔ‘Ûß c…\C#%µøúu9+ôwþîÓßú{H_ç½L5Ö=AkzO™ NД-Àæl¸ƒ%.Œé ·‚Ë«]û·—ÁrŠ6;ÔEšþÌkù¨1õý‡)^9V{¸š­C¯·^,æËÕÎÖY'hdµ]’é Æ’'ÖÌÖ¸|_xk±ŠÕFUWTu lGd'ASÐÒúÈì.†5²k>lM”ÆD™ H•ã.êDïhÏœ:ºŸ ž^Ónº…ÅÓPväRÅ”XR ´<ª…Y…^zsJŒ#]íMº^€͸FÊÚ|=_l…ÝM¦SÜ«×a0^Oó[PÒ»hÂNÊ2ëÎdBùt¤ì½J LÔ*a0 g¯0kEÔÆH|ÌÄl/ k×ÖtŽi:(ÓÚ Tæ†ú &OÌó×þg´óP˜AÍ%ìãs´å“ˆk@oGhå8OƯ@e™pfo+Jðyoïµ×ðäÊ;›ú@ÓÛ@ÆX½\.ä½ãy¸Â¢§U¯P*‹ÛÅrÖy¯ ã·•h íV³ý­¥@›'(l\\O†×^øp{9ŸÒAÎïB%æði3ÈDkä |DÂÔbë°.éÛ`6ŸyÊÊPŸÏþ¼0HR›¨â£àr}u,óX£ÇMÒ#l5±F8¿•n‚`1Às+$H¶ª² Ü–qëûÓiÆ4†gfN3?Yè ûBmph?÷—£Zhyà¿å2˜Ÿl‘QMƦMu$”„CJX”ï1<ª–iqb·4ZT©—‚Ü÷PΜŠ"ÈTµ:T5ìª ðñ"߇|Fç=ó0œ î‡– b[ÀíP|PÜ@ï 8Aˆ ÒØ…(Ýác ¾à>žw<¬uŸåû;5·,åšXøµÖ£•Eáù 5ךܺã_#Õ/ƒq°DSÞÈYTlËœÝàÒQâ‹•0‰€J‡K‡ÑDEc4b²Z…^ÀùB¤[bHs;KôH4…z«íX­0±jØP€ èì|uÄpú˜QHÙNé­ªšBÔç)âÚçŠxhfyš4ÁÅuƒGÞtjŒP ñ´Õ5 ëêš³N¬Xt¡œC^_ÿà,P+µþ(þÐGÓ¶|‡{ÃLqsFžÆçØ4–‰{F-“~}§ÍÂøb²”~``™Ì¨º.““‘èA†ã”g0w~›íÁ·ÐÂç¶ ©×š'Þ[0Z®Î×b¬ËÊ:T÷G#•ÆIŽFY°Cég{1á~uþ¥Ù•ÐY0Cwª¡3Gj‰c¥`GŒD,èâà"ò7')â¶1€ÆÚµðT¡°Qmx oذÁãElïlq‘ d v°e¢œùÌŒX•®['¬FWfÚÐ$Â¥o¯wë/oèÔ?M{ZC¯ä<¹¸ Uöc”¨D\Ú’{oÕ|ʨÌÌÈ@?à0˜¹,¤iÁÐóoA#o‚$íÙÑãBi”k£9êÄ+>&¹ ÈI‡§…“™¯´@",I€Ô¡ùì‘q$°/«ï#©ÊüLÈåˆZGÁ 4x¼à&yê8Õ¡NüCrÐ`â¬9SŒ—¨…ZÍÁ¬íRЧíši;¡]G‡´7x/}øËÉøpËW¸óCí…ÄÕ3?3ÖŽÒú‘Ò‹'ôaðùÖ÷(²õÈÚÓè´ ólµ{)_ŽéŽnꦵá%팩ýÜDø€úÓ ´¸©{n[‹¥*{Áß½lb?€\§9=¢Iêx\Q!Ömk×}y‹Õ³ùîJ¤`¡1UÒš24ÉZ°á^[“¿¼àýéäj†{x^û©¨9šD 0Ç´–¦aô T‰E\`^Ö¹ˆ§ØŠ }¸¡;l%ÜÊá-išsÁØÐÏÌ,u= é›ê«î¾8ÃéßqÂ9:¸!;u¡,æwûsÝ͹Ó#Ñ’°ëØÒ€ ?p«3éãâ}ýN¸óg´áb’ðÀ P{v`háà(kûÀóÿÜ#\ø$X~Bö‡ÉA“(R» ¼ƒßˆBkΆuÄÕÀÄ#b[†yœ„Ø:yz4ª„Nj«jM7Ÿ„UŸºì±üÏÞËIy½Ï:2Â+«x ’îP+F°ãiÑÔCÁ½fŶ ”ˆ* $V¤ª$i$QNå2r*ª«_ú DHô´‚DVâywºÏƒÖ†Ë"4¾ÒÒ®•¹G×Ïq •óÕä6ál‚Êç IÓÞâH¦TzÀüòo¨r )ŽôÈÑdLÿÊt9ÎU€†,5zæ‰8ø•¿ÄäÙ/ih˜{[öV[z¨ë rw^)ÛŒžN’DàáA'±ïM:DŒÕSGBéfC0JçdFbu'îzød ƒ‹‚&ÝÖ|~â„££ûID<öN:­Vç‚è|¹×BDí…)›! `‰û>Á ÜÖ³Œnqr¾¿iLîÃ)Œf½xt<Ùó²Ùôµä½t/.‡]{>ÇSwY yZ÷úÙ|ñ`Jðs¹tâxбXmöjÛûÕ-;m¢§Ÿw·ô‹@ën²•áv-öRÒ£ŒüKû­OATìÌ“Ž¥éùD;´ú†ŸŠbéÙFT“È—ÂåŠkóÅÆ¶Bl飙9H¤HCtŽ™a©ùdRp&;û2ç%”ä½³~7Ç•ð›ô·:"õ[£‰ÆÜTòé´»©Ø¡ÿÐ;“ÙÆQ~ “d ß˜É !¹=“åêu”à•€#4MÌz4cû¶£v.sD½¸¤ TÆ­«‹aÖ ù*,¹.&Ò-x©(gRUY#tY;‰6^É\ÛåžXг(jЧ¼ê1—•ÜõE¦¦±àv²2ž6+?¼‘}è…=’¼(½ænMCF››? ç|Öp5_¢Ÿ5 ݯy¦Ññe2dÿÓGlŽpÿ¤Þ…׉ÂÝŠ—_¦ÃY,ƒíKØ(´Í6ŸÍ ©ädêÔ -%@öo%3[äÒ1Ð„Ž¤¡ãÔ‚ ƒ~Œt.hÀS‡Ù°[=Ì=“„pp‘tB/øq=¥3`Aœõ™ÕŠÓ®+“ø ß­Ö›èÕZm N:ÝÓjŸ’Ÿ0Ñ÷­^°YUü^È&„c×dñµ åd¶X‹¼DrTtÐVßÜ÷ÈØAP „DŒè4ðdö‘ÝŸPm[zGóˆ«x·DCF<ÿY’Ý]ÆaÁ=:”OPYæ*>?ªÕ'Ðe€>;ì®ÝeRèHjKæa)o67öô. þxo”Â[\ň™'ÍOâr°’S¥¢\ÎÁõ2×SsºJfÝÄ~,ƒ«52 Õc WXOv|ëߨº(HëÛû1«žåzFô£6lfx=¿ pmhH¶!QôÔÀÏ|Ž.jì sõ£è?C,\·^ö8èôr8Ë+ž=Z>ê'uM°f¦–…ú6 Ÿ 5¼æqÝRÃsÄ‹¹/‘¹èGÊ— AÞΕ½d®å|±œ I+׳áz¹¤ –ÃëÉ*PÚEs§±ãfþ-Û/܆bX nƒ[¤.Õ&Z± ¡Âê›°¶ ;‡²\Ó±ù÷ggÕ8Ð,‚–Ð$uÑñ ¼†ª±•”|Äœ||ÂÇÆôשv"› E×òÿÑýD NÏq_ìÒÀ‡yoì㥼A(Œë è÷Á:å2@ŽEQ3$K-WZ)ñmd,FÒû<Ÿ~6Þú`Y»‚Pmh_)ÄÞ牯‹Œ¾jÞa-hf_•,Ò4‚5e@*|ÔçU÷Ä–N kh¬R´”àÕ< p©-sÍ•Þü˜‰ÏÚï !)ÍÛ.àÍwl¼@«ÖlÎáš›WÌH`Ç#±ËWë60#ƒd¿é}:=î´zŽqKxŠÆ8¡SvµÆ˜FA¥±%q;fˆéÊ@Y˜Åï*x ЦìúÊCL×áEîÇïq8‰Ž j¤t„±žÁŠË…’LŒ×ÄfÛÑKíNY&ßn%—e€xÅq¡‘IÑ!‡$ÊÑœÈòñMË çÁcƒä6’Z°7ÇÖÁ¨à"/ت%rÒ•LÆç’9@S›«5¢G%)¦]\¿ä':¶—Å¡loãO!<ÙKyÙr»ìÂ{¤·XÊî+1€K\P¶Ä‹N ÊÉæÌò¼¦É˜¬¸ Ð…©.¹ß«ƒíæ˜NÒþ,çy€H›"숰‰Ìh‹4È€} :FqjÓ8§gµÃý‚W+PÐbà/§“`™³Y”HÀñ4’aÅe0’Zɰ \²¥ë¹#q@"v ‰Š×Ã(gØÔqáÓpÉñ|1„Ž j´äì®vsT§‘´+ºQ ñhÔŸôªD)ÁL›m6A;Î@?úZíkHmMGV‹qµhÜyÖ‰=€Ê¯˜ƒ¶Jwyöî#,¤€úƸERÑÊ«÷'­ê»Í%ßÑçQFÈ'T7]ó½6’„ÊJBÀ0jZ؉íðo6ß-áf&€A¹7)O•¦a² )ë&Tc‘¬¶,~肎U™ÌhÆ’câ‚vC èÊ ¬¬—å­ñ®Ž<ÕQ}´$3@±Ä7-]Nm´4PåÄÉ•ÙÂhõ'Ó5#DŽ ô JÌ$¤m9Ê.T6E ¨µ;ñøÐd+qr¢ðEX qø3ްŠýìÉSå$X£êP’n‘XjÆ )\V˸™À@²'Ëb]}ÝÕ(‚n 5R3—nè“)'¸µÎÒõ{‰b§ÑÞ÷44Z>|O“w šã4€e1v±ä"Åø®éú¤Ü„âÒ±&ÏMIZìô5û¡ˆFðâCµe½`Dµy¯mAB†CÚ$|¨!!÷o34#Hb µ¨c–$‰UÄ/üŽ+㤠1…»ˆxâ?1¡âÖ!¡=Û ƒÉ§Ïvtn]2˜Ûd`äN³‡vAâss°[{–\{–T{µ¿€®¢.Xž£G$‘ªPZñÉwöQr¯êèñ4&ã|„ÌÉš[Þ§HVRúìŠ\pÚ:”¦\y$Ùç™.PuŠ p…(¢Ç}›=Tò†”b â'gÌ­U7èôšßlòÉ~Ò8“ídLÒ"RgCí4Ô@)ÛÆ’'; ÊROÁ‡sÒpÜë$ƒh㘵5:pg,i«“A=qez¬·$.NO Æ^Ÿü0Íós.¹÷íÚ¼hÉ & vöD°³4°3û”åŒ'ŠxŒ¥ ç, ¡˜Ñîw?9\5êÉÇOÊT…ò»&xÙ`çjG–Á`PëwºƒV³× Pé6ZÍÊ­hz-qÖѪiá÷qŒ¸ÎÁÊ‘±‹QKIŠ E]®í¬j'wfËÒtÄ ª´©í¶s Ú¦³³8H»ŽB«±þ'Ë«9‘?•$7ÒR Z0Š Ú§L.ôÐ)ÃõÉ»~tOÞhŠ@å{W¯^ñâÁc¡¥’5ô#%’X¦ :ú6Ò5Z*ì09× c ÜT0]I¿{Žä¥Ü˜z즕َ^D;¡Ú&ñ‰fˆ>‰hãd¡Ìš‹ìp ‚¥*ÑC"Âã‹TLÓ N?ÖŸÌÓŒ{ƒÁÏVŠ’N![®äðd:AËÞÈ¢\Ú—žq•ža$÷ü+L¨_f±#G´!þÙž6¹¬Ðj‡VÅYUãÉ Oª}Tn´G Ц<¡Ãz³Fí|…® ”ÌþÁïxͳT´Ÿ‡º£®f!Ý‚·¿HzÚÈSìrÉÓ®ÞF&Þ}§<´p¸_J”>bŒ§­óáp½´ôK‘Œ³r´¬IµC„Åä$Üw‘´]Ád^QJ£]çè+Y±h¶˜IRC1ø4"S–E¡4eè,:è#Ke’®ÑÁ2nãFj—‹XAó=_bÀ²¨3ŒšØk6›å¡h’©««~ ­mÔíWÜ€&EEV€g«Â£üHû…üh„TËù:4´³€!ƒ\£y š‡>ìx¼«öD ÎóK¯o¼æ{’~74šÄÍ•œDŽP¡ù¾ù¾Ú:á²€uIÔ­À9GÅ”ô24›ÌäDœûLjèô/Glz.~±^(8‹épSЯÝÒŒUØÙè>lu0l´: …Óø¸ˆS'Ì׫déÕR¼Ônø¥a¡ÿ+Íÿz¦ðÅ~åÖÙ±ž4ÿ¿‚Ô†¯ÏP%úÝ2ér¬¤=g’fUP(S»Y)iŽuMÛhµ6öR#É[B{|:xOB2æG{®­ˆv¥‘\®'xduR$Ü-‡ZJ†xáOs>á|€ì,·¦)ëhUHbaô:z>±eå½í˜¨-oÄ5kYdìhc;˜[ࢅ+9oñ¢Vï1%s›:u°Ò .69*î{—“Ï”?[Ùu-±º=v~SN„;»RCÈs¤œ ábù+¹Ï)<# €¥s§ýDçÈDåðÄRþfŸ}u:³16!µ¾ÑiFî&ðZŒÉf.u aìLÛWjm<«¶žåQÓiN&\ÊŽBUáìæu²'•!;‰g§Úñ˜ìs TŸ0dó¤²GžÇU{h{´b$ªÌê³Nrlyh«šXK|IEÉ <cO ósµ„]Ÿ ”¼eoa>#½Ûþ†{«ÏŠ”o“=½( q-{rB›ò1€Œ ŽçÊÁ˜³=B{RŒÜ¼XЀKÿŽP&õ©k‰@Ȫ<ÅNýî¨*.ÑV6áspÇågêß£Þ úäÈØ\K1§ÂhŽœ)03ðAÙLÇŠ‰h€N¸Gö"D,˜­Žœý—1ëöŒ¡ª‹ ÌhˆxˆÈ€µŸˆeuè¸öq¢±°÷R­_겯ÔÎI)ñŠe9ÎÊ‹°Ï%ðgËè©îÖV±d&—kÉK™û áŽ,¦QçíǪÚåñ!Ñ#ÒÚÖæ~ºáÏøª ò£Ór­fÄ[fµ‘Ú-gP@” |ú9˜>Xæc墽íó¥lÎnD‡Iíh©ÉØ/Ϊë1<†/:°2X[7þáäëAcÓ¡Vü†Œ~JqÏèrÝôF“Fð®Y.˜Jè£ÉÜ¡¥¥éqôµÎ'Á‘×èPl"EžÃdA¾òܳö,é^¨…eÌ€6¡”8; I!”ÈP3$n0_™ ÑàH{“$È3PiÀ‰?ˆmR´±v¯-Áˆ'ŽÆ²dÄà-ýeZ(­5qj£ìò=#Œä-Øg0VØ“i]'¯˜ 0Tq 4×ÚÛ›“ù2HPwP%ÉŠ§@%YŒ13P,òr€Õ©ƒ'"q :@¤£ Ä»˜ä-ºÀ£”¥òL=ÅèºÑÅšh·ÙìË[|­Ô{G»÷ÄUòESGÔ}êàÔ8Ál8 ò~Ù¡dè`KW}øR,!(Wcº6ôjI‹ŸN†”½çÐ+LŸòRýâà\KöšÍGzkUÐ±Êæ¡c gèO&÷Åf[sû8ª’¸¹5O,YeÌ–årÖ·©ôÆK‚ãXí ¬/¦j …}¤!7˜è—´Ã™¥™–„õH‘ðW’SþU^û@Á÷jÏRÿQiXÔj+ÚÄ™¬‚f {YÅg‹NG‰ 0“A=ÒÂÅWL¿ÿkn·|þ<öIËÿ=Yqý¯‘ü‘û? ŲÉÿ]Ú£üß•òsþïßâ³û2óÇð¬ã^ýgÍzþ³WÜ©`ÖîÒna·øÚ+”ÞìÞ ¨ºÓ`¹ò÷ ï™—x=Dæí`•bRïöv‹¯¸÷¦òúÍÞž·¸1µ¡º{›cvˆ—¾¦Ìáex—Á=˜¼-ÓŸÏ&ÄÊ(´Wó§àø³ Z˜»|”,üäÜ̹œÈ&®/.CåAe¾F!/XÞ€Òþ wÈùë©÷ar? ÈŸ«¿þ%„;Ï"Nþ‰"f¹ƒo|ra¢šœEƒÌºÉJ'OÛUhVÂ|îȵ„mqM‹º¾Xgsóšh. KÕú‘bŽi‚ 6Œèv¾ ÔÈ"+5 o*ɼ>5X,'¸Àä8œÖk<Šlºlooë6Þ5ÚýÝDç\Ì÷ový çóýƒ¿»û‘üWö9kËÎî1“s‚œwÿ.íÁÖUƒcÀ'Œµýnð~ ®ȸOQÅUÑ»¤‹õ>ìVû²JsÞåúj<¹GætºÓÝ9]/×°éçÃÕë"]ýMEéÈKg¨¾Z'¬æë3¡½'¼Æ°î¥TPöO­Þ[ΑSÍ1nþ­7 N¼%°Túî2²Rî/o¹DŽ›³p"®³\.GU3üãÕ+þõ3üýÙ#xÙBl¾¿ fæÿeЀ]úÅHcî;N @îQD“Y" ··Ws^¢l~Åà¡ tš¹ã[uÀ^}o½í"UAöÅ¢ð¨pD•¼¿H­#ïÕ«Z™ÉØËþ§æ '´_ 3f2—ËÀ¿1XʼÌÊÀÞJ_Žž„;Š5LÃ\ø¯¹ßqlyE«î:âëÙÒìÓñ×¹û?›í…ýJ¥µÿUJ…â³ýï7ø%Yö?œu2Ûí‹å¯´[„¯Å7¥ƒ7Å×òç‘åÅÛc¥Ç“ðÚÛ¦%\Í çÜxÃÉ‚. D”Å YK´ðô†×³`B"»kúC[¡×žÓÐ;[Î?ƒhø—ýÿ_ „³`µ³žM¶ñ:Àõòjg|Cšnº½ïÙ8÷lœû2αcÌ—XèlºWö¶~ÄÎæ¯*—d;“눞`?ShMµ¢ýìgÕóþûN×µ–ELeÉ6¯§X˪‘¥ä—YË¢¦2êW¢±L¥mcé†1†ñe¶±4ÃÂúBÛXºaŒ pO¶¥Ƙ ¾Ì6–`C0¿Ì67ŒÅ󉶱¨a̱uµN,+—þV µ‹Yùðð~•ûÉíúÖ» ¼i0»ÂÍdìíí³ecGB³Xmœ‚« €éÇ*¹0<åMT¬H;:®Üªµ¾„Z!]FK÷Ú¬È JVsr‘Ðòt“L°—jÇfÃåƒ8.cžÕQ¬(,»Å]¢¤óÜ€ªÔ}£€™v¦¸Ï¢}['èëIDOEO«¿m|jÁôd³Tu»”{YÉaÝJåP›zèNzÕ[rì»_Yv2å\¡ŠÔ¤Ä?¶2ëÈù娽ï+?|_ÚÛÿ/…Ú>žßlt2eξ§Nx¯¼’3Ýþ£ÁpuÏæ·® ˆ©8ÑËv…9'ÒV@Ã*Ö@ÌÁTÿ JÐqz?À|R˜©<ÈRÂòÀË9¯ƒûlz»D§þQQq@ ]?`jÍxj²ÒYô¸2ãsäŽ"•GÁ¯¨l†£kÇÊ8c²a_#‚~hµTܬR`ˆ­'Âx¬ZÇ+€Ši„5iäÊ,~a‡±4ÌÆf\F+À <©‚ÕÄðr ë7Vó02kN× ´öÔz¦âðrø¤75Ž0žÒxRGvQU »BS]Š ®v·ÌM7À#ÿ¶t7_޲¦Ï³¿#ÿüœRôÿÑþÿZm •ÂÁÞ^šþÐÿ*¥ƒýÂ~™ôÿ2<ÛûZØôùëÿ›æ—\Ó½èûO¥X9ˆùíáû¯1ÀÇ>¿óùwü¿d¾ÉTÇ­×»¥¢W,¼©ì½)•½Ùäfê‡É®_ví"ù}Uv‹…ÝÒ¾W(¼ÙÛ{S)yë‘ãøµäùõºŒj/†€¨åV‰eŠÞ—8…=›‰žÍDÿšf¢g®g®g®g®_éÃ%{ofogßößªì– »äKelZ™Aºã¸n©PDÒü%|w1YêÎõ7[‘‚Œ…ÆÀ*=£«©¨u@)`ñÙäóçŸÿI“ÿCTÂéÍ߯7£ðoã_ÕÆ—éû(ÿïïUJÏúßoðÑóïƒ`\/ÿm|Éü‹ø¼X*Nø<ÿÿüOtþwIä±ÿ«y<¦ÿ—÷÷ û–þ@öŸbåYÿÿ->Žþ¯çžtøƒÃxô—8¼z¥uôW¯0~«œw£¸¼De?^Ìó¾HµO÷òž5ûgÍþÒäW«öÞïS·÷ž•ûgåþY¹W;æÙ|Éü3ÁNŒ£>¹š`VøÆëÉ‚ò£ÕæËÅ|é¯ìåt¦W˜ä`ÊûÎóÖöÀin4ÿ "îa|{ @/Ö< ¢»$°PŠ©Æ« SWK¹h2¾;ÒÞôö¡®üäýóf«ÎªWt­Å¡ Ê|11©yü•ŠêR o#J¿9hmó%ñ’2–>`Gpÿ•Û%IBøªÚ©(‘]Ä›a)†—7¼Ž8_"Çk$3¼ÄÏ^Í×›ïšxs¬×æ-¼Z§{¶£ ðía‹G]4ûï‘T»¸l£ÄiÈ9Î/q¬Ö•À»b=%é:ÛéV)³pÜ‹Nó§8ÛĦï«ý^‡˜°üóVÇvÒíœÆù8! r¸Fþqd¨`XHk öÑ윦¸N^s á ʼnڒV™æDs `‚.`á´J€™Ciz 7ØCnÐäÐ@, àxâÍ•¡wêc'\3ÍÙPÙÏŸTÖËâ’qpŒ‘’®ºy|šã5N‚µ^1WñR$ é_øtŽCC¶WQ„äò“DfB27°‹…¿ô¡³‹ëP vOb+O!#¼ó±i¦Rþ*)ôÖ9m%q‹˜ÃÏ’Â;m˜géá¢W¯¥fP¢±e0çcÂS„—¸3 G8!1FZÎ(%ÚµO¹º GÈÌæÀ¯ò8,™H¼5`r{»ž!ãã”ë ñ03§ Ÿòêå1á;Œ,¯CizCŸo¨½õgë±Ï¹(ZJä¸Y«W|i/Þ÷­Ëèôqcf’¨ *ëó[T„(kUï!\·K]X»^Œ|‰í×Îv›gÞ,Xa€.ƨJBRaØ ß˜7}nÌ´'Û–ÚïôÌ-(IŽM½aeDçswgÚPmæèyâ¢8û¶%Sñ#ÞÈ¡Ÿ$„ïˆÉ¡‰R'v(9ç}”??9 ‹´$¨_Â%#bá넆ïªggݧ¹ýö˜Ãnk®º½½åã}ðœE[H¢‘òû»%”ÏüñkK²EdÑûèQ8ô •7{Å7…’÷3ɰ)%âwŒIýArv|­g‘“dq·xþòŠà“´ðÙã^=—ó~úÉÃoÞ_Hf,ìç¶þà9µ/'”•êÓ0ˆ¾·Þ’?œÛöZ ùìg2¦¾WÏ?zón­ñ$Aï2ø<‰&ˆÜ1¾K2Ì/4Ç…Žëü|NOëuIÕIÓ‡á—þL2ˆûôÈÇ+çó¾k%ÎÍ™gOÔM<³» òU4w ‡/,w¼aÝN™S}AH Žü½o©Åb¡’{±ƒ– ¾«ÅÛÛ…’)G7 ùÈæÎÛÜ'É\!ó†ùUw@=Ì×xÃC8Ÿ~fÇoܰ‚;ÐÀçë«kò9–4©¾7d UY0".ÙÜÇLI¯pX(fTò€1&äU©à•»°ˆ½Î•U¢ÿnƒaœúr<<<älª‰³jíÛF¿÷]f¯X’„ì¿Àt­+ÎÍm•?­~¬#gŠ…Òž[wÊæ3b~ ÅÚ5®^Úsk#º1ùt4½n«zÜheöËNMã¥/§þe0µk¾?i~lÔaŒ2Ä?ìrš¨BY%ôµ×O©TMÕ¿ªª•Í5\#±Z»]ÝhasU¤,FvmPŸË%¨+ÍRöYÚ5C-M”KÛ”bæ¤TÀšÅ}¬YJ­YÜO©‰—‹è¡6Ï>W¼þ êÙ—2&À…ö±|,dÁ4X) „êÜ´R9ÝmÂaC€fÑÏì•uB1¼œh¶š>˜‹ShY ú°au?e2…{Ær¨|ÁcÒÔ%‹œ`‘aÇ öúÕþy 2 ­A ëú:4Uv_ªJ÷÷÷X£ür—êÀ7mËvFÛ´;ýæÉ§Aç Š3¦Q˜?`ªß9ö ä½NUñA¦GˆäQf‹³‹—v§ÑívºÐ)iCÒ¨XEp+†BÀ^©Üb,K´ÎÎIµÙŠTÈEóN“ëÓj³)K“3¾ûEEYšNû ) G¹\gˆwqÖmœ É(“a†ÁÈ_c3 ÑÑ­sÍÉC‘£5žíÖXkAЭû5fg—;hÜ"ížZJº7YQ†s…§ì)tRñ¦É”n4°~)w¢†Må · ÑŽ¢ÐjÄ'eÔæÚ±([ìÃV ãØWä¿ä‹›¤³Þßñnh·ùãLæ@79¿Oã¸Pô]&shzzµœ¯ Sh[Èèµ)tаâñÖ§Œb‰³5žüÈ<%pÁþàâ[@‘çèoø¡+1 ù¨ÿ9eÏúHÊJ{d# #‘¤åÐìÕÛ° xþðÇæâÀÐ3%ž>DCtÚ½*ŒªÄs†?ÒÖ`õŒ¦·T–ñ3ë¦Óùüˆ2;‘/Àä<£\dy4ˆKa^Ã5­ Œh'‰Ë-ûmv‘ktY s) f¸Äóõq§R(ð<ßú‹…›0о• ó¼]s²cÐ:^ÌC:Wó²¸/–þÝ,ÒqÜU¡î¡ì´û˜„?ŽœV§…xþZꎷf­µ‘ÖÊ<}”~ú¶#ÖËaÁ|c&25žÂÆlD‹I],7‰Ígó»S– Ü‚>ÍÏÍ#%{ݶ,Mñæ Â0Þãjÿ°PæùƒÉXhWiù—yê`88UÍÏ Ûý ëÖéœ.*<4øî-Â`=šow»y¯{R+í)ÆP¶2-Y80Î…7¥°'-½,¼» –¹ôŽH-ЦN³¡.Áˆd¶-rÞF• 1Ïg*Œu¤¶ò¬ãÂÏ©±ü•öÏ•µ[ª{*ùÒ mR‹0f'q0‚']\*J˜.ië¦ gµôgá8JU©ÄÃÒe¨†³i¹ „c¬Vv«)NïŸTÂÕ³W‰Wò|tMH®Wm`:º›LGCœsÊ“o Œœ1œú¡\Fä*oµˆB"_¡s¹ð%õ[°²­ jï«È&ÊZdÑoŽJ#Ìái³ŸsK¿ïÙzÁû”À‘Ê F+‰ëd?~ü™uîë/žvhûiè9™úWê‚&±ÛgÈ["-ÚRX^œ´ªïýOgÓjï[‹kXN»xyä®{äú"–>ή:ÀØþA­Ó>A]@| œ{úꚺ|;­>VtÚ­OPÿ0¹>Èçcf}d ØÜUI…¤Ž€†QîàBjwhK2(iÏi;² ±ÃÀJø²Qíò%|{ ™òñBÙiÈ÷«#ÂN¹"¸ºwa…¹}¼ ¯¢Zo’:ýÁÁe"8B ú¢Ášª‚"pðƒÔøxÖè6Oé„U6ÖS›÷(Í?¸=¼DÃ7 îm!©S]VDëeÔ%ß×Zܶ¾//·ÙT¿‚xøåõAËéVkµÎy»*< B‡…‹—ÉÈ]Ž´gøÃá* Zpû¬—Ra V=Á ¿ `¾ë´$œ¢‡ø5~WÇô9Ð( Pó¬û-ËCFŠÀÁêô’ÌiYê{ð,sIãBžÎp*181và|§ -¹å6Íè ©ô‹§ÔPüb ¶«ýN÷“°; µ…]\ª(›zõvøåŸÙ­ÌÖ¥43ö¢„bÒËÆ^•½Ÿ8a]K#Øo‹…ŸâÏŠ9OÝäU§W¸k_߯9>ªÞ0\ ßl è M¨ÿ(îìÀöü³¢jë]§Ûì¿?]z¯Û«fÈä ß™háIriàÍnƒy.Ê Ì)ýéÞÄe$ùĺgÝæ‡j*ƒÈ…â‚ð¢Ëàj2c£™¯èºÄ!ý!\•Ú¢§ô•¸MQ Á#A$éà^gU´;š¥ ÙüÏw(+\ÆÛ+–L©êÇäR°—t›ý9 _”F“„>lÑMç%-«Ý² óøS¿RE6›ÖÖ«ƒÝÃÜËÒ«rN7xêß[¶WV\'W’:,ÒµQí5öAnÁü®IÍ¿*åv˘BDI Xï»m¦*:sÄ“ã1 Ž!Ê äúôÆÇ>j½#[Ô¹‚ÖcÛì|ëåŒ fÄ8” »²¨³Ê1Á†õaÐ;¯Õ ¥LÁ}ÜùÐèž´:™ìv1ç¾:®ÖONûð¢Qû¶w~ ¯ÊñWÇç'­FÞ!>Ô©âiÐéÖ]>(ÂS¡oÞR8m¡ì ݲÀ.Óø3ëdˆx@c_ßÛåcå¤;­f¿ßjÀ–RoVÛ™b©LërVÛȧè¼diJ%Ì™ ³Ÿýû¼·:RëqóR)—hU㈩0šÇ§y” gõ3¤\)–HãíKX^˜ "ïö.Ì#Lêìe£ENNç”í{IGkê÷,,£ðà< ׳òá¾óh¢ü÷çäâǧ¸µœ’ƒ„g°”ß5ºàTƒnóÝû~¨ÁÀŸ.®ýÁÀ…ÆO ¡ZÈ8“¤æìÁ‚ní@[<,ß·ÃýÃØàþr˜Ð¿I¸xã”\ù×óÀÅÒåm¡\Š>*ÒPwœ€ºÈ3 ‰® ²þîü#>páù ôê ÷}§µnõSâ \/~ÂõâµÂP¼4¾ ËêIõ<‡Z“¾;#ˆä¤É6+Içê=ZÖ´bô#dî JX{æ¼Y¿³úk=õþó­µ’½?ýÉsß9„ˆ¯‚[Ȭ⩦óµxf['©â½#=RZ-}K3ßÂû’ÏYñÜp2…Ûìî#7n–´1):Ù š¬úÒåª t]ù;]Îo/½5ÔœRÿÆ“{ªh–8óù>¥Û¸éÔä…¾Ñ7ýÉì3×Ìð_EÎ{úÒWÜŠøhƒ!Õ›úfßk¹"™ä(aïÛÊMJaa[+ty¹ÊÌÊPŽüu×`R¹óOýqväÜÒ tà‚|y¬gœcl2“{Ÿ—º›—4Gåþ…h´VÛ½&ìµ?®'K¹¼—®ýäßx¯åü6 YFoMΛËÀ_.ñÄ íHrɆYÁ$¯~û¦¸d )«¤¨|ØHÄ?±·Nï­MÄ[*“1£–î˜[Žtj|ÓÞË7^ñȶ¿$Џ¥øˆòW¡dÊÙ{„¡éû¦²}áÙ’? ï¸ç¦èj¨‹.fdèNºYÁWîµ 'KVÏ¢íƒ ñÒOä=‹4t§ìzFÇqoŠ4nùEânöôø;$ÀAeçõNÙ/ç"H9H ÃŽ“›V+M¡©1¼†7Š‚½¢X å{…å¼á™qNN ƒL —§Wt_nödJZ2žÒ'Èšt]ò‘I÷}]ò1JbUˆxŒD\Z’ý“)i#êíÄ~9ý j]ú “ç, ¡¤Œ”¹t­‚Y¸ çk¼eù‡ªœ‹ÀÐBbb†õDëù³´z2߉µfaj-mwOnn™VQÛŠ­z?{ïUXG®;+[×°éL9².ÉÝÇU¸È;q F’aaKî?Îè$o™ÁU°¢ÛctZ8¹œöÈ)‹Ùä¨,jñ¢Úk…ä};(Œªì.Ô 5»X¶²39OÆnôæÂ¾žo–KQFÁ-]cïË-ê‹åã8þÅ(ÒB|Þ.)¤dˆŒPzP/¼`\‘~=ÁkÕñR÷ùêzÇAÖ»F¿÷¾Óíga;ªÛtÆ|5ÚÕ`ˆw+dõè³P–ò×gCLZŸÍšT…XöûÂxÌ!åÈGQî§x‘âú-CÂæ_½UžH”Ëîg«Ó~—þÂnN­nR^A«›¥J¼Ÿ¦L‘Ê÷7•)¥ ×)§]¶"Ã=;ß<-*]ájò€ja>޾7ôêÕ+x‰Ð¾ùÆ;4¡·ôø 3]Þ4C*;ãj0Õ=&ìÀÔüšO±Ç¥Jòsà<‰Ïã#œnš+·‰ñÅÅUô?ÅýË?±øÿU0Å<û_1üÿ‘øÿÒ^y¯ÿ/ì?Çÿÿ'þ_ͽ¹¢¸‹ÿ*þ_Þ³wºíh”œþÏ©\‰¦ÿÛSÙ¥ÿKOð•ï}}Nðœà9Áßÿ¦$Ï9žs<ç0 þÔîšwfõ­vÃMñgÙjµ°då‡Á…pI¯æÃùÔÑ3ÍjM{ni/ ´uH€”ãðS„kç9´€ó%©+‰ö拨kO½£=ôê•§zÉ…/<û6½»9ÞךR´Ùji?Ã&í“KöŽ¡\!>¾p}9 ®`W‹¹#½«fJ•×zŒÈ,•o1YÆ'Ž7l¦©°;n°ô¥Ð£2â…Û•ƒ”¨ùC×:Sý8©°_1¢»DAÄN!@s…'¶otö_¯ëÕö6Z¦0`˜ü+¼˜OîAгõ*9Ëõ‚ÊbÜö6ònÆÁ%V¥ãn£ú-Ôãi¥Ëîœ)?…wâpæ6ŒœÞÞÏ-Œ«Þ¡ëg‘k$ÛìKQâ>Î6à O!lU8s^òÔyÀ/(¨³T~íÙ©IÆ)Š'È’ë'†òÞA ¯ ê1•Êâ*O¬8ý:ïAŸË<½uˆ'+Iåî(·¯Ü¥9Úk¼þžðÙ qô@2Þ·i;ôô>µkï5FiMS Q&ûP±ä°Ðk§ÀEÁ¦ÃÛQøý ÿc+óÚz‘÷^`§ñ#~œ½ÈÃû^Á4àõÓj÷[ürÌÿ5¹N‡þ~âz5ªÖháwU‚}ŒqmÒÿ°œ V‡ÿò/`8X¯°õó‘x°Q΋ôÚŠi÷bÞà¤Ùíõ §Ñ7­*¼øÑço³÷¹L6«o_ZÏÁ#¼6ÔªÊG“èï/ù.IÍç"M |Õsø¾m—ý\‚˜Ó r½‹­ÎúÓ´aÛÎGÝ!…âÑêYøŽwª*ݨ½ï°T0¼vüɤ@·¤Il¤{äÀ¢‘öi&”ï×#©p½ ¬w5÷|'Çû•‰7ã&‘¨gØgf3ˆÇ;srRÇÀ©H¨hŽƒ`ô8Œ8 J Òñ†Ò— ãù€ãÙ{£=iáhö7Llëä «½Z³‰ÁY»/›šAE2}$-ÆV爞ªÅ,4óéü [—=>Å8­]¹%žNJÕ}ŒÏR{2žÌ€^JŽþ4i±žŸÕÏÏ2ì“,a´^h2µ8ôù $æRɪ$èJ¨Ò®cTP‰™îœÓyÜ5\qô{Çp-â¤Òkre—…ý ƒµÔ–ŸH­¡bÄM‰f¹_­Uk=ö Žx ÄëÁ8O»ßbüÔ”"“ŠH4¦ï4ÐC§óz°–C‚[¯\:(€Šˆ®Ë%š`|à¡ý6iØËgÕ:qíbüYTÓz"ç¾èaàÖ.Æi€Ú—Æ­û R6ê´e÷Dœ “-rs,—-n<žpaÉ Ë£Úài§ÞȰï` ^¡àõzúŒâÈhn?zu±Õ´âd£êtZõA£ý¡Ù…²LóÜ™¢/vcöy²œÏ(Å—vXIF5 ò\ãÜVå”MC"õµkÝOgýLù…=s_ÚÀÚ ÓIšèvp÷l|융H‡³ͶÛdªCÑÚ\¯—É_Å»³„I,e„I€~¯*¿**±’åß“¤¢ šgl™y×ñªxúKâeõ´ÁÒ&Iø¨ßöxi)rgüͼ–DÝOýt£ÖíPeJ%³ßý¸èB«-°:óÍc®'"4@%r¡±IEùµÞ&#A·QëàX Žp`Eø^X("B ˆ}PQbÌð¬rÞã’V- W$ÕR+ªÈ"¢¦Zõm™t’êZgÑži™h\˜ÂJzwéårGš±‰ª)aÑ\Œ¿n–פ\¤žáD§"„ë~ý`¹Ø¡)ëã.²]Y=¯¶ÍËãRxŠr-‡óÎÒ8mdq#„ˆq¡ÄReèoìpP`°Ê×s“õ’ tgÍ@z©3co@«ž¯x e(  ‚¨j+-È©³Î#<_U£ÀGâŠO¯¤ábH£î’΀÷]’ïDyL«ü±ÓæÇ*O¼÷ÌÙ•Z0H'ÂF·\,œhëÊíG'îEµ['Ï”’ý¢×ªe2eSëõ&ÅsÝÇýnõ CÓ1JË}S­QTPEoôM‘ƒ~õ˜‚ËÜW­¦¨‰·i]ÜgмÚsTT8E„òòäX2ƒížÏBÌà I(ÓRd!pƒ$H\˜û†º!‘gî›fûãa9°Í}¬P½;Œ¾\׉È3cLØ^Rtžƒ´fÍ >jžáœØOªÐÅŠûäS?“Ùs¡p™ÙwK‘±è R VÈ¡Û/4½vKÕ0šÛ}„y0ÜÎ7.0…óˆ²>¸½oµacåÉyø‘–Ü^ä¬Þ¢;$Ý"¨*±‡%ÐAœ‡§5ìàëÈ3 ß.Dž]€LRŒ>ɼyvÜ‚åÈÃ>tÇÒl÷Ð&ç#l@‚`»™ |æ¥A-Gf.[ÕæÅ[Ð1Ì0ìo05KÈÙÀV“Ë ¢æ½»ÀSF;€E¾$ä5¨ÓOunÊçs“±UŽÓW*G×F¯Jµš°}¾(ìTþˆ}íz2hÙÆ>ÇØ I€Mx ¿Ît%")’XB@ÊÁÿ‰ZÔ—’.¤ø¿ ùy!¿QžËè ïhZI©ä.ñXM•nsáFƒadäEA™>5Š”¼N‡²2Ú8L1=ZuŽb¸ïy„¨mR½ŒÉ.l=ô h\t‚0ƒÔeÂ9 `úrÿC¤• ÖÈrÜW«¶ûxZù®a~ª]:MŒ0Àzã¤zÞêG˜`«ñ¡Ñ¢0B`»e·å“óv-Ò(FÕõbíµÎ±1—IÂîE±w‘êç½÷Àã1Þ;þM »m ‰>‡C²5O±aH$L*ôK(ø¬Ñ«YûÊh~t²ˆN•¸ÐÅõœìúVÈûìÊ»ƒ§°q:k›²\¼ï j­&€¹ªÆ‚X¤~,»”©ÍÉÒD¾’Ô)‘Ú"Ü%Õ&qp!½÷oɳW|€­˜ýÑ|Ä{ÿžd»Æà¢úɳ~uzÞ©ÓÞÍ+j×"|Ã)(#T!þâÛF÷¸Ñíô*Öä%¼Þ‹4H¯{˜¤§JÚ·öî¯_ÔÚ$Ä_ôA´Ìd^¿Öˆe³ Äš¨\>ôG†¼ÌˆûsôÙGâšÐ¯M§%Ñ6â‡&£IÜÉhVž=ä\ru¬iñþ—ËÊ"¥žtׄYaTµ*¦ò¦·˜ o¼ÀIȤQ²T5Åö˜Æ›tPs‘êaZ}ÖœX‰hâ9Ú³‚Õzab’ZE"#êÒ-§R“T£¤ëIë®ULUFµÏI ª„]m¬Ðmü•Ücm…<þéÈ—õ5V×ÐhÕ¦÷£•­Ž¦T…ÿ1æºY·Yõ®ÞPïcïh}¼Þ²Ÿs²ÌSˆ=®7zƒÚÉñ~Åa¼æ]‡Þ•b神²^kªu{¹Éø"«ò1¿Ò!ù©€ž!žÅ$CÖŽ.zÊôúÛú…z@°\m›Ð@§™ ýCV§îEšlp%=üà_þ•¾À#£:нM^ý±yzlõ[ó™K¬Œ53nolÉDMÔÚ‰3»²j'Âcl'míôò¿ÛGûŸù‰ûW‹¯êýýèýïû¼ó=âÿ]Ù«<ûÿ×ÿ›æž¸ËÑëß÷ß*¯VŽx>ÑûûùN÷g—ïg—ïg—ïg—ïg—ïß¹Ë7m¨™½JäJ÷èîÞ Ò?sݽåJnëµØ“É4€’ËôL¹éd›öv±\ιÆäÆ»^ó;L!^´œtÃàŠ ”7ˆ´pÆ×=ð˜n÷¯LUËþˆÒxnÎÀ ,Sâ2È‚¤BH:ÌV«û"wLØf&<ûªp‘gv”YÊm_ØËˆõ’|Ž9À' É ú¯GKJbâl¯œÿ€CøfÔóŽÛÔ°«¬”ÓŒbß9»Ù8ûS†Æz“É —áj=_üA’ 0zÔ-øŽŠÿŒ]\mIµë"Šji¬a>9jƒó„SÐý‘QРõ¿¬wTïíר·Œ~¿ì·Ôý–~Ù¯oëŒê—¶é!Gh§qÞ®7NÌ%úØÃµLtú's:Xç„SŽ5Ï×3·d•³¯©ŒüCò¿þ<™Oc\´wV­5$©.ìý7ÞóÍc~Ò©öÉ î‡|ðiW=®Ö;úf¼.ù –®H#øãMAù¦¾Ÿ`=ãõ:ã°“·9Óh|löú=•°ŸÆªœÂé¶„02 4K«|ý˜Ë/±!ç5b ¶²Ì,ãYU~þ<ž?ÏŸçÏóçùóüyþ<ž?ÏŸËÏÿ‹7íP¸boxbackup/test/bbackupd/Makefile.extra0000664000175000017500000000120311047660153020625 0ustar siretartsiretartlink-extra: ../../bin/bbackupd/autogen_ClientException.o \ ../../bin/bbackupd/BackupClientContext.o \ ../../bin/bbackupd/BackupClientDeleteList.o \ ../../bin/bbackupd/BackupClientDirectoryRecord.o \ ../../bin/bbackupd/Win32BackupService.o \ ../../bin/bbackupd/BackupClientInodeToIDMap.o \ ../../bin/bbackupd/Win32ServiceFunctions.o \ ../../bin/bbackupd/BackupDaemon.o \ ../../bin/bbstored/BackupStoreContext.o \ ../../bin/bbstored/BBStoreDHousekeeping.o \ ../../bin/bbstored/HousekeepStoreAccount.o \ ../../bin/bbstored/autogen_BackupProtocolServer.o \ ../../bin/bbstored/BackupCommands.o \ ../../bin/bbstored/BackupStoreDaemon.o boxbackup/test/bbackupd/testbbackupd.cpp0000664000175000017500000035452211512153225021233 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testbbackupd.cpp // Purpose: test backup daemon (and associated client bits) // Created: 2003/10/07 // // -------------------------------------------------------------------------- #include "Box.h" // do not include MinGW's dirent.h on Win32, // as we override some of it in lib/win32. #ifndef WIN32 #include #endif #include #include #include #include #include #include #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SYS_XATTR_H #include #include #endif #ifdef HAVE_SIGNAL_H #include #endif #include #ifdef HAVE_SYSCALL #include #endif #include "autogen_BackupProtocolServer.h" #include "BackupClientCryptoKeys.h" #include "BackupClientFileAttributes.h" #include "BackupClientRestore.h" #include "BackupDaemon.h" #include "BackupDaemonConfigVerify.h" #include "BackupQueries.h" #include "BackupStoreConstants.h" #include "BackupStoreContext.h" #include "BackupStoreDaemon.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BoxPortsAndFiles.h" #include "BoxTime.h" #include "BoxTimeToUnix.h" #include "CollectInBufferStream.h" #include "CommonException.h" #include "Configuration.h" #include "FileModificationTime.h" #include "FileStream.h" #include "IOStreamGetLine.h" #include "LocalProcessStream.h" #include "SSLLib.h" #include "ServerControl.h" #include "Socket.h" #include "SocketStreamTLS.h" #include "TLSContext.h" #include "Test.h" #include "Timer.h" #include "Utils.h" #include "autogen_BackupProtocolClient.h" #include "intercept.h" #include "ServerControl.h" #include "MemLeakFindOn.h" // ENOATTR may be defined in a separate header file which we may not have #ifndef ENOATTR #define ENOATTR ENODATA #endif // two cycles and a bit #define TIME_TO_WAIT_FOR_BACKUP_OPERATION 12 void wait_for_backup_operation(const char* message) { wait_for_operation(TIME_TO_WAIT_FOR_BACKUP_OPERATION, message); } int bbstored_pid = 0; int bbackupd_pid = 0; #ifdef HAVE_SYS_XATTR_H bool readxattr_into_map(const char *filename, std::map &rOutput) { rOutput.clear(); ssize_t xattrNamesBufferSize = llistxattr(filename, NULL, 0); if(xattrNamesBufferSize < 0) { return false; } else if(xattrNamesBufferSize > 0) { // There is some data there to look at char *xattrNamesBuffer = (char*)malloc(xattrNamesBufferSize + 4); if(xattrNamesBuffer == NULL) return false; char *xattrDataBuffer = 0; int xattrDataBufferSize = 0; // note: will leak these buffers if a read error occurs. (test code, so doesn't matter) ssize_t ns = llistxattr(filename, xattrNamesBuffer, xattrNamesBufferSize); if(ns < 0) { return false; } else if(ns > 0) { // Read all the attribute values const char *xattrName = xattrNamesBuffer; while(xattrName < (xattrNamesBuffer + ns)) { // Store size of name int xattrNameSize = strlen(xattrName); bool ok = true; ssize_t dataSize = lgetxattr(filename, xattrName, NULL, 0); if(dataSize < 0) { if(errno == ENOATTR) { // Deleted from under us ok = false; } else { return false; } } else if(dataSize == 0) { // something must have removed all the data from under us ok = false; } else { // Make sure there's enough space in the buffer to get the attribute if(xattrDataBuffer == 0) { xattrDataBuffer = (char*)malloc(dataSize + 4); xattrDataBufferSize = dataSize + 4; } else if(xattrDataBufferSize < (dataSize + 4)) { char *resized = (char*)realloc(xattrDataBuffer, dataSize + 4); if(resized == NULL) return false; xattrDataBuffer = resized; xattrDataBufferSize = dataSize + 4; } } // Read the data! dataSize = 0; if(ok) { dataSize = lgetxattr(filename, xattrName, xattrDataBuffer, xattrDataBufferSize - 1 /*for terminator*/); if(dataSize < 0) { if(errno == ENOATTR) { // Deleted from under us ok = false; } else { return false; } } else if(dataSize == 0) { // something must have deleted this from under us ok = false; } else { // Terminate the data xattrDataBuffer[dataSize] = '\0'; } // Got the data in the buffer } // Store in map if(ok) { rOutput[std::string(xattrName)] = std::string(xattrDataBuffer, dataSize); } // Next attribute xattrName += xattrNameSize + 1; } } if(xattrNamesBuffer != 0) ::free(xattrNamesBuffer); if(xattrDataBuffer != 0) ::free(xattrDataBuffer); } return true; } static FILE *xattrTestDataHandle = 0; bool write_xattr_test(const char *filename, const char *attrName, unsigned int length, bool *pNotSupported = 0) { if(xattrTestDataHandle == 0) { xattrTestDataHandle = ::fopen("testfiles/test3.tgz", "rb"); // largest test file } if(xattrTestDataHandle == 0) { return false; } else { char data[1024]; if(length > sizeof(data)) length = sizeof(data); if(::fread(data, length, 1, xattrTestDataHandle) != 1) { return false; } if(::lsetxattr(filename, attrName, data, length, 0) != 0) { if(pNotSupported != 0) { *pNotSupported = (errno == ENOTSUP); } return false; } } return true; } void finish_with_write_xattr_test() { if(xattrTestDataHandle != 0) { ::fclose(xattrTestDataHandle); } } #endif // HAVE_SYS_XATTR_H bool attrmatch(const char *f1, const char *f2) { EMU_STRUCT_STAT s1, s2; TEST_THAT(EMU_LSTAT(f1, &s1) == 0); TEST_THAT(EMU_LSTAT(f2, &s2) == 0); #ifdef HAVE_SYS_XATTR_H { std::map xattr1, xattr2; if(!readxattr_into_map(f1, xattr1) || !readxattr_into_map(f2, xattr2)) { return false; } if(!(xattr1 == xattr2)) { return false; } } #endif // HAVE_SYS_XATTR_H // if link, just make sure other file is a link too, and that the link to names match if((s1.st_mode & S_IFMT) == S_IFLNK) { #ifdef WIN32 TEST_FAIL_WITH_MESSAGE("No symlinks on win32!") #else if((s2.st_mode & S_IFMT) != S_IFLNK) return false; char p1[PATH_MAX], p2[PATH_MAX]; int p1l = ::readlink(f1, p1, PATH_MAX); int p2l = ::readlink(f2, p2, PATH_MAX); TEST_THAT(p1l != -1 && p2l != -1); // terminate strings properly p1[p1l] = '\0'; p2[p2l] = '\0'; return strcmp(p1, p2) == 0; #endif } // modification times if(FileModificationTime(s1) != FileModificationTime(s2)) { return false; } // compare the rest return (s1.st_mode == s2.st_mode && s1.st_uid == s2.st_uid && s1.st_gid == s2.st_gid); } int test_basics() { // Read attributes from files BackupClientFileAttributes t1; t1.ReadAttributes("testfiles/test1"); TEST_THAT(!t1.IsSymLink()); #ifndef WIN32 BackupClientFileAttributes t2; t2.ReadAttributes("testfiles/test2"); TEST_THAT(t2.IsSymLink()); // Check that it's actually been encrypted (search for symlink name encoded in it) void *te = ::memchr(t2.GetBuffer(), 't', t2.GetSize() - 3); TEST_THAT(te == 0 || ::memcmp(te, "test", 4) != 0); #endif BackupClientFileAttributes t3; { Logging::Guard guard(Log::ERROR); TEST_CHECK_THROWS(t3.ReadAttributes("doesn't exist"), CommonException, OSFileError); } // Create some more files FILE *f = fopen("testfiles/test1_n", "w"); fclose(f); f = fopen("testfiles/test2_n", "w"); fclose(f); // Apply attributes to these new files t1.WriteAttributes("testfiles/test1_n"); #ifdef WIN32 t1.WriteAttributes("testfiles/test2_n"); #else t2.WriteAttributes("testfiles/test2_n"); #endif #ifndef WIN32 { Logging::Guard guard(Log::ERROR); TEST_CHECK_THROWS(t1.WriteAttributes("testfiles/test1_nXX"), CommonException, OSFileError); TEST_CHECK_THROWS(t3.WriteAttributes("doesn't exist"), BackupStoreException, AttributesNotLoaded); } // Test that attributes are vaguely similar TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_n")); TEST_THAT(attrmatch("testfiles/test2", "testfiles/test2_n")); #endif // Check encryption, and recovery from encryption // First, check that two attributes taken from the same thing have different encrypted values (think IV) BackupClientFileAttributes t1b; t1b.ReadAttributes("testfiles/test1"); TEST_THAT(::memcmp(t1.GetBuffer(), t1b.GetBuffer(), t1.GetSize()) != 0); // But that comparing them works OK. TEST_THAT(t1 == t1b); // Then store them both to a stream CollectInBufferStream stream; t1.WriteToStream(stream); t1b.WriteToStream(stream); // Read them back again stream.SetForReading(); BackupClientFileAttributes t1_r, t1b_r; t1_r.ReadFromStream(stream, 1000); t1b_r.ReadFromStream(stream, 1000); TEST_THAT(::memcmp(t1_r.GetBuffer(), t1b_r.GetBuffer(), t1_r.GetSize()) != 0); TEST_THAT(t1_r == t1b_r); TEST_THAT(t1 == t1_r); TEST_THAT(t1b == t1b_r); TEST_THAT(t1_r == t1b); TEST_THAT(t1b_r == t1); #ifdef HAVE_SYS_XATTR_H // Write some attributes to the file, checking for ENOTSUP bool xattrNotSupported = false; if(!write_xattr_test("testfiles/test1", "user.attr_1", 1000, &xattrNotSupported) && xattrNotSupported) { ::printf("***********\nYour platform supports xattr, but your filesystem does not.\nSkipping tests.\n***********\n"); } else { BackupClientFileAttributes x1, x2, x3, x4; // Write more attributes TEST_THAT(write_xattr_test("testfiles/test1", "user.attr_2", 947)); TEST_THAT(write_xattr_test("testfiles/test1", "user.sadfohij39998.3hj", 123)); // Read file attributes x1.ReadAttributes("testfiles/test1"); // Write file attributes FILE *f = fopen("testfiles/test1_nx", "w"); fclose(f); x1.WriteAttributes("testfiles/test1_nx"); // Compare to see if xattr copied TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_nx")); // Add more attributes to a file x2.ReadAttributes("testfiles/test1"); TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23)); // Read them again, and check that the Compare() function detects that they're different x3.ReadAttributes("testfiles/test1"); TEST_THAT(x1.Compare(x2, true, true)); TEST_THAT(!x1.Compare(x3, true, true)); // Change the value of one of them, leaving the size the same. TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23)); x4.ReadAttributes("testfiles/test1"); TEST_THAT(!x1.Compare(x4, true, true)); } finish_with_write_xattr_test(); #endif // HAVE_SYS_XATTR_H return 0; } int test_setupaccount() { TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " "testfiles/bbstored.conf create 01234567 0 1000B 2000B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); return 0; } int test_run_bbstored() { std::string cmd = BBSTORED " " + bbstored_args + " testfiles/bbstored.conf"; bbstored_pid = LaunchServer(cmd, "testfiles/bbstored.pid"); TEST_THAT(bbstored_pid != -1 && bbstored_pid != 0); if(bbstored_pid > 0) { ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbstored_pid)); return 0; // success } return 1; } int test_kill_bbstored(bool wait_for_process = false) { TEST_THAT(KillServer(bbstored_pid, wait_for_process)); ::safe_sleep(1); TEST_THAT(!ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbstored_pid)) { bbstored_pid = 0; } #ifdef WIN32 TEST_THAT(unlink("testfiles/bbstored.pid") == 0); #else TestRemoteProcessMemLeaks("bbstored.memleaks"); #endif return 0; } int64_t GetDirID(BackupProtocolClient &protocol, const char *name, int64_t InDirectory) { protocol.QueryListDirectory( InDirectory, BackupProtocolClientListDirectory::Flags_Dir, BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, true /* want attributes */); // Retrieve the directory from the stream following BackupStoreDirectory dir; std::auto_ptr dirstream(protocol.ReceiveStream()); dir.ReadFromStream(*dirstream, protocol.GetTimeout()); BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; int64_t dirid = 0; BackupStoreFilenameClear dirname(name); while((en = i.Next()) != 0) { if(en->GetName() == dirname) { dirid = en->GetObjectID(); } } return dirid; } void terminate_on_alarm(int sigraised) { abort(); } #ifndef WIN32 void do_interrupted_restore(const TLSContext &context, int64_t restoredirid) { int pid = 0; switch((pid = fork())) { case 0: // child process { // connect and log in SocketStreamTLS conn; conn.Open(context, Socket::TypeINET, "localhost", 22011); BackupProtocolClient protocol(conn); protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); std::auto_ptr loginConf(protocol.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly)); // Test the restoration TEST_THAT(BackupClientRestore(protocol, restoredirid, "Test1", "testfiles/restore-interrupt", true /* print progress dots */) == Restore_Complete); // Log out protocol.QueryFinished(); } exit(0); break; case -1: { printf("Fork failed\n"); exit(1); } default: { // Wait until a resume file is written, then terminate the child while(true) { // Test for existence of the result file int64_t resumesize = 0; if(FileExists("testfiles/restore-interrupt.boxbackupresume", &resumesize) && resumesize > 16) { // It's done something. Terminate it. ::kill(pid, SIGTERM); break; } // Process finished? int status = 0; if(waitpid(pid, &status, WNOHANG) != 0) { // child has finished anyway. return; } // Give up timeslot so as not to hog the processor ::sleep(0); } // Just wait until the child has completed int status = 0; waitpid(pid, &status, 0); } } } #endif // !WIN32 #ifdef WIN32 bool set_file_time(const char* filename, FILETIME creationTime, FILETIME lastModTime, FILETIME lastAccessTime) { HANDLE handle = openfile(filename, O_RDWR, 0); TEST_THAT(handle != INVALID_HANDLE_VALUE); if (handle == INVALID_HANDLE_VALUE) return false; BOOL success = SetFileTime(handle, &creationTime, &lastAccessTime, &lastModTime); TEST_THAT(success); TEST_THAT(CloseHandle(handle)); return success; } #endif void intercept_setup_delay(const char *filename, unsigned int delay_after, int delay_ms, int syscall_to_delay); bool intercept_triggered(); int64_t SearchDir(BackupStoreDirectory& rDir, const std::string& rChildName) { BackupStoreDirectory::Iterator i(rDir); BackupStoreFilenameClear child(rChildName.c_str()); BackupStoreDirectory::Entry *en = i.FindMatchingClearName(child); if (en == 0) return 0; int64_t id = en->GetObjectID(); TEST_THAT(id > 0); TEST_THAT(id != BackupProtocolClientListDirectory::RootDirectory); return id; } SocketStreamTLS sSocket; std::auto_ptr Connect(TLSContext& rContext) { sSocket.Open(rContext, Socket::TypeINET, "localhost", 22011); std::auto_ptr connection; connection.reset(new BackupProtocolClient(sSocket)); connection->Handshake(); std::auto_ptr serverVersion(connection->QueryVersion( BACKUP_STORE_SERVER_VERSION)); if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION) { THROW_EXCEPTION(BackupStoreException, WrongServerVersion); } return connection; } std::auto_ptr ConnectAndLogin(TLSContext& rContext, int flags) { std::auto_ptr connection(Connect(rContext)); connection->QueryLogin(0x01234567, flags); return connection; } std::auto_ptr ReadDirectory ( BackupProtocolClient& rClient, int64_t id ) { std::auto_ptr dirreply( rClient.QueryListDirectory(id, false, 0, false)); std::auto_ptr dirstream(rClient.ReceiveStream()); std::auto_ptr apDir(new BackupStoreDirectory()); apDir->ReadFromStream(*dirstream, rClient.GetTimeout()); return apDir; } int start_internal_daemon() { // ensure that no child processes end up running tests! int own_pid = getpid(); BOX_TRACE("Test PID is " << own_pid); // this is a quick hack to allow passing some options to the daemon const char* argv[] = { "dummy", bbackupd_args.c_str(), }; BackupDaemon daemon; int result; if (bbackupd_args.size() > 0) { result = daemon.Main("testfiles/bbackupd.conf", 2, argv); } else { result = daemon.Main("testfiles/bbackupd.conf", 1, argv); } TEST_EQUAL_LINE(0, result, "Daemon exit code"); // ensure that no child processes end up running tests! if (getpid() != own_pid) { // abort! BOX_INFO("Daemon child finished, exiting now."); _exit(0); } TEST_THAT(TestFileExists("testfiles/bbackupd.pid")); printf("Waiting for backup daemon to start: "); int pid = -1; for (int i = 0; i < 30; i++) { printf("."); fflush(stdout); safe_sleep(1); if (TestFileExists("testfiles/bbackupd.pid")) { pid = ReadPidFile("testfiles/bbackupd.pid"); } if (pid > 0) { break; } } printf(" done.\n"); fflush(stdout); TEST_THAT(pid > 0); return pid; } bool stop_internal_daemon(int pid) { bool killed_server = KillServer(pid, false); TEST_THAT(killed_server); return killed_server; } static struct dirent readdir_test_dirent; static int readdir_test_counter = 0; static int readdir_stop_time = 0; static char stat_hook_filename[512]; // First test hook, during the directory scanning stage, returns empty. // This will not match the directory on the store, so a sync will start. // We set up the next intercept for the same directory by passing NULL. extern "C" struct dirent *readdir_test_hook_2(DIR *dir); #ifdef LINUX_WEIRD_LSTAT extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf); #else extern "C" int lstat_test_hook(const char *file_name, struct stat *buf); #endif extern "C" struct dirent *readdir_test_hook_1(DIR *dir) { #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE intercept_setup_readdir_hook(NULL, readdir_test_hook_2); #endif return NULL; } // Second test hook, during the directory sync stage, keeps returning // new filenames until the timer expires, then disables the intercept. extern "C" struct dirent *readdir_test_hook_2(DIR *dir) { if (time(NULL) >= readdir_stop_time) { #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE intercept_setup_readdir_hook(NULL, NULL); intercept_setup_lstat_hook (NULL, NULL); // we will not be called again. #endif } // fill in the struct dirent appropriately memset(&readdir_test_dirent, 0, sizeof(readdir_test_dirent)); #ifdef HAVE_STRUCT_DIRENT_D_INO readdir_test_dirent.d_ino = ++readdir_test_counter; #endif snprintf(readdir_test_dirent.d_name, sizeof(readdir_test_dirent.d_name), "test.%d", readdir_test_counter); // ensure that when bbackupd stats the file, it gets the // right answer snprintf(stat_hook_filename, sizeof(stat_hook_filename), "testfiles/TestDir1/spacetest/d1/test.%d", readdir_test_counter); #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE intercept_setup_lstat_hook(stat_hook_filename, lstat_test_hook); #endif return &readdir_test_dirent; } #ifdef LINUX_WEIRD_LSTAT extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf) #else extern "C" int lstat_test_hook(const char *file_name, struct stat *buf) #endif { // TRACE1("lstat hook triggered for %s", file_name); memset(buf, 0, sizeof(*buf)); buf->st_mode = S_IFREG; return 0; } // Simulate a symlink that is on a different device than the file // that it points to. int lstat_test_post_hook(int old_ret, const char *file_name, struct stat *buf) { BOX_TRACE("lstat post hook triggered for " << file_name); if (old_ret == 0 && strcmp(file_name, "testfiles/symlink-to-TestDir1") == 0) { buf->st_dev ^= 0xFFFF; } return old_ret; } bool test_entry_deleted(BackupStoreDirectory& rDir, const std::string& rName) { BackupStoreDirectory::Iterator i(rDir); BackupStoreDirectory::Entry *en = i.FindMatchingClearName( BackupStoreFilenameClear(rName)); TEST_THAT(en != 0); if (en == 0) return false; int16_t flags = en->GetFlags(); TEST_THAT(flags && BackupStoreDirectory::Entry::Flags_Deleted); return flags && BackupStoreDirectory::Entry::Flags_Deleted; } int test_bbackupd() { // First, wait for a normal period to make sure the last changes // attributes are within a normal backup timeframe. // wait_for_backup_operation(); // Connection gubbins TLSContext context; context.Initialise(false /* client */, "testfiles/clientCerts.pem", "testfiles/clientPrivKey.pem", "testfiles/clientTrustedCAs.pem"); printf("\n==== Testing that ReadDirectory on nonexistent directory " "does not crash\n"); { std::auto_ptr client = ConnectAndLogin( context, 0 /* read-write */); { Logging::Guard guard(Log::ERROR); TEST_CHECK_THROWS(ReadDirectory(*client, 0x12345678), ConnectionException, Conn_Protocol_UnexpectedReply); } client->QueryFinished(); sSocket.Close(); } // unpack the files for the initial test TEST_THAT(::system("rm -rf testfiles/TestDir1") == 0); TEST_THAT(::mkdir("testfiles/TestDir1", 0777) == 0); #ifdef WIN32 TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz " "-C testfiles/TestDir1") == 0); #else TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz " "| ( cd testfiles/TestDir1 && tar xf - )") == 0); #endif #ifdef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE printf("\n==== Skipping intercept-based KeepAlive tests " "on this platform.\n"); #else printf("\n==== Testing SSL KeepAlive messages\n"); { #ifdef WIN32 #error TODO: implement threads on Win32, or this test \ will not finish properly #endif // bbackupd daemon will try to initialise timers itself Timers::Cleanup(); // something to diff against (empty file doesn't work) int fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); TEST_THAT(fd > 0); char buffer[10000]; memset(buffer, 0, sizeof(buffer)); TEST_EQUAL_LINE(sizeof(buffer), write(fd, buffer, sizeof(buffer)), "Buffer write"); TEST_THAT(close(fd) == 0); int pid = start_internal_daemon(); wait_for_backup_operation("internal daemon to run a sync"); TEST_THAT(stop_internal_daemon(pid)); // two-second delay on the first read() of f1 // should mean that a single keepalive is sent, // and diff does not abort. intercept_setup_delay("testfiles/TestDir1/spacetest/f1", 0, 2000, SYS_read, 1); TEST_THAT(unlink("testfiles/bbackupd.log") == 0); pid = start_internal_daemon(); intercept_clear_setup(); fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); TEST_THAT(fd > 0); // write again, to update the file's timestamp TEST_EQUAL_LINE(sizeof(buffer), write(fd, buffer, sizeof(buffer)), "Buffer write"); TEST_THAT(close(fd) == 0); wait_for_backup_operation("internal daemon to sync " "spacetest/f1"); // can't test whether intercept was triggered, because // it's in a different process. // TEST_THAT(intercept_triggered()); TEST_THAT(stop_internal_daemon(pid)); // check that keepalive was written to logs, and // diff was not aborted, i.e. upload was a diff FileStream fs("testfiles/bbackupd.log", O_RDONLY); IOStreamGetLine reader(fs); bool found1 = false; while (!reader.IsEOF()) { std::string line; TEST_THAT(reader.GetLine(line)); if (line == "Send GetBlockIndexByName(0x3,\"f1\")") { found1 = true; break; } } TEST_THAT(found1); if (found1) { std::string line; TEST_THAT(reader.GetLine(line)); std::string comp = "Receive Success(0x"; TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receiving stream, size 124", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Send GetIsAlive()", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receive IsAlive()", line); TEST_THAT(reader.GetLine(line)); comp = "Send StoreFile(0x3,"; TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), line); comp = ",\"f1\")"; std::string sub = line.substr(line.size() - comp.size()); TEST_EQUAL_LINE(comp, sub, line); std::string comp2 = ",0x0,"; sub = line.substr(line.size() - comp.size() - comp2.size() + 1, comp2.size()); TEST_LINE(comp2 != sub, line); } if (failures > 0) { // stop early to make debugging easier Timers::Init(); return 1; } // four-second delay on first read() of f1 // should mean that no keepalives were sent, // because diff was immediately aborted // before any matching blocks could be found. intercept_setup_delay("testfiles/TestDir1/spacetest/f1", 0, 4000, SYS_read, 1); pid = start_internal_daemon(); intercept_clear_setup(); fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); TEST_THAT(fd > 0); // write again, to update the file's timestamp TEST_EQUAL_LINE(sizeof(buffer), write(fd, buffer, sizeof(buffer)), "Buffer write"); TEST_THAT(close(fd) == 0); wait_for_backup_operation("internal daemon to sync " "spacetest/f1 again"); // can't test whether intercept was triggered, because // it's in a different process. // TEST_THAT(intercept_triggered()); TEST_THAT(stop_internal_daemon(pid)); // check that the diff was aborted, i.e. upload was not a diff found1 = false; while (!reader.IsEOF()) { std::string line; TEST_THAT(reader.GetLine(line)); if (line == "Send GetBlockIndexByName(0x3,\"f1\")") { found1 = true; break; } } TEST_THAT(found1); if (found1) { std::string line; TEST_THAT(reader.GetLine(line)); std::string comp = "Receive Success(0x"; TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receiving stream, size 124", line); // delaying for 4 seconds in one step means that // the diff timer and the keepalive timer will // both expire, and the diff timer is honoured first, // so there will be no keepalives. TEST_THAT(reader.GetLine(line)); comp = "Send StoreFile(0x3,"; TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), line); comp = ",0x0,\"f1\")"; std::string sub = line.substr(line.size() - comp.size()); TEST_EQUAL_LINE(comp, sub, line); } if (failures > 0) { // stop early to make debugging easier Timers::Init(); return 1; } intercept_setup_delay("testfiles/TestDir1/spacetest/f1", 0, 1000, SYS_read, 3); pid = start_internal_daemon(); intercept_clear_setup(); fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); TEST_THAT(fd > 0); // write again, to update the file's timestamp TEST_EQUAL_LINE(sizeof(buffer), write(fd, buffer, sizeof(buffer)), "Buffer write"); TEST_THAT(close(fd) == 0); wait_for_backup_operation("internal daemon to sync " "spacetest/f1 again"); // can't test whether intercept was triggered, because // it's in a different process. // TEST_THAT(intercept_triggered()); TEST_THAT(stop_internal_daemon(pid)); // check that the diff was aborted, i.e. upload was not a diff found1 = false; while (!reader.IsEOF()) { std::string line; TEST_THAT(reader.GetLine(line)); if (line == "Send GetBlockIndexByName(0x3,\"f1\")") { found1 = true; break; } } TEST_THAT(found1); if (found1) { std::string line; TEST_THAT(reader.GetLine(line)); std::string comp = "Receive Success(0x"; TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receiving stream, size 124", line); // delaying for 3 seconds in steps of 1 second // means that the keepalive timer will expire 3 times, // and on the 3rd time the diff timer will expire too. // The diff timer is honoured first, so there will be // only two keepalives. TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Send GetIsAlive()", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receive IsAlive()", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Send GetIsAlive()", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receive IsAlive()", line); // but two matching blocks should have been found // already, so the upload should be a diff. TEST_THAT(reader.GetLine(line)); comp = "Send StoreFile(0x3,"; TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), line); comp = ",\"f1\")"; std::string sub = line.substr(line.size() - comp.size()); TEST_EQUAL_LINE(comp, sub, line); std::string comp2 = ",0x0,"; sub = line.substr(line.size() - comp.size() - comp2.size() + 1, comp2.size()); TEST_LINE(comp2 != sub, line); } if (failures > 0) { // stop early to make debugging easier Timers::Init(); return 1; } intercept_setup_readdir_hook("testfiles/TestDir1/spacetest/d1", readdir_test_hook_1); // time for at least two keepalives readdir_stop_time = time(NULL) + 12 + 2; pid = start_internal_daemon(); intercept_clear_setup(); std::string touchfile = "testfiles/TestDir1/spacetest/d1/touch-me"; fd = open(touchfile.c_str(), O_CREAT | O_WRONLY); TEST_THAT(fd > 0); // write again, to update the file's timestamp TEST_EQUAL_LINE(sizeof(buffer), write(fd, buffer, sizeof(buffer)), "Buffer write"); TEST_THAT(close(fd) == 0); wait_for_backup_operation("internal daemon to scan " "spacetest/d1"); // can't test whether intercept was triggered, because // it's in a different process. // TEST_THAT(intercept_triggered()); TEST_THAT(stop_internal_daemon(pid)); // check that keepalives were sent during the dir search found1 = false; // skip to next login while (!reader.IsEOF()) { std::string line; TEST_THAT(reader.GetLine(line)); if (line == "Send ListDirectory(0x3,0xffff,0xc,true)") { found1 = true; break; } } TEST_THAT(found1); if (found1) { found1 = false; while (!reader.IsEOF()) { std::string line; TEST_THAT(reader.GetLine(line)); if (line == "Send ListDirectory(0x3,0xffffffff,0xc,true)") { found1 = true; break; } } } if (found1) { std::string line; TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receive Success(0x3)", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receiving stream, size 425", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Send GetIsAlive()", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receive IsAlive()", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Send GetIsAlive()", line); TEST_THAT(reader.GetLine(line)); TEST_EQUAL("Receive IsAlive()", line); } if (failures > 0) { // stop early to make debugging easier Timers::Init(); return 1; } TEST_THAT(unlink(touchfile.c_str()) == 0); // restore timers for rest of tests Timers::Init(); } #endif // PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); std::string cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if(bbackupd_pid > 0) { printf("\n==== Testing that backup pauses when " "store is full\n"); // wait for files to be uploaded BOX_TRACE("Waiting for all outstanding files to be uploaded") wait_for_sync_end(); BOX_TRACE("done.") // Set limit to something very small // 26 blocks will be used at this point. // (12 files + location * 2 for raidfile) // 20 is what we'll need in a minute // set soft limit to 0 to ensure that all deleted files // are deleted immediately by housekeeping TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " "testfiles/bbstored.conf setlimit 01234567 0B 20B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // Unpack some more files #ifdef WIN32 TEST_THAT(::system("tar xzvf testfiles/spacetest2.tgz " "-C testfiles/TestDir1") == 0); #else TEST_THAT(::system("gzip -d < testfiles/spacetest2.tgz " "| ( cd testfiles/TestDir1 && tar xf - )") == 0); #endif // Delete a file and a directory TEST_THAT(::unlink("testfiles/TestDir1/spacetest/f1") == 0); TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d7") == 0); // The following files should be on the server: // 00000001 -d---- 00002 (root) // 00000002 -d---- 00002 Test1 // 00000003 -d---- 00002 Test1/spacetest // 00000004 f-X--- 00002 Test1/spacetest/f1 // 00000005 f----- 00002 Test1/spacetest/f2 // 00000006 -d---- 00002 Test1/spacetest/d1 // 00000007 f----- 00002 Test1/spacetest/d1/f3 // 00000008 f----- 00002 Test1/spacetest/d1/f4 // 00000009 -d---- 00002 Test1/spacetest/d2 // 0000000a -d---- 00002 Test1/spacetest/d3 // 0000000b -d---- 00002 Test1/spacetest/d3/d4 // 0000000c f----- 00002 Test1/spacetest/d3/d4/f5 // 0000000d -d---- 00002 Test1/spacetest/d6 // 0000000e -dX--- 00002 Test1/spacetest/d7 // This is 28 blocks total, of which 2 in deleted files // and 18 in directories. Note that f1 and d7 may or may // not be deleted yet. // // spacetest1 + spacetest2 = 16 files = 32 blocks with raidfile // minus one file and one dir is 28 blocks // // d2/f6, d6/d8 and d6/d8/f7 are new // even if the client marks f1 and d7 as deleted, and // housekeeping deleted them, the backup cannot complete // if the limit is 20 blocks. BOX_TRACE("Waiting for sync for bbackupd to notice that the " "store is full"); wait_for_sync_end(); BOX_TRACE("Sync finished."); BOX_TRACE("Compare to check that there are differences"); int compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query0a.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); BOX_TRACE("Compare finished."); // Check that the notify script was run TEST_THAT(TestFileExists("testfiles/notifyran.store-full.1")); // But only once! TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); // Kill the daemon terminate_bbackupd(bbackupd_pid); wait_for_operation(5, "housekeeping to remove the " "deleted files"); // This removes f1 and d7, which were previously marked // as deleted, so total usage drops by 4 blocks to 24. // BLOCK { std::auto_ptr client = ConnectAndLogin(context, 0 /* read-write */); std::auto_ptr usage( client->QueryGetAccountUsage()); TEST_EQUAL_LINE(24, usage->GetBlocksUsed(), "blocks used"); TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), "deleted blocks"); TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(), "directory blocks"); client->QueryFinished(); sSocket.Close(); } if (failures > 0) { // stop early to make debugging easier return 1; } // ensure time is different to refresh the cache ::safe_sleep(1); BOX_TRACE("Restart bbackupd with more exclusions"); // Start again with a new config that excludes d3 and f2, // and hence also d3/d4 and d3/d4/f5. bbackupd should mark // them as deleted and housekeeping should clean up, // making space to upload the new files. // total required: (13-2-4+3)*2 = 20 blocks /* cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd-exclude.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; */ BackupDaemon bbackupd; bbackupd.Configure("testfiles/bbackupd-exclude.conf"); bbackupd.InitCrypto(); BOX_TRACE("done."); // Should be marked as deleted by this run // wait_for_sync_end(); { // Logging::Guard guard(Log::ERROR); bbackupd.RunSyncNow(); } TEST_THAT(bbackupd.StorageLimitExceeded()); // Check that the notify script was run // TEST_THAT(TestFileExists("testfiles/notifyran.store-full.2")); // But only twice! // TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.3")); // All these should be marked as deleted but hopefully // not removed by housekeeping yet: // f1 deleted // f2 excluded // d1 excluded (why?) // d1/f3 excluded (why?) // d3 excluded // d3/d4 excluded // d3/d4/f5 excluded // d7 deleted // Careful with timing here, these files will be removed by // housekeeping the next time it runs. On Win32, housekeeping // runs immediately after disconnect, but only if enough time // has elapsed since the last housekeeping. Since the // backup run closely follows the last one, housekeeping // should not run afterwards. On other platforms, we want to // get in immediately after the backup and hope that // housekeeping doesn't beat us to it. BOX_TRACE("Find out whether bbackupd marked files as deleted"); { std::auto_ptr client = ConnectAndLogin(context, 0 /* read-write */); std::auto_ptr rootDir = ReadDirectory(*client, BackupProtocolClientListDirectory::RootDirectory); int64_t testDirId = SearchDir(*rootDir, "Test1"); TEST_THAT(testDirId != 0); std::auto_ptr Test1_dir = ReadDirectory(*client, testDirId); int64_t spacetestDirId = SearchDir(*Test1_dir, "spacetest"); TEST_THAT(spacetestDirId != 0); std::auto_ptr spacetest_dir = ReadDirectory(*client, spacetestDirId); // these files were deleted before, they should be // long gone by now TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0); TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0); // these files have just been deleted, because // they are excluded by the new configuration. // but housekeeping should not have run yet TEST_THAT(test_entry_deleted(*spacetest_dir, "f2")); TEST_THAT(test_entry_deleted(*spacetest_dir, "d3")); int64_t d3_id = SearchDir(*spacetest_dir, "d3"); TEST_THAT(d3_id != 0); std::auto_ptr d3_dir = ReadDirectory(*client, d3_id); TEST_THAT(test_entry_deleted(*d3_dir, "d4")); int64_t d4_id = SearchDir(*d3_dir, "d4"); TEST_THAT(d4_id != 0); std::auto_ptr d4_dir = ReadDirectory(*client, d4_id); TEST_THAT(test_entry_deleted(*d4_dir, "f5")); std::auto_ptr usage( client->QueryGetAccountUsage()); TEST_EQUAL_LINE(24, usage->GetBlocksUsed(), "blocks used"); TEST_EQUAL_LINE(4, usage->GetBlocksInDeletedFiles(), "deleted blocks"); TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(), "directory blocks"); // d1/f3 and d1/f4 are the only two files on the // server which are not deleted, they use 2 blocks // each, the rest is directories and 2 deleted files // (f1 and d3/d4/f5) // Log out. client->QueryFinished(); sSocket.Close(); } BOX_TRACE("done."); if (failures > 0) { // stop early to make debugging easier return 1; } wait_for_operation(5, "housekeeping to remove the " "deleted files"); BOX_TRACE("Check that the files were removed"); { std::auto_ptr client = ConnectAndLogin(context, 0 /* read-write */); std::auto_ptr rootDir = ReadDirectory(*client, BackupProtocolClientListDirectory::RootDirectory); int64_t testDirId = SearchDir(*rootDir, "Test1"); TEST_THAT(testDirId != 0); std::auto_ptr Test1_dir = ReadDirectory(*client, testDirId); int64_t spacetestDirId = SearchDir(*Test1_dir, "spacetest"); TEST_THAT(spacetestDirId != 0); std::auto_ptr spacetest_dir = ReadDirectory(*client, spacetestDirId); TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0); TEST_THAT(SearchDir(*spacetest_dir, "f2") == 0); TEST_THAT(SearchDir(*spacetest_dir, "d3") == 0); TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0); std::auto_ptr usage( client->QueryGetAccountUsage()); TEST_EQUAL_LINE(16, usage->GetBlocksUsed(), "blocks used"); TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), "deleted blocks"); TEST_EQUAL_LINE(12, usage->GetBlocksInDirectories(), "directory blocks"); // d1/f3 and d1/f4 are the only two files on the // server, they use 2 blocks each, the rest is // directories. // Log out. client->QueryFinished(); sSocket.Close(); } if (failures > 0) { // stop early to make debugging easier return 1; } // Need 22 blocks free to upload everything TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " "testfiles/bbstored.conf setlimit 01234567 0B 22B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // Run another backup, now there should be enough space // for everything we want to upload. { Logging::Guard guard(Log::ERROR); bbackupd.RunSyncNow(); } TEST_THAT(!bbackupd.StorageLimitExceeded()); // Check that the contents of the store are the same // as the contents of the disc // (-a = all, -c = give result in return code) BOX_TRACE("Check that all files were uploaded successfully"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd-exclude.conf " "-l testfiles/query1.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); BOX_TRACE("done."); // BLOCK { std::auto_ptr client = ConnectAndLogin(context, 0 /* read-write */); std::auto_ptr usage( client->QueryGetAccountUsage()); TEST_EQUAL_LINE(22, usage->GetBlocksUsed(), "blocks used"); TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), "deleted blocks"); TEST_EQUAL_LINE(14, usage->GetBlocksInDirectories(), "directory blocks"); // d2/f6, d6/d8 and d6/d8/f7 are new // i.e. 2 new files, 1 new directory client->QueryFinished(); sSocket.Close(); } if (failures > 0) { // stop early to make debugging easier return 1; } // Put the limit back TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " "testfiles/bbstored.conf setlimit 01234567 " "1000B 2000B") == 0); TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); // Start again with the old config BOX_TRACE("Restart bbackupd with original configuration"); // terminate_bbackupd(); cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; BOX_TRACE("done."); // unpack the initial files again #ifdef WIN32 TEST_THAT(::system("tar xzvf testfiles/test_base.tgz " "-C testfiles") == 0); #else TEST_THAT(::system("gzip -d < testfiles/test_base.tgz " "| ( cd testfiles && tar xf - )") == 0); #endif wait_for_backup_operation("bbackupd to upload more files"); // Check that the contents of the store are the same // as the contents of the disc // (-a = all, -c = give result in return code) BOX_TRACE("Check that all files were uploaded successfully"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query1.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); BOX_TRACE("done."); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if (failures > 0) { // stop early to make debugging easier return 1; } } // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); #ifndef WIN32 // requires fork printf("\n==== Testing that bbackupd responds correctly to " "connection failure\n"); { // Kill the daemons terminate_bbackupd(bbackupd_pid); test_kill_bbstored(); // create a new file to force an upload const char* new_file = "testfiles/TestDir1/force-upload-2"; int fd = open(new_file, O_CREAT | O_EXCL | O_WRONLY, 0700); if (fd <= 0) { perror(new_file); } TEST_THAT(fd > 0); const char* control_string = "whee!\n"; TEST_THAT(write(fd, control_string, strlen(control_string)) == (int)strlen(control_string)); close(fd); // sleep to make it old enough to upload safe_sleep(4); class MyHook : public BackupStoreContext::TestHook { virtual std::auto_ptr StartCommand( BackupProtocolObject& rCommand) { if (rCommand.GetType() == BackupProtocolServerStoreFile::TypeID) { // terminate badly THROW_EXCEPTION(CommonException, Internal); } return std::auto_ptr(); } }; MyHook hook; bbstored_pid = fork(); if (bbstored_pid < 0) { BOX_LOG_SYS_ERROR("failed to fork()"); return 1; } if (bbstored_pid == 0) { // in fork child TEST_THAT(setsid() != -1); if (!Logging::IsEnabled(Log::TRACE)) { Logging::SetGlobalLevel(Log::NOTHING); } // BackupStoreDaemon must be destroyed before exit(), // to avoid memory leaks being reported. { BackupStoreDaemon bbstored; bbstored.SetTestHook(hook); bbstored.SetRunInForeground(true); bbstored.Main("testfiles/bbstored.conf"); } Timers::Cleanup(); // avoid memory leaks exit(0); } // in fork parent bbstored_pid = WaitForServerStartup("testfiles/bbstored.pid", bbstored_pid); TEST_THAT(::system("rm -f testfiles/notifyran.store-full.*") == 0); // Ignore SIGPIPE so that when the connection is broken, // the daemon doesn't terminate. ::signal(SIGPIPE, SIG_IGN); { Log::Level newLevel = Logging::GetGlobalLevel(); if (!Logging::IsEnabled(Log::TRACE)) { newLevel = Log::NOTHING; } Logging::Guard guard(newLevel); BackupDaemon bbackupd; bbackupd.Configure("testfiles/bbackupd.conf"); bbackupd.InitCrypto(); bbackupd.RunSyncNowWithExceptionHandling(); } ::signal(SIGPIPE, SIG_DFL); TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1")); TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2")); TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.1")); test_kill_bbstored(true); if (failures > 0) { // stop early to make debugging easier return 1; } TEST_THAT(test_run_bbstored() == 0); cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; } #endif // !WIN32 #ifndef WIN32 printf("\n==== Testing that absolute symlinks are not followed " "during restore\n"); { #define SYM_DIR "testfiles" DIRECTORY_SEPARATOR "TestDir1" \ DIRECTORY_SEPARATOR "symlink_test" TEST_THAT(::mkdir(SYM_DIR, 0777) == 0); TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a", 0777) == 0); TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a" DIRECTORY_SEPARATOR "subdir", 0777) == 0); TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "b", 0777) == 0); FILE* fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a" DIRECTORY_SEPARATOR "subdir" DIRECTORY_SEPARATOR "content", "w"); TEST_THAT(fp != NULL); fputs("before\n", fp); fclose(fp); char buf[PATH_MAX]; TEST_THAT(getcwd(buf, sizeof(buf)) == buf); std::string path = buf; path += DIRECTORY_SEPARATOR SYM_DIR DIRECTORY_SEPARATOR "a" DIRECTORY_SEPARATOR "subdir"; TEST_THAT(symlink(path.c_str(), SYM_DIR DIRECTORY_SEPARATOR "b" DIRECTORY_SEPARATOR "link") == 0); // also test symlink-to-self loop does not break restore TEST_THAT(symlink("self", SYM_DIR "/self") == 0); wait_for_operation(4, "symlinks to be old enough"); sync_and_wait(); // Check that the backup was successful, i.e. no differences int compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query1.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // now stop bbackupd and update the test file, // make the original directory unreadable terminate_bbackupd(bbackupd_pid); fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a" DIRECTORY_SEPARATOR "subdir" DIRECTORY_SEPARATOR "content", "w"); TEST_THAT(fp != NULL); fputs("after\n", fp); fclose(fp); TEST_THAT(chmod(SYM_DIR, 0) == 0); // check that we can restore it compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-Wwarning \"restore Test1 testfiles/restore-symlink\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); // make it accessible again TEST_THAT(chmod(SYM_DIR, 0755) == 0); // check that the original file was not overwritten FileStream fs(SYM_DIR "/a/subdir/content"); IOStreamGetLine gl(fs); std::string line; TEST_THAT(gl.GetLine(line)); TEST_THAT(line != "before"); TEST_EQUAL("after", line); #undef SYM_DIR /* // This is not worth testing or fixing. // #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE printf("\n==== Testing that symlinks to other filesystems " "can be backed up as roots\n"); intercept_setup_lstat_post_hook(lstat_test_post_hook); TEST_THAT(symlink("TestDir1", "testfiles/symlink-to-TestDir1") == 0); struct stat stat_st, lstat_st; TEST_THAT(stat("testfiles/symlink-to-TestDir1", &stat_st) == 0); TEST_THAT(lstat("testfiles/symlink-to-TestDir1", &lstat_st) == 0); TEST_EQUAL_LINE((stat_st.st_dev ^ 0xFFFF), lstat_st.st_dev, "stat vs lstat"); BackupDaemon bbackupd; bbackupd.Configure("testfiles/bbackupd-symlink.conf"); bbackupd.InitCrypto(); bbackupd.RunSyncNow(); intercept_clear_setup(); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query0a.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // and again using the symlink during compare compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd-symlink.conf " "-l testfiles/query0a.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); #endif */ bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; } #endif // !WIN32 // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); printf("\n==== Testing that redundant locations are deleted on time\n"); // unpack the files for the redundant location test TEST_THAT(::system("rm -rf testfiles/TestDir2") == 0); TEST_THAT(::mkdir("testfiles/TestDir2", 0777) == 0); #ifdef WIN32 TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz " "-C testfiles/TestDir2") == 0); #else TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz " "| ( cd testfiles/TestDir2 && tar xf - )") == 0); #endif // BLOCK { // Kill the daemon terminate_bbackupd(bbackupd_pid); // Start it with a config that has a temporary location // that will be created on the server std::string cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd-temploc.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; sync_and_wait(); { std::auto_ptr client = ConnectAndLogin(context, BackupProtocolClientLogin::Flags_ReadOnly); std::auto_ptr dir = ReadDirectory(*client, BackupProtocolClientListDirectory::RootDirectory); int64_t testDirId = SearchDir(*dir, "Test2"); TEST_THAT(testDirId != 0); client->QueryFinished(); sSocket.Close(); } // Kill the daemon terminate_bbackupd(bbackupd_pid); // Start it again with the normal config (no Test2) cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // Test2 should be deleted after 10 seconds (4 runs) wait_for_sync_end(); wait_for_sync_end(); wait_for_sync_end(); // not yet! should still be there { std::auto_ptr client = ConnectAndLogin(context, BackupProtocolClientLogin::Flags_ReadOnly); std::auto_ptr dir = ReadDirectory(*client, BackupProtocolClientListDirectory::RootDirectory); int64_t testDirId = SearchDir(*dir, "Test2"); TEST_THAT(testDirId != 0); client->QueryFinished(); sSocket.Close(); } wait_for_sync_end(); // NOW it should be gone { std::auto_ptr client = ConnectAndLogin(context, BackupProtocolClientLogin::Flags_ReadOnly); std::auto_ptr root_dir = ReadDirectory(*client, BackupProtocolClientListDirectory::RootDirectory); TEST_THAT(test_entry_deleted(*root_dir, "Test2")); client->QueryFinished(); sSocket.Close(); } } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if(bbackupd_pid > 0) { // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); printf("\n==== Check that read-only directories and " "their contents can be restored.\n"); int compareReturnValue; { #ifdef WIN32 TEST_THAT(::system("chmod 0555 testfiles/" "TestDir1/x1") == 0); #else TEST_THAT(chmod("testfiles/TestDir1/x1", 0555) == 0); #endif wait_for_sync_end(); // too new wait_for_sync_end(); // should be backed up now compareReturnValue = ::system(BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "\"compare -cEQ Test1 testfiles/TestDir1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // check that we can restore it compareReturnValue = ::system(BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "\"restore Test1 testfiles/restore1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // check that it restored properly compareReturnValue = ::system(BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "\"compare -cEQ Test1 testfiles/restore1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // put the permissions back to sensible values #ifdef WIN32 TEST_THAT(::system("chmod 0755 testfiles/" "TestDir1/x1") == 0); TEST_THAT(::system("chmod 0755 testfiles/" "restore1/x1") == 0); #else TEST_THAT(chmod("testfiles/TestDir1/x1", 0755) == 0); TEST_THAT(chmod("testfiles/restore1/x1", 0755) == 0); #endif } #ifdef WIN32 printf("\n==== Check that filenames in UTF-8 " "can be backed up\n"); // We have no guarantee that a random Unicode string can be // represented in the user's character set, so we go the // other way, taking three random characters from the // character set and converting them to Unicode. // // We hope that these characters are valid in most // character sets, but they probably are not in multibyte // character sets such as Shift-JIS, GB2312, etc. This test // will probably fail if your system locale is set to // Chinese, Japanese, etc. where one of these character // sets is used by default. You can check the character // set for your system in Control Panel -> Regional // Options -> General -> Language Settings -> Set Default // (System Locale). Because bbackupquery converts from // system locale to UTF-8 via the console code page // (which you can check from the Command Prompt with "chcp") // they must also be valid in your code page (850 for // Western Europe). // // In ISO-8859-1 (Danish locale) they are three Danish // accented characters, which are supported in code page // 850. Depending on your locale, YYMV (your yak may vomit). std::string foreignCharsNative("\x91\x9b\x86"); std::string foreignCharsUnicode; TEST_THAT(ConvertConsoleToUtf8(foreignCharsNative.c_str(), foreignCharsUnicode)); std::string basedir("testfiles/TestDir1"); std::string dirname("test" + foreignCharsUnicode + "testdir"); std::string dirpath(basedir + "/" + dirname); TEST_THAT(mkdir(dirpath.c_str(), 0) == 0); std::string filename("test" + foreignCharsUnicode + "testfile"); std::string filepath(dirpath + "/" + filename); char cwdbuf[1024]; TEST_THAT(getcwd(cwdbuf, sizeof(cwdbuf)) == cwdbuf); std::string cwd = cwdbuf; // Test that our emulated chdir() works properly // with relative and absolute paths TEST_THAT(::chdir(dirpath.c_str()) == 0); TEST_THAT(::chdir("../../..") == 0); TEST_THAT(::chdir(cwd.c_str()) == 0); // Check that it can be converted to the system encoding // (which is what is needed on the command line) std::string systemDirName; TEST_THAT(ConvertEncoding(dirname.c_str(), CP_UTF8, systemDirName, CP_ACP)); std::string systemFileName; TEST_THAT(ConvertEncoding(filename.c_str(), CP_UTF8, systemFileName, CP_ACP)); // Check that it can be converted to the console encoding // (which is what we will see in the output) std::string consoleDirName; TEST_THAT(ConvertUtf8ToConsole(dirname.c_str(), consoleDirName)); std::string consoleFileName; TEST_THAT(ConvertUtf8ToConsole(filename.c_str(), consoleFileName)); // test that bbackupd will let us lcd into the local // directory using a relative path std::string command = BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "\"lcd testfiles/TestDir1/" + systemDirName + "\" " "quit"; compareReturnValue = ::system(command.c_str()); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); // and back out again command = BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "\"lcd testfiles/TestDir1/" + systemDirName + "\" " "\"lcd ..\" quit"; compareReturnValue = ::system(command.c_str()); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); // and using an absolute path command = BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "\"lcd " + cwd + "/testfiles/TestDir1/" + systemDirName + "\" quit"; compareReturnValue = ::system(command.c_str()); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); // and back out again command = BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "\"lcd " + cwd + "/testfiles/TestDir1/" + systemDirName + "\" " "\"lcd ..\" quit"; compareReturnValue = ::system(command.c_str()); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); { FileStream fs(filepath.c_str(), O_CREAT | O_RDWR); std::string data("hello world\n"); fs.Write(data.c_str(), data.size()); TEST_EQUAL_LINE(12, fs.GetPosition(), "FileStream position"); fs.Close(); } wait_for_backup_operation("upload of file with unicode name"); // Compare to check that the file was uploaded compareReturnValue = ::system(BBACKUPQUERY " -Wwarning " "-c testfiles/bbackupd.conf \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Check that we can find it in directory listing { std::auto_ptr client = ConnectAndLogin(context, 0); std::auto_ptr dir = ReadDirectory( *client, BackupProtocolClientListDirectory::RootDirectory); int64_t baseDirId = SearchDir(*dir, "Test1"); TEST_THAT(baseDirId != 0); dir = ReadDirectory(*client, baseDirId); int64_t testDirId = SearchDir(*dir, dirname.c_str()); TEST_THAT(testDirId != 0); dir = ReadDirectory(*client, testDirId); TEST_THAT(SearchDir(*dir, filename.c_str()) != 0); // Log out client->QueryFinished(); sSocket.Close(); } // Check that bbackupquery shows the dir in console encoding command = BBACKUPQUERY " -Wwarning " "-c testfiles/bbackupd.conf " "-q \"list Test1\" quit"; pid_t bbackupquery_pid; std::auto_ptr queryout; queryout = LocalProcessStream(command.c_str(), bbackupquery_pid); TEST_THAT(queryout.get() != NULL); TEST_THAT(bbackupquery_pid != -1); IOStreamGetLine reader(*queryout); std::string line; bool found = false; while (!reader.IsEOF()) { TEST_THAT(reader.GetLine(line)); if (line.find(consoleDirName) != std::string::npos) { found = true; } } TEST_THAT(!(queryout->StreamDataLeft())); TEST_THAT(reader.IsEOF()); TEST_THAT(found); queryout->Close(); // Check that bbackupquery can list the dir when given // on the command line in system encoding, and shows // the file in console encoding command = BBACKUPQUERY " -c testfiles/bbackupd.conf " "-Wwarning \"list Test1/" + systemDirName + "\" quit"; queryout = LocalProcessStream(command.c_str(), bbackupquery_pid); TEST_THAT(queryout.get() != NULL); TEST_THAT(bbackupquery_pid != -1); IOStreamGetLine reader2(*queryout); found = false; while (!reader2.IsEOF()) { TEST_THAT(reader2.GetLine(line)); if (line.find(consoleFileName) != std::string::npos) { found = true; } } TEST_THAT(!(queryout->StreamDataLeft())); TEST_THAT(reader2.IsEOF()); TEST_THAT(found); queryout->Close(); // Check that bbackupquery can compare the dir when given // on the command line in system encoding. command = BBACKUPQUERY " -c testfiles/bbackupd.conf " "-Wwarning \"compare -cEQ Test1/" + systemDirName + " testfiles/TestDir1/" + systemDirName + "\" quit"; compareReturnValue = ::system(command.c_str()); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); // Check that bbackupquery can restore the dir when given // on the command line in system encoding. command = BBACKUPQUERY " -c testfiles/bbackupd.conf " "-Wwarning \"restore Test1/" + systemDirName + " testfiles/restore-" + systemDirName + "\" quit"; compareReturnValue = ::system(command.c_str()); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); // Compare to make sure it was restored properly. command = BBACKUPQUERY " -c testfiles/bbackupd.conf " "-Wwarning \"compare -cEQ Test1/" + systemDirName + " testfiles/restore-" + systemDirName + "\" quit"; compareReturnValue = ::system(command.c_str()); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); std::string fileToUnlink = "testfiles/restore-" + dirname + "/" + filename; TEST_THAT(::unlink(fileToUnlink.c_str()) == 0); // Check that bbackupquery can get the file when given // on the command line in system encoding. command = BBACKUPQUERY " -c testfiles/bbackupd.conf " "-Wwarning \"get Test1/" + systemDirName + "/" + systemFileName + " " + "testfiles/restore-" + systemDirName + "/" + systemFileName + "\" quit"; compareReturnValue = ::system(command.c_str()); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // And after changing directory to a relative path command = BBACKUPQUERY " -c testfiles/bbackupd.conf " "-Wwarning " "\"lcd testfiles\" " "\"cd Test1/" + systemDirName + "\" " + "\"get " + systemFileName + "\" quit"; compareReturnValue = ::system(command.c_str()); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks"); // cannot overwrite a file that exists, so delete it std::string tmp = "testfiles/" + filename; TEST_THAT(::unlink(tmp.c_str()) == 0); // And after changing directory to an absolute path command = BBACKUPQUERY " -c testfiles/bbackupd.conf -Wwarning " "\"lcd " + cwd + "/testfiles\" " "\"cd Test1/" + systemDirName + "\" " + "\"get " + systemFileName + "\" quit"; compareReturnValue = ::system(command.c_str()); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Command_OK); TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks"); // Compare to make sure it was restored properly. // The Get command does not restore attributes, so // we must compare without them (-A) to succeed. command = BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-Wwarning \"compare -cAEQ Test1/" + systemDirName + " testfiles/restore-" + systemDirName + "\" quit"; compareReturnValue = ::system(command.c_str()); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); // Compare without attributes. This should fail. command = BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-Werror \"compare -cEQ Test1/" + systemDirName + " testfiles/restore-" + systemDirName + "\" quit"; compareReturnValue = ::system(command.c_str()); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); #endif // WIN32 // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Check that SyncAllowScript is executed and can " "pause backup\n"); fflush(stdout); { wait_for_sync_end(); // we now have 3 seconds before bbackupd // runs the SyncAllowScript again. const char* sync_control_file = "testfiles" DIRECTORY_SEPARATOR "syncallowscript.control"; int fd = open(sync_control_file, O_CREAT | O_EXCL | O_WRONLY, 0700); if (fd <= 0) { perror(sync_control_file); } TEST_THAT(fd > 0); const char* control_string = "10\n"; TEST_THAT(write(fd, control_string, strlen(control_string)) == (int)strlen(control_string)); close(fd); // this will pause backups, bbackupd will check // every 10 seconds to see if they are allowed again. const char* new_test_file = "testfiles" DIRECTORY_SEPARATOR "TestDir1" DIRECTORY_SEPARATOR "Added_During_Pause"; fd = open(new_test_file, O_CREAT | O_EXCL | O_WRONLY, 0700); if (fd <= 0) { perror(new_test_file); } TEST_THAT(fd > 0); close(fd); struct stat st; // next poll should happen within the next // 5 seconds (normally about 3 seconds) wait_for_operation(1, "2 seconds before next run"); TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR "syncallowscript.notifyran.1", &st) != 0); wait_for_operation(4, "2 seconds after run"); TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR "syncallowscript.notifyran.1", &st) == 0); TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR "syncallowscript.notifyran.2", &st) != 0); // next poll should happen within the next // 10 seconds (normally about 8 seconds) wait_for_operation(6, "2 seconds before next run"); TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR "syncallowscript.notifyran.2", &st) != 0); wait_for_operation(4, "2 seconds after run"); TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR "syncallowscript.notifyran.2", &st) == 0); // bbackupquery compare might take a while // on slow machines, so start the timer now long start_time = time(NULL); // check that no backup has run (compare fails) compareReturnValue = ::system(BBACKUPQUERY " " "-Werror " "-c testfiles/bbackupd.conf " "-l testfiles/query3.log " "\"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(unlink(sync_control_file) == 0); wait_for_sync_start(); long end_time = time(NULL); long wait_time = end_time - start_time + 2; // should be about 10 seconds if (wait_time < 8 || wait_time > 12) { printf("Waited for %ld seconds, should have " "been %s", wait_time, control_string); } TEST_THAT(wait_time >= 8); TEST_THAT(wait_time <= 12); wait_for_sync_end(); // check that backup has run (compare succeeds) compareReturnValue = ::system(BBACKUPQUERY " " "-Wwarning " "-c testfiles/bbackupd.conf " "-l testfiles/query3a.log " "\"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); if (failures > 0) { // stop early to make debugging easier return 1; } } // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Delete file and update another, " "create symlink.\n"); // Delete a file TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); #ifndef WIN32 // New symlink TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/symlink-to-dir") == 0); #endif // Update a file (will be uploaded as a diff) { // Check that the file is over the diffing // threshold in the bbackupd.conf file TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") > 1024); // Add a bit to the end FILE *f = ::fopen("testfiles/TestDir1/f45.df", "a"); TEST_THAT(f != 0); ::fprintf(f, "EXTRA STUFF"); ::fclose(f); TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") > 1024); } // wait long enough for new files to be old enough to backup wait_for_operation(5, "new files to be old enough"); // wait for backup daemon to do it's stuff sync_and_wait(); // compare to make sure that it worked compareReturnValue = ::system(BBACKUPQUERY " -Wwarning " "-c testfiles/bbackupd.conf " "-l testfiles/query2.log " "\"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Try a quick compare, just for fun compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query2q.log " "-Wwarning \"compare -acqQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // Check that store errors are reported neatly printf("\n==== Create store error\n"); TEST_THAT(system("rm -f testfiles/notifyran.backup-error.*") == 0); // break the store TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf", "testfiles/0_0/backup/01234567/info.rf.bak") == 0); TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf", "testfiles/0_1/backup/01234567/info.rf.bak") == 0); TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf", "testfiles/0_2/backup/01234567/info.rf.bak") == 0); // Create a file to trigger an upload { int fd1 = open("testfiles/TestDir1/force-upload", O_CREAT | O_EXCL | O_WRONLY, 0700); TEST_THAT(fd1 > 0); TEST_THAT(write(fd1, "just do it", 10) == 10); TEST_THAT(close(fd1) == 0); } wait_for_operation(4, "bbackupd to try to access the store"); // Check that an error was reported just once TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1")); TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2")); // Now kill bbackupd and start one that's running in // snapshot mode, check that it automatically syncs after // an error, without waiting for another sync command. terminate_bbackupd(bbackupd_pid); std::string cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd-snapshot.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; sync_and_wait(); // Check that the error was reported once more TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.2")); TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.3")); // Fix the store (so that bbackupquery compare works) TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak", "testfiles/0_0/backup/01234567/info.rf") == 0); TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak", "testfiles/0_1/backup/01234567/info.rf") == 0); TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak", "testfiles/0_2/backup/01234567/info.rf") == 0); int store_fixed_time = time(NULL); // Check that we DO get errors on compare (cannot do this // until after we fix the store, which creates a race) compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3b.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Test initial state TEST_THAT(!TestFileExists("testfiles/" "notifyran.backup-start.wait-snapshot.1")); // Set a tag for the notify script to distinguish from // previous runs. { int fd1 = open("testfiles/notifyscript.tag", O_CREAT | O_EXCL | O_WRONLY, 0700); TEST_THAT(fd1 > 0); TEST_THAT(write(fd1, "wait-snapshot", 13) == 13); TEST_THAT(close(fd1) == 0); } // bbackupd should pause for about 90 seconds from // store_fixed_time, so check that it hasn't run after // 85 seconds after store_fixed_time wait_for_operation(85 - time(NULL) + store_fixed_time, "just before bbackupd recovers"); TEST_THAT(!TestFileExists("testfiles/" "notifyran.backup-start.wait-snapshot.1")); // Should not have backed up, should still get errors compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3b.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // wait another 10 seconds, bbackup should have run wait_for_operation(10, "bbackupd to recover"); TEST_THAT(TestFileExists("testfiles/" "notifyran.backup-start.wait-snapshot.1")); // Check that it did get uploaded, and we have no more errors compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3b.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); // Stop the snapshot bbackupd terminate_bbackupd(bbackupd_pid); // Break the store again TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf", "testfiles/0_0/backup/01234567/info.rf.bak") == 0); TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf", "testfiles/0_1/backup/01234567/info.rf.bak") == 0); TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf", "testfiles/0_2/backup/01234567/info.rf.bak") == 0); // Modify a file to trigger an upload { int fd1 = open("testfiles/TestDir1/force-upload", O_WRONLY, 0700); TEST_THAT(fd1 > 0); TEST_THAT(write(fd1, "and again", 9) == 9); TEST_THAT(close(fd1) == 0); } // Restart the old bbackupd, in automatic mode cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); ::safe_sleep(1); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; sync_and_wait(); // Fix the store again TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak", "testfiles/0_0/backup/01234567/info.rf") == 0); TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak", "testfiles/0_1/backup/01234567/info.rf") == 0); TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak", "testfiles/0_2/backup/01234567/info.rf") == 0); store_fixed_time = time(NULL); // Check that we DO get errors on compare (cannot do this // until after we fix the store, which creates a race) compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3b.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Test initial state TEST_THAT(!TestFileExists("testfiles/" "notifyran.backup-start.wait-automatic.1")); // Set a tag for the notify script to distinguist from // previous runs. { int fd1 = open("testfiles/notifyscript.tag", O_CREAT | O_EXCL | O_WRONLY, 0700); TEST_THAT(fd1 > 0); TEST_THAT(write(fd1, "wait-automatic", 14) == 14); TEST_THAT(close(fd1) == 0); } // bbackupd should pause for about 90 seconds from // store_fixed_time, so check that it hasn't run after // 85 seconds from store_fixed_time wait_for_operation(85 - time(NULL) + store_fixed_time, "just before bbackupd recovers"); TEST_THAT(!TestFileExists("testfiles/" "notifyran.backup-start.wait-automatic.1")); // Should not have backed up, should still get errors compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3b.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // wait another 10 seconds, bbackup should have run wait_for_operation(10, "bbackupd to recover"); TEST_THAT(TestFileExists("testfiles/" "notifyran.backup-start.wait-automatic.1")); // Check that it did get uploaded, and we have no more errors compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3b.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // Bad case: delete a file/symlink, replace it with a directory printf("\n==== Replace symlink with directory, " "add new directory\n"); #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/symlink-to-dir") == 0); #endif TEST_THAT(::mkdir("testfiles/TestDir1/symlink-to-dir", 0755) == 0); TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) == 0); // NOTE: create a file within the directory to // avoid deletion by the housekeeping process later #ifndef WIN32 TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file/contents") == 0); #endif wait_for_backup_operation("bbackupd to sync the changes"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3c.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // And the inverse, replace a directory with a file/symlink printf("\n==== Replace directory with symlink\n"); #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" "/contents") == 0); #endif TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); #ifndef WIN32 TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file") == 0); #endif wait_for_backup_operation("bbackupd to sync the changes"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3d.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // And then, put it back to how it was before. printf("\n==== Replace symlink with directory " "(which was a symlink)\n"); #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/x1" "/dir-to-file") == 0); #endif TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) == 0); #ifndef WIN32 TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file/contents2") == 0); #endif wait_for_backup_operation("bbackupd to sync the changes"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3e.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // And finally, put it back to how it was before // it was put back to how it was before // This gets lots of nasty things in the store with // directories over other old directories. printf("\n==== Put it all back to how it was\n"); #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" "/contents2") == 0); #endif TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); #ifndef WIN32 TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file") == 0); #endif wait_for_backup_operation("bbackupd to sync the changes"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3f.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // rename an untracked file over an // existing untracked file printf("\n==== Rename over existing untracked file\n"); int fd1 = open("testfiles/TestDir1/untracked-1", O_CREAT | O_EXCL | O_WRONLY, 0700); int fd2 = open("testfiles/TestDir1/untracked-2", O_CREAT | O_EXCL | O_WRONLY, 0700); TEST_THAT(fd1 > 0); TEST_THAT(fd2 > 0); TEST_THAT(write(fd1, "hello", 5) == 5); TEST_THAT(close(fd1) == 0); safe_sleep(1); TEST_THAT(write(fd2, "world", 5) == 5); TEST_THAT(close(fd2) == 0); TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-1")); TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-2")); // back up both files wait_for_operation(5, "untracked files to be old enough"); wait_for_backup_operation("bbackupd to sync the " "untracked files"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3g.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); #ifdef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/untracked-2") == 0); #endif TEST_THAT(::rename("testfiles/TestDir1/untracked-1", "testfiles/TestDir1/untracked-2") == 0); TEST_THAT(!TestFileExists("testfiles/TestDir1/untracked-1")); TEST_THAT( TestFileExists("testfiles/TestDir1/untracked-2")); wait_for_backup_operation("bbackupd to sync the untracked " "files again"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3g.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // case which went wrong: rename a tracked file over an // existing tracked file printf("\n==== Rename over existing tracked file\n"); fd1 = open("testfiles/TestDir1/tracked-1", O_CREAT | O_EXCL | O_WRONLY, 0700); fd2 = open("testfiles/TestDir1/tracked-2", O_CREAT | O_EXCL | O_WRONLY, 0700); TEST_THAT(fd1 > 0); TEST_THAT(fd2 > 0); char buffer[1024]; TEST_THAT(write(fd1, "hello", 5) == 5); TEST_THAT(write(fd1, buffer, sizeof(buffer)) == sizeof(buffer)); TEST_THAT(close(fd1) == 0); safe_sleep(1); TEST_THAT(write(fd2, "world", 5) == 5); TEST_THAT(write(fd2, buffer, sizeof(buffer)) == sizeof(buffer)); TEST_THAT(close(fd2) == 0); TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-1")); TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-2")); // wait for them to be old enough to back up wait_for_operation(5, "tracked files to be old enough"); // back up both files sync_and_wait(); // compare to make sure that it worked compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3h.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); #ifdef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/tracked-2") == 0); #endif TEST_THAT(::rename("testfiles/TestDir1/tracked-1", "testfiles/TestDir1/tracked-2") == 0); TEST_THAT(!TestFileExists("testfiles/TestDir1/tracked-1")); TEST_THAT( TestFileExists("testfiles/TestDir1/tracked-2")); wait_for_backup_operation("bbackupd to sync the tracked " "files again"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3i.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // case which went wrong: rename a tracked file // over a deleted file printf("\n==== Rename an existing file over a deleted file\n"); TEST_THAT(!TestFileExists("testfiles/TestDir1/x1/dsfdsfs98.fd")); TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", "testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); wait_for_backup_operation("bbackupd to sync"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3j.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Add files with old times, update " "attributes of one to latest time\n"); // Move that file back TEST_THAT(::rename("testfiles/TestDir1/x1/dsfdsfs98.fd", "testfiles/TestDir1/df9834.dsf") == 0); // Add some more files // Because the 'm' option is not used, these files will // look very old to the daemon. // Lucky it'll upload them then! #ifdef WIN32 TEST_THAT(::system("tar xzvf testfiles/test2.tgz " "-C testfiles") == 0); #else TEST_THAT(::system("gzip -d < testfiles/test2.tgz " "| ( cd testfiles && tar xf - )") == 0); ::chmod("testfiles/TestDir1/sub23/dhsfdss/blf.h", 0415); #endif // Wait and test wait_for_backup_operation("bbackupd to sync old files"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3k.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); // Check that modifying files with old timestamps // still get added printf("\n==== Modify existing file, but change timestamp " "to rather old\n"); wait_for_sync_end(); // Then modify an existing file { // in the archive, it's read only #ifdef WIN32 TEST_THAT(::system("chmod 0777 testfiles" "/TestDir1/sub23/rand.h") == 0); #else TEST_THAT(chmod("testfiles/TestDir1/sub23" "/rand.h", 0777) == 0); #endif FILE *f = fopen("testfiles/TestDir1/sub23/rand.h", "w+"); if (f == 0) { perror("Failed to open"); } TEST_THAT(f != 0); if (f != 0) { fprintf(f, "MODIFIED!\n"); fclose(f); } // and then move the time backwards! struct timeval times[2]; BoxTimeToTimeval(SecondsToBoxTime( (time_t)(365*24*60*60)), times[1]); times[0] = times[1]; TEST_THAT(::utimes("testfiles/TestDir1/sub23/rand.h", times) == 0); } // Wait and test wait_for_sync_end(); // files too new wait_for_sync_end(); // should (not) be backed up this time compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3l.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); // Add some files and directories which are marked as excluded printf("\n==== Add files and dirs for exclusion test\n"); #ifdef WIN32 TEST_THAT(::system("tar xzvf testfiles/testexclude.tgz " "-C testfiles") == 0); #else TEST_THAT(::system("gzip -d < " "testfiles/testexclude.tgz " "| ( cd testfiles && tar xf - )") == 0); #endif // Wait and test wait_for_sync_end(); wait_for_sync_end(); // compare with exclusions, should not find differences compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3m.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // compare without exclusions, should find differences compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3n.log " "-Werror \"compare -acEQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // check that the excluded files did not make it // into the store, and the included files did printf("\n==== Check that exclude/alwaysinclude commands " "actually work\n"); { std::auto_ptr client = ConnectAndLogin(context, BackupProtocolClientLogin::Flags_ReadOnly); std::auto_ptr dir = ReadDirectory( *client, BackupProtocolClientListDirectory::RootDirectory); int64_t testDirId = SearchDir(*dir, "Test1"); TEST_THAT(testDirId != 0); dir = ReadDirectory(*client, testDirId); TEST_THAT(!SearchDir(*dir, "excluded_1")); TEST_THAT(!SearchDir(*dir, "excluded_2")); TEST_THAT(!SearchDir(*dir, "exclude_dir")); TEST_THAT(!SearchDir(*dir, "exclude_dir_2")); // xx_not_this_dir_22 should not be excluded by // ExcludeDirsRegex, because it's a file TEST_THAT(SearchDir (*dir, "xx_not_this_dir_22")); TEST_THAT(!SearchDir(*dir, "zEXCLUDEu")); TEST_THAT(SearchDir (*dir, "dont.excludethis")); TEST_THAT(SearchDir (*dir, "xx_not_this_dir_ALWAYSINCLUDE")); int64_t sub23id = SearchDir(*dir, "sub23"); TEST_THAT(sub23id != 0); dir = ReadDirectory(*client, sub23id); TEST_THAT(!SearchDir(*dir, "xx_not_this_dir_22")); TEST_THAT(!SearchDir(*dir, "somefile.excludethis")); client->QueryFinished(); sSocket.Close(); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; #ifndef WIN32 // These tests only work as non-root users. if(::getuid() != 0) { // Check that the error has not been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); // Check that read errors are reported neatly printf("\n==== Add unreadable files\n"); { // Dir and file which can't be read TEST_THAT(::mkdir("testfiles/TestDir1/sub23" "/read-fail-test-dir", 0000) == 0); int fd = ::open("testfiles/TestDir1" "/read-fail-test-file", O_CREAT | O_WRONLY, 0000); TEST_THAT(fd != -1); ::close(fd); } // Wait and test... wait_for_backup_operation("bbackupd to try to sync " "unreadable file"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3o.log " "-Werror \"compare -acQ\" quit"); // should find differences TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Error); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Check that it was reported correctly TEST_THAT(TestFileExists("testfiles/notifyran.read-error.1")); // Check that the error was only reported once TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); // Set permissions on file and dir to stop // errors in the future TEST_THAT(::chmod("testfiles/TestDir1/sub23" "/read-fail-test-dir", 0770) == 0); TEST_THAT(::chmod("testfiles/TestDir1" "/read-fail-test-file", 0770) == 0); } #endif TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Continuously update file, " "check isn't uploaded\n"); // Make sure everything happens at the same point in the // sync cycle: wait until exactly the start of a sync wait_for_sync_start(); // Then wait a second, to make sure the scan is complete ::safe_sleep(1); { // Open a file, then save something to it every second for(int l = 0; l < 12; ++l) { FILE *f = ::fopen("testfiles/TestDir1/continousupdate", "w+"); TEST_THAT(f != 0); fprintf(f, "Loop iteration %d\n", l); fflush(f); fclose(f); printf("."); fflush(stdout); safe_sleep(1); } printf("\n"); fflush(stdout); // Check there's a difference compareReturnValue = ::system("perl testfiles/" "extcheck1.pl"); TEST_RETURN(compareReturnValue, 1); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); printf("\n==== Keep on continuously updating file, " "check it is uploaded eventually\n"); for(int l = 0; l < 28; ++l) { FILE *f = ::fopen("testfiles/TestDir1/" "continousupdate", "w+"); TEST_THAT(f != 0); fprintf(f, "Loop 2 iteration %d\n", l); fflush(f); fclose(f); printf("."); fflush(stdout); safe_sleep(1); } printf("\n"); fflush(stdout); compareReturnValue = ::system("perl testfiles/" "extcheck2.pl"); TEST_RETURN(compareReturnValue, 1); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Delete directory, change attributes\n"); // Delete a directory TEST_THAT(::system("rm -rf testfiles/TestDir1/x1") == 0); // Change attributes on an original file. ::chmod("testfiles/TestDir1/df9834.dsf", 0423); // Wait and test wait_for_backup_operation("bbackupd to sync deletion " "of directory"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query4.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); printf("\n==== Restore files and directories\n"); int64_t deldirid = 0; int64_t restoredirid = 0; { // connect and log in std::auto_ptr client = ConnectAndLogin(context, BackupProtocolClientLogin::Flags_ReadOnly); // Find the ID of the Test1 directory restoredirid = GetDirID(*client, "Test1", BackupProtocolClientListDirectory::RootDirectory); TEST_THAT(restoredirid != 0); // Test the restoration TEST_THAT(BackupClientRestore(*client, restoredirid, "Test1", "testfiles/restore-Test1", true /* print progress dots */) == Restore_Complete); // On Win32 we can't open another connection // to the server, so we'll compare later. // Make sure you can't restore a restored directory TEST_THAT(BackupClientRestore(*client, restoredirid, "Test1", "testfiles/restore-Test1", true /* print progress dots */) == Restore_TargetExists); // Find ID of the deleted directory deldirid = GetDirID(*client, "x1", restoredirid); TEST_THAT(deldirid != 0); // Just check it doesn't bomb out -- will check this // properly later (when bbackupd is stopped) TEST_THAT(BackupClientRestore(*client, deldirid, "Test1", "testfiles/restore-Test1-x1", true /* print progress dots */, true /* deleted files */) == Restore_Complete); // Make sure you can't restore to a nonexistant path printf("\n==== Try to restore to a path " "that doesn't exist\n"); fflush(stdout); { Logging::Guard guard(Log::FATAL); TEST_THAT(BackupClientRestore(*client, restoredirid, "Test1", "testfiles/no-such-path/subdir", true /* print progress dots */) == Restore_TargetPathNotFound); } // Log out client->QueryFinished(); sSocket.Close(); } // Compare the restored files compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query10.log " "-Wwarning " "\"compare -cEQ Test1 testfiles/restore-Test1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; #ifdef WIN32 // make one of the files read-only, expect a compare failure compareReturnValue = ::system("attrib +r " "testfiles\\restore-Test1\\f1.dat"); TEST_RETURN(compareReturnValue, 0); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query10a.log " "-Werror " "\"compare -cEQ Test1 testfiles/restore-Test1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // set it back, expect no failures compareReturnValue = ::system("attrib -r " "testfiles\\restore-Test1\\f1.dat"); TEST_RETURN(compareReturnValue, 0); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf -l testfiles/query10a.log " "-Wwarning " "\"compare -cEQ Test1 testfiles/restore-Test1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // change the timestamp on a file, expect a compare failure char* testfile = "testfiles\\restore-Test1\\f1.dat"; HANDLE handle = openfile(testfile, O_RDWR, 0); TEST_THAT(handle != INVALID_HANDLE_VALUE); FILETIME creationTime, lastModTime, lastAccessTime; TEST_THAT(GetFileTime(handle, &creationTime, &lastAccessTime, &lastModTime) != 0); TEST_THAT(CloseHandle(handle)); FILETIME dummyTime = lastModTime; dummyTime.dwHighDateTime -= 100; // creation time is backed up, so changing it should cause // a compare failure TEST_THAT(set_file_time(testfile, dummyTime, lastModTime, lastAccessTime)); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query10a.log " "-Werror " "\"compare -cEQ Test1 testfiles/restore-Test1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // last access time is not backed up, so it cannot be compared TEST_THAT(set_file_time(testfile, creationTime, lastModTime, dummyTime)); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query10a.log " "-Wwarning " "\"compare -cEQ Test1 testfiles/restore-Test1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // last write time is backed up, so changing it should cause // a compare failure TEST_THAT(set_file_time(testfile, creationTime, dummyTime, lastAccessTime)); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query10a.log " "-Werror " "\"compare -cEQ Test1 testfiles/restore-Test1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // set back to original values, check that compare succeeds TEST_THAT(set_file_time(testfile, creationTime, lastModTime, lastAccessTime)); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query10a.log " "-Wwarning " "\"compare -cEQ Test1 testfiles/restore-Test1\" " "quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); #endif // WIN32 TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Add files with current time\n"); // Add some more files and modify others // Use the m flag this time so they have a recent modification time #ifdef WIN32 TEST_THAT(::system("tar xzvmf testfiles/test3.tgz " "-C testfiles") == 0); #else TEST_THAT(::system("gzip -d < testfiles/test3.tgz " "| ( cd testfiles && tar xmf - )") == 0); #endif // Wait and test wait_for_backup_operation("bbackupd to sync new files"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query5.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // Rename directory printf("\n==== Rename directory\n"); TEST_THAT(rename("testfiles/TestDir1/sub23/dhsfdss", "testfiles/TestDir1/renamed-dir") == 0); wait_for_backup_operation("bbackupd to sync renamed directory"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query6.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // and again, but with quick flag compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query6q.log " "-Wwarning \"compare -acqQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Rename some files -- one under the threshold, others above printf("\n==== Rename files\n"); TEST_THAT(rename("testfiles/TestDir1/continousupdate", "testfiles/TestDir1/continousupdate-ren") == 0); TEST_THAT(rename("testfiles/TestDir1/df324", "testfiles/TestDir1/df324-ren") == 0); TEST_THAT(rename("testfiles/TestDir1/sub23/find2perl", "testfiles/TestDir1/find2perl-ren") == 0); wait_for_backup_operation("bbackupd to sync renamed files"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query6.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // Check that modifying files with madly in the future // timestamps still get added printf("\n==== Create a file with timestamp way ahead " "in the future\n"); // Time critical, so sync wait_for_sync_start(); // Then wait a second, to make sure the scan is complete ::safe_sleep(1); // Then modify an existing file { FILE *f = fopen("testfiles/TestDir1/sub23/" "in-the-future", "w"); TEST_THAT(f != 0); fprintf(f, "Back to the future!\n"); fclose(f); // and then move the time forwards! struct timeval times[2]; BoxTimeToTimeval(GetCurrentBoxTime() + SecondsToBoxTime((time_t)(365*24*60*60)), times[1]); times[0] = times[1]; TEST_THAT(::utimes("testfiles/TestDir1/sub23/" "in-the-future", times) == 0); } // Wait and test wait_for_backup_operation("bbackup to sync future file"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query3e.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Change client store marker\n"); // Then... connect to the server, and change the // client store marker. See what that does! { bool done = false; int tries = 4; while(!done && tries > 0) { try { std::auto_ptr protocol = Connect(context); // Make sure the marker isn't zero, // because that's the default, and // it should have changed std::auto_ptr loginConf(protocol->QueryLogin(0x01234567, 0)); TEST_THAT(loginConf->GetClientStoreMarker() != 0); // Change it to something else protocol->QuerySetClientStoreMarker(12); // Success! done = true; // Log out protocol->QueryFinished(); sSocket.Close(); } catch(...) { tries--; } } TEST_THAT(done); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Check change of store marker pauses daemon\n"); // Make a change to a file, to detect whether or not // it's hanging around waiting to retry. { FILE *f = ::fopen("testfiles/TestDir1/fileaftermarker", "w"); TEST_THAT(f != 0); ::fprintf(f, "Lovely file you got there."); ::fclose(f); } // Wait a little bit longer than usual wait_for_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION * 3) / 2, "bbackupd to detect changed store marker"); // Test that there *are* differences compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query6.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Different); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; // 100 seconds - (12*3/2) wait_for_operation(82, "bbackupd to recover"); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; #ifndef WIN32 printf("\n==== Interrupted restore\n"); { do_interrupted_restore(context, restoredirid); int64_t resumesize = 0; TEST_THAT(FileExists("testfiles/" "restore-interrupt.boxbackupresume", &resumesize)); // make sure it has recorded something to resume TEST_THAT(resumesize > 16); printf("\n==== Resume restore\n"); std::auto_ptr client = ConnectAndLogin(context, BackupProtocolClientLogin::Flags_ReadOnly); // Check that the restore fn returns resume possible, // rather than doing anything TEST_THAT(BackupClientRestore(*client, restoredirid, "Test1", "testfiles/restore-interrupt", true /* print progress dots */) == Restore_ResumePossible); // Then resume it TEST_THAT(BackupClientRestore(*client, restoredirid, "Test1", "testfiles/restore-interrupt", true /* print progress dots */, false /* deleted files */, false /* undelete server */, true /* resume */) == Restore_Complete); client->QueryFinished(); sSocket.Close(); // Then check it has restored the correct stuff compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query14.log " "-Wwarning \"compare -cEQ Test1 " "testfiles/restore-interrupt\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); } #endif // !WIN32 TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; printf("\n==== Check restore deleted files\n"); { std::auto_ptr client = ConnectAndLogin(context, 0 /* read-write */); // Do restore and undelete TEST_THAT(BackupClientRestore(*client, deldirid, "Test1", "testfiles/restore-Test1-x1-2", true /* print progress dots */, true /* deleted files */, true /* undelete on server */) == Restore_Complete); client->QueryFinished(); sSocket.Close(); // Do a compare with the now undeleted files compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query11.log " "-Wwarning " "\"compare -cEQ Test1/x1 " "testfiles/restore-Test1-x1-2\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); } // Final check on notifications TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; #ifdef WIN32 printf("\n==== Testing locked file behaviour:\n"); // Test that locked files cannot be backed up, // and the appropriate error is reported. // Wait for the sync to finish, so that we have time to work wait_for_sync_end(); // Now we have about three seconds to work handle = openfile("testfiles/TestDir1/lockedfile", O_CREAT | O_EXCL | O_LOCK, 0); TEST_THAT(handle != INVALID_HANDLE_VALUE); if (handle != 0) { // first sync will ignore the file, it's too new wait_for_sync_end(); TEST_THAT(!TestFileExists("testfiles/" "notifyran.read-error.1")); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if (handle != 0) { // this sync should try to back up the file, // and fail, because it's locked wait_for_sync_end(); TEST_THAT(TestFileExists("testfiles/" "notifyran.read-error.1")); TEST_THAT(!TestFileExists("testfiles/" "notifyran.read-error.2")); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if (handle != 0) { // now close the file and check that it is // backed up on the next run. CloseHandle(handle); wait_for_sync_end(); // still no read errors? TEST_THAT(!TestFileExists("testfiles/" "notifyran.read-error.2")); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if (handle != 0) { // compare, and check that it works // reports the correct error message (and finishes) compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query15a.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if (handle != 0) { // open the file again, compare and check that compare // reports the correct error message (and finishes) handle = openfile("testfiles/TestDir1/lockedfile", O_LOCK, 0); TEST_THAT(handle != INVALID_HANDLE_VALUE); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query15.log " "-Werror \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Error); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // close the file again, check that compare // works again CloseHandle(handle); } TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if (handle != 0) { compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query15a.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); } #endif // Kill the daemon terminate_bbackupd(bbackupd_pid); // Start it again cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); TEST_THAT(ServerIsAlive(bbackupd_pid)); TEST_THAT(ServerIsAlive(bbstored_pid)); if (!ServerIsAlive(bbackupd_pid)) return 1; if (!ServerIsAlive(bbstored_pid)) return 1; if(bbackupd_pid != -1 && bbackupd_pid != 0) { // Wait and compare (a little bit longer than usual) wait_for_operation( (TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2, "bbackupd to sync everything"); compareReturnValue = ::system(BBACKUPQUERY " " "-c testfiles/bbackupd.conf " "-l testfiles/query4a.log " "-Wwarning \"compare -acQ\" quit"); TEST_RETURN(compareReturnValue, BackupQueries::ReturnCode::Compare_Same); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); // Kill it again terminate_bbackupd(bbackupd_pid); } } /* // List the files on the server - why? ::system(BBACKUPQUERY " -q -c testfiles/bbackupd.conf " "-l testfiles/queryLIST.log \"list -rotdh\" quit"); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); */ #ifndef WIN32 if(::getuid() == 0) { ::printf("WARNING: This test was run as root. " "Some tests have been omitted.\n"); } #endif return 0; } int test(int argc, const char *argv[]) { // SSL library SSLLib::Initialise(); // Keys for subsystems BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); // Initial files #ifdef WIN32 TEST_THAT(::system("tar xzvf testfiles/test_base.tgz " "-C testfiles") == 0); #else TEST_THAT(::system("gzip -d < testfiles/test_base.tgz " "| ( cd testfiles && tar xf - )") == 0); #endif // Do the tests int r = test_basics(); if(r != 0) return r; r = test_setupaccount(); if(r != 0) return r; r = test_run_bbstored(); TEST_THAT(r == 0); if(r != 0) return r; r = test_bbackupd(); if(r != 0) { if (bbackupd_pid) { KillServer(bbackupd_pid); } if (bbstored_pid) { KillServer(bbstored_pid); } return r; } test_kill_bbstored(); return 0; } boxbackup/test/backupdiff/0000775000175000017500000000000011652362372016376 5ustar siretartsiretartboxbackup/test/backupdiff/testbackupdiff.cpp0000664000175000017500000004464011126433077022105 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: testbackupdiff.cpp // Purpose: Test diffing routines for backup store files // Created: 12/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "Test.h" #include "BackupClientCryptoKeys.h" #include "BackupStoreFile.h" #include "BackupStoreFilenameClear.h" #include "FileStream.h" #include "BackupStoreFileWire.h" #include "BackupStoreObjectMagic.h" #include "BackupStoreFileCryptVar.h" #include "BackupStoreException.h" #include "CollectInBufferStream.h" #include "MemLeakFindOn.h" using namespace BackupStoreFileCryptVar; // from another file void create_test_files(); bool files_identical(const char *file1, const char *file2) { FileStream f1(file1); FileStream f2(file2); if(f1.BytesLeftToRead() != f2.BytesLeftToRead()) { return false; } while(f1.StreamDataLeft()) { char buffer1[2048]; char buffer2[2048]; int s = f1.Read(buffer1, sizeof(buffer1)); if(f2.Read(buffer2, s) != s) { return false; } if(::memcmp(buffer1, buffer2, s) != 0) { return false; } } if(f2.StreamDataLeft()) { return false; } return true; } bool make_file_of_zeros(const char *filename, size_t size) { #ifdef WIN32 HANDLE handle = openfile(filename, O_WRONLY | O_CREAT | O_EXCL, 0); TEST_THAT(handle != INVALID_HANDLE_VALUE); TEST_THAT(SetFilePointer(handle, size, NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER); TEST_THAT(GetLastError() == NO_ERROR); BOOL result = SetEndOfFile(handle); if (result != TRUE) { BOX_ERROR("Failed to create large file " << filename << " (" << (size >> 20) << " MB): " << GetErrorMessage(GetLastError())); } TEST_THAT(result == TRUE); TEST_THAT(CloseHandle(handle) == TRUE); #else int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd < 0) perror(filename); TEST_THAT(fd >= 0); TEST_THAT(ftruncate(fd, size) == 0); TEST_THAT(close(fd) == 0); #endif bool correct_size = ((size_t)TestGetFileSize(filename) == size); TEST_THAT(correct_size); if (!correct_size) { BOX_ERROR("Failed to create large file " << filename << " (" << (size >> 20) << " MB): " << "got " << (TestGetFileSize(filename) >> 20) << " MB instead"); } return correct_size; } void check_encoded_file(const char *filename, int64_t OtherFileID, int new_blocks_expected, int old_blocks_expected) { FileStream enc(filename); // Use the interface verify routine int64_t otherIDFromFile = 0; TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc, &otherIDFromFile)); TEST_THAT(otherIDFromFile == OtherFileID); // Now do our own reading enc.Seek(0, IOStream::SeekType_Absolute); BackupStoreFile::MoveStreamPositionToBlockIndex(enc); // Read in header to check magic value is as expected file_BlockIndexHeader hdr; TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0)); TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)); TEST_THAT((uint64_t)box_ntoh64(hdr.mOtherFileID) == (uint64_t)OtherFileID); // number of blocks int64_t nblocks = box_ntoh64(hdr.mNumBlocks); BOX_TRACE("Reading index from '" << filename << "', has " << nblocks << " blocks"); BOX_TRACE("======== ===== ========== ======== ========"); BOX_TRACE(" Index Where EncSz/Idx Size WChcksm"); // Read them all in int64_t nnew = 0, nold = 0; for(int64_t b = 0; b < nblocks; ++b) { file_BlockIndexEntry en; TEST_THAT(enc.ReadFullBuffer(&en, sizeof(en), 0)); int64_t s = box_ntoh64(en.mEncodedSize); // Decode the rest uint64_t iv = box_ntoh64(hdr.mEntryIVBase); iv += b; sBlowfishDecryptBlockEntry.SetIV(&iv); file_BlockIndexEntryEnc entryEnc; sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc), en.mEnEnc, sizeof(en.mEnEnc)); if(s > 0) { nnew++; BOX_TRACE(std::setw(8) << b << " this s=" << std::setw(8) << s << " " << std::setw(8) << ntohl(entryEnc.mSize) << " " << std::setw(8) << std::setfill('0') << std::hex << ntohl(entryEnc.mWeakChecksum)); } else { nold++; BOX_TRACE(std::setw(8) << b << " other i=" << std::setw(8) << (0-s) << " " << std::setw(8) << ntohl(entryEnc.mSize) << " " << std::setw(8) << std::setfill('0') << std::hex << ntohl(entryEnc.mWeakChecksum)); } } BOX_TRACE("======== ===== ========== ======== ========"); TEST_THAT(new_blocks_expected == nnew); TEST_THAT(old_blocks_expected == nold); } void test_diff(int from, int to, int new_blocks_expected, int old_blocks_expected, bool expect_completely_different = false) { // First, get the block index of the thing it's comparing against char from_encoded[256]; sprintf(from_encoded, "testfiles/f%d.encoded", from); FileStream blockindex(from_encoded); BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); // make filenames char from_orig[256]; sprintf(from_orig, "testfiles/f%d", from); char to_encoded[256]; sprintf(to_encoded, "testfiles/f%d.encoded", to); char to_diff[256]; sprintf(to_diff, "testfiles/f%d.diff", to); char to_orig[256]; sprintf(to_orig, "testfiles/f%d", to); char rev_diff[256]; sprintf(rev_diff, "testfiles/f%d.revdiff", to); char from_rebuild[256]; sprintf(from_rebuild, "testfiles/f%d.rebuilt", to); char from_rebuild_dec[256]; sprintf(from_rebuild_dec, "testfiles/f%d.rebuilt_dec", to); // Then call the encode varient for diffing files bool completelyDifferent = !expect_completely_different; // oposite of what we want { BackupStoreFilenameClear f1name("filename"); FileStream out(to_diff, O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded( BackupStoreFile::EncodeFileDiff( to_orig, 1 /* dir ID */, f1name, 1000 + from /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, NULL, // DiffTimer interface 0, &completelyDifferent)); encoded->CopyStreamTo(out); } TEST_THAT(completelyDifferent == expect_completely_different); // Test that the number of blocks in the file match what's expected check_encoded_file(to_diff, expect_completely_different?(0):(1000 + from), new_blocks_expected, old_blocks_expected); // filename char to_testdec[256]; sprintf(to_testdec, "testfiles/f%d.testdec", to); if(!completelyDifferent) { // Then produce a combined file { FileStream diff(to_diff); FileStream diff2(to_diff); FileStream from(from_encoded); FileStream out(to_encoded, O_WRONLY | O_CREAT | O_EXCL); BackupStoreFile::CombineFile(diff, diff2, from, out); } // And check it check_encoded_file(to_encoded, 0, new_blocks_expected + old_blocks_expected, 0); } else { #ifdef WIN32 // Emulate the above stage! char src[256], dst[256]; sprintf(src, "testfiles\\f%d.diff", to); sprintf(dst, "testfiles\\f%d.encoded", to); TEST_THAT(CopyFile(src, dst, FALSE) != 0) #else // Emulate the above stage! char cmd[256]; sprintf(cmd, "cp testfiles/f%d.diff testfiles/f%d.encoded", to, to); ::system(cmd); #endif } // Decode it { FileStream enc(to_encoded); BackupStoreFile::DecodeFile(enc, to_testdec, IOStream::TimeOutInfinite); TEST_THAT(files_identical(to_orig, to_testdec)); } // Then do some comparisons against the block index { FileStream index(to_encoded); BackupStoreFile::MoveStreamPositionToBlockIndex(index); TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(to_orig, index, IOStream::TimeOutInfinite) == true); } { char from_orig[256]; sprintf(from_orig, "testfiles/f%d", from); FileStream index(to_encoded); BackupStoreFile::MoveStreamPositionToBlockIndex(index); TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(from_orig, index, IOStream::TimeOutInfinite) == files_identical(from_orig, to_orig)); } // Check that combined index creation works as expected { // Load a combined index into memory FileStream diff(to_diff); FileStream from(from_encoded); std::auto_ptr indexCmbStr(BackupStoreFile::CombineFileIndices(diff, from)); CollectInBufferStream indexCmb; indexCmbStr->CopyStreamTo(indexCmb); // Then check that it's as expected! FileStream result(to_encoded); BackupStoreFile::MoveStreamPositionToBlockIndex(result); CollectInBufferStream index; result.CopyStreamTo(index); TEST_THAT(indexCmb.GetSize() == index.GetSize()); TEST_THAT(::memcmp(indexCmb.GetBuffer(), index.GetBuffer(), index.GetSize()) == 0); } // Check that reverse delta can be made, and that it decodes OK { // Create reverse delta { bool reversedCompletelyDifferent = !completelyDifferent; FileStream diff(to_diff); FileStream from(from_encoded); FileStream from2(from_encoded); FileStream reversed(rev_diff, O_WRONLY | O_CREAT); BackupStoreFile::ReverseDiffFile(diff, from, from2, reversed, to, &reversedCompletelyDifferent); TEST_THAT(reversedCompletelyDifferent == completelyDifferent); } // Use it to combine a file { FileStream diff(rev_diff); FileStream diff2(rev_diff); FileStream from(to_encoded); FileStream out(from_rebuild, O_WRONLY | O_CREAT | O_EXCL); BackupStoreFile::CombineFile(diff, diff2, from, out); } // And then confirm that this file is actually the one we want { FileStream enc(from_rebuild); BackupStoreFile::DecodeFile(enc, from_rebuild_dec, IOStream::TimeOutInfinite); TEST_THAT(files_identical(from_orig, from_rebuild_dec)); } // Do some extra checking { TEST_THAT(files_identical(from_rebuild, from_encoded)); } } } void test_combined_diff(int version1, int version2, int serial) { char combined_file[256]; char last_diff[256]; sprintf(last_diff, "testfiles/f%d.diff", version1 + 1); // ie from version1 to version1 + 1 for(int v = version1 + 2; v <= version2; ++v) { FileStream diff1(last_diff); char next_diff[256]; sprintf(next_diff, "testfiles/f%d.diff", v); FileStream diff2(next_diff); FileStream diff2b(next_diff); sprintf(combined_file, "testfiles/comb%d_%d.cmbdiff", version1, v); FileStream out(combined_file, O_WRONLY | O_CREAT); BackupStoreFile::CombineDiffs(diff1, diff2, diff2b, out); strcpy(last_diff, combined_file); } // Then do a combine on it, and check that it decodes to the right thing char orig_enc[256]; sprintf(orig_enc, "testfiles/f%d.encoded", version1); char combined_out[256]; sprintf(combined_out, "testfiles/comb%d_%d.out", version1, version2); { FileStream diff(combined_file); FileStream diff2(combined_file); FileStream from(orig_enc); FileStream out(combined_out, O_WRONLY | O_CREAT); BackupStoreFile::CombineFile(diff, diff2, from, out); } char combined_out_dec[256]; sprintf(combined_out_dec, "testfiles/comb%d_%d_s%d.dec", version1, version2, serial); char to_orig[256]; sprintf(to_orig, "testfiles/f%d", version2); { FileStream enc(combined_out); BackupStoreFile::DecodeFile(enc, combined_out_dec, IOStream::TimeOutInfinite); TEST_THAT(files_identical(to_orig, combined_out_dec)); } } #define MAX_DIFF 9 void test_combined_diffs() { int serial = 0; // Number of items to combine at once for(int stages = 2; stages <= 4; ++stages) { // Offset to get complete coverage for(int offset = 0; offset < stages; ++offset) { // And then actual end file number for(int f = 0; f <= (MAX_DIFF - stages - offset); ++f) { // And finally, do something! test_combined_diff(offset + f, offset + f + stages, ++serial); } } } } int test(int argc, const char *argv[]) { // Want to trace out all the details #ifndef BOX_RELEASE_BUILD #ifndef WIN32 BackupStoreFile::TraceDetailsOfDiffProcess = true; #endif #endif // Create all the test files create_test_files(); // Setup the crypto BackupClientCryptoKeys_Setup("testfiles/backup.keys"); // Encode the first file { BackupStoreFilenameClear f0name("f0"); FileStream out("testfiles/f0.encoded", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/f0", 1 /* dir ID */, f0name)); encoded->CopyStreamTo(out); out.Close(); check_encoded_file("testfiles/f0.encoded", 0, 33, 0); } // Check the "seek to index" code { FileStream enc("testfiles/f0.encoded"); BackupStoreFile::MoveStreamPositionToBlockIndex(enc); // Read in header to check magic value is as expected file_BlockIndexHeader hdr; TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0)); TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)); } // Diff some files -- parameters are from number, to number, // then the number of new blocks expected, and the number of old blocks expected. // Diff the original file to a copy of itself, and check that there is no data in the file // This checks that the hash table is constructed properly, because two of the blocks share // the same weak checksum. test_diff(0, 1, 0, 33); // Insert some new data // Blocks from old file moved whole, but put in different order test_diff(1, 2, 7, 32); // Delete some data, but not on block boundaries test_diff(2, 3, 1, 29); // Add a very small amount of data, not on block boundary // delete a little data test_diff(3, 4, 3, 25); // 1 byte insertion between two blocks test_diff(4, 5, 1, 28); // a file with some new content at the very beginning // NOTE: You might expect the last numbers to be 2, 29, but the small 1 byte block isn't searched for test_diff(5, 6, 3, 28); // some new content at the very end // NOTE: 1 byte block deleted, so number aren't what you'd initial expect. test_diff(6, 7, 2, 30); // a completely different file, with no blocks matching. test_diff(7, 8, 14, 0, true /* completely different expected */); // diff to zero sized file test_diff(8, 9, 0, 0, true /* completely different expected */); // Test that combining diffs works test_combined_diffs(); // Check zero sized file works OK to encode on its own, using normal encoding { { // Encode BackupStoreFilenameClear fn("filename"); FileStream out("testfiles/f9.zerotest", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/f9", 1 /* dir ID */, fn)); encoded->CopyStreamTo(out); out.Close(); check_encoded_file("testfiles/f9.zerotest", 0, 0, 0); } { // Decode FileStream enc("testfiles/f9.zerotest"); BackupStoreFile::DecodeFile(enc, "testfiles/f9.testdec.zero", IOStream::TimeOutInfinite); TEST_THAT(files_identical("testfiles/f9", "testfiles/f9.testdec.zero")); } } #ifndef WIN32 // Check that symlinks aren't diffed TEST_THAT(::symlink("f2", "testfiles/f2.symlink") == 0) // And go and diff it against the previous encoded file { bool completelyDifferent = false; { FileStream blockindex("testfiles/f1.encoded"); BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); BackupStoreFilenameClear f1name("filename"); FileStream out("testfiles/f2.symlink.diff", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded( BackupStoreFile::EncodeFileDiff( "testfiles/f2.symlink", 1 /* dir ID */, f1name, 1001 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, NULL, // DiffTimer interface 0, &completelyDifferent)); encoded->CopyStreamTo(out); } TEST_THAT(completelyDifferent == true); check_encoded_file("testfiles/f2.symlink.diff", 0, 0, 0); } #endif // Check that diffing against a file which isn't "complete" and // references another isn't allowed { FileStream blockindex("testfiles/f1.diff"); BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); BackupStoreFilenameClear f1name("filename"); FileStream out("testfiles/f2.testincomplete", O_WRONLY | O_CREAT | O_EXCL); TEST_CHECK_THROWS(BackupStoreFile::EncodeFileDiff("testfiles/f2", 1 /* dir ID */, f1name, 1001 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, 0, 0), BackupStoreException, CannotDiffAnIncompleteStoreFile); } // Found a nasty case where files of lots of the same thing // suck up lots of processor time -- because of lots of matches // found. Check this out! #ifdef WIN32 BOX_WARNING("Testing diffing two large streams, may take a while!"); ::fflush(stderr); #endif if (!make_file_of_zeros("testfiles/zero.0", 20*1024*1024)) { return 1; } if (!make_file_of_zeros("testfiles/zero.1", 200*1024*1024)) { remove("testfiles/zero.0"); return 1; } // Generate a first encoded file { BackupStoreFilenameClear f0name("zero.0"); FileStream out("testfiles/zero.0.enc", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/zero.0", 1 /* dir ID */, f0name)); encoded->CopyStreamTo(out); } // Then diff from it -- time how long it takes... { int beginTime = time(0); FileStream blockindex("testfiles/zero.0.enc"); BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); BackupStoreFilenameClear f1name("zero.1"); FileStream out("testfiles/zero.1.enc", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded(BackupStoreFile::EncodeFileDiff("testfiles/zero.1", 1 /* dir ID */, f1name, 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, 0, 0)); encoded->CopyStreamTo(out); printf("Time taken: %d seconds\n", (int)(time(0) - beginTime)); #ifdef WIN32 TEST_THAT(time(0) < (beginTime + 300)); #else TEST_THAT(time(0) < (beginTime + 40)); #endif } // Remove zero-files to save disk space remove("testfiles/zero.0"); remove("testfiles/zero.1"); #if 0 // Code for a nasty real world example! (16Mb files, won't include them in the distribution // for obvious reasons...) // Generate a first encoded file { BackupStoreFilenameClear f0name("0000000000000000.old"); FileStream out("testfiles/0000000000000000.enc.0", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded(BackupStoreFile::EncodeFile("/Users/ben/Desktop/0000000000000000.old", 1 /* dir ID */, f0name)); encoded->CopyStreamTo(out); } // Then diff from it -- time how long it takes... { int beginTime = time(0); FileStream blockindex("testfiles/0000000000000000.enc.0"); BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); BackupStoreFilenameClear f1name("0000000000000000.new"); FileStream out("testfiles/0000000000000000.enc.1", O_WRONLY | O_CREAT | O_EXCL); std::auto_ptr encoded(BackupStoreFile::EncodeFileDiff("/Users/ben/Desktop/0000000000000000.new", 1 /* dir ID */, f1name, 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, 0, 0)); encoded->CopyStreamTo(out); TEST_THAT(time(0) < (beginTime + 20)); } #endif // 0 return 0; } boxbackup/test/backupdiff/difftestfiles.cpp0000664000175000017500000001536611173734217021747 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: createtestfiles.cpp // Purpose: Create the test files for the backupdiff test // Created: 12/1/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "FileStream.h" #include "PartialReadStream.h" #include "Test.h" #include "RollingChecksum.h" #include "MemLeakFindOn.h" #define ACT_END 0 #define ACT_COPY 1 #define ACT_NEW 2 #define ACT_SKIP 3 #define ACT_COPYEND 4 typedef struct { int action, length, seed; } gen_action; #define INITIAL_FILE_LENGTH (128*1024 + 342) gen_action file1actions[] = { {ACT_COPYEND, 0, 0}, {ACT_END, 0, 0} }; gen_action file2actions[] = { {ACT_COPY, 16*1024, 0}, // Do blocks on block boundaries, but swapped around a little {ACT_SKIP, 4*1024, 0}, {ACT_COPY, 8*1024, 0}, {ACT_SKIP, -12*1024, 0}, {ACT_COPY, 4*1024, 0}, {ACT_SKIP, 8*1024, 0}, // Get rest of file with some new data inserted {ACT_COPY, 37*1024 + 12, 0}, {ACT_NEW, 23*1024 + 129, 23990}, {ACT_COPYEND, 0, 0}, {ACT_END, 0, 0} }; gen_action file3actions[] = { {ACT_COPY, 12*1024 + 983, 0}, {ACT_SKIP, 37*1024 + 12, 0}, {ACT_COPYEND, 0, 0}, {ACT_END, 0, 0} }; gen_action file4actions[] = { {ACT_COPY, 20*1024 + 2385, 0}, {ACT_NEW, 12, 2334}, {ACT_COPY, 16*1024 + 385, 0}, {ACT_SKIP, 9*1024 + 42, 0}, {ACT_COPYEND, 0, 0}, {ACT_END, 0, 0} }; // insert 1 byte a block into the file, between two other blocks gen_action file5actions[] = { {ACT_COPY, 4*1024, 0}, {ACT_NEW, 1, 2334}, {ACT_COPYEND, 0, 0}, {ACT_END, 0, 0} }; gen_action file6actions[] = { {ACT_NEW, 6*1024, 12353452}, {ACT_COPYEND, 0, 0}, {ACT_END, 0, 0} }; // but delete that one byte block, it's annoying gen_action file7actions[] = { {ACT_COPY, 10*1024, 0}, {ACT_SKIP, 1, 0}, {ACT_COPYEND, 0, 0}, {ACT_NEW, 7*1024, 1235352}, {ACT_END, 0, 0} }; gen_action file8actions[] = { {ACT_NEW, 54*1024 + 9, 125352}, {ACT_END, 0, 0} }; gen_action file9actions[] = { {ACT_END, 0, 0} }; gen_action *testfiles[] = {file1actions, file2actions, file3actions, file4actions, file5actions, file6actions, file7actions, file8actions, file9actions, 0}; // Nice random data for testing written files class R250 { public: // Set up internal state table with 32-bit random numbers. // The bizarre bit-twiddling is because rand() returns 16 bits of which // the bottom bit is always zero! Hence, I use only some of the bits. // You might want to do something better than this.... R250(int seed) : posn1(0), posn2(103) { // populate the state and incr tables srand(seed); for (int i = 0; i != stateLen; ++i) { state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); incrTable[i] = i == stateLen - 1 ? 0 : i + 1; } // stir up the numbers to ensure they're random for (int j = 0; j != stateLen * 4; ++j) (void) next(); } // Returns the next random number. Xor together two elements separated // by 103 mod 250, replacing the first element with the result. Then // increment the two indices mod 250. inline int next() { int ret = (state[posn1] ^= state[posn2]); // xor and replace element posn1 = incrTable[posn1]; // increment indices using lookup table posn2 = incrTable[posn2]; return ret; } private: enum { stateLen = 250 }; // length of the state table int state[stateLen]; // holds the random number state int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen int posn1, posn2; // indices into the state table }; void make_random_data(void *buffer, int size, int seed) { R250 rand(seed); int n = size / sizeof(int); int *b = (int*)buffer; for(int l = 0; l < n; ++l) { b[l] = rand.next(); } } void write_test_data(IOStream &rstream, int size, int seed) { R250 rand(seed); while(size > 0) { // make a nice buffer of data int buffer[2048/sizeof(int)]; for(unsigned int l = 0; l < (sizeof(buffer) / sizeof(int)); ++l) { buffer[l] = rand.next(); } // Write out... unsigned int w = size; if(w > sizeof(buffer)) w = sizeof(buffer); rstream.Write(buffer, w); size -= w; } } void gen_varient(IOStream &out, char *sourcename, gen_action *pact) { // Open source FileStream source(sourcename); while(true) { switch(pact->action) { case ACT_END: { // all done return; } case ACT_COPY: { PartialReadStream copy(source, pact->length); copy.CopyStreamTo(out); break; } case ACT_NEW: { write_test_data(out, pact->length, pact->seed); break; } case ACT_SKIP: { source.Seek(pact->length, IOStream::SeekType_Relative); break; } case ACT_COPYEND: { source.CopyStreamTo(out); break; } } ++pact; } } void create_test_files() { // First, the keys for the crypto { FileStream keys("testfiles/backup.keys", O_WRONLY | O_CREAT); write_test_data(keys, 1024, 237); } // Create the initial file -- needs various special properties... // 1) Two blocks much be the different, but have the same weak checksum // 2) A block must exist twice, but at an offset which isn't a multiple of the block size. { FileStream f0("testfiles/f0", O_WRONLY | O_CREAT); // Write first bit. write_test_data(f0, (16*1024), 20012); // Now repeated checksum blocks uint8_t blk[4096]; make_random_data(blk, sizeof(blk), 12201); // Three magic numbers which make the checksum work: Use this perl to find them: /* for($z = 1; $z < 4096; $z++) { for($n = 0; $n <= 255; $n++) { for($m = 0; $m <= 255; $m++) { if($n != $m && (($n*4096 + $m*(4096-$z)) % (64*1024) == ($n*(4096-$z) + $m*4096) % (64*1024))) { print "$z: $n $m\n"; } } } } */ blk[0] = 255; blk[1024] = 191; // Checksum to check RollingChecksum c1(blk, sizeof(blk)); // Write f0.Write(blk, sizeof(blk)); // Adjust block and write again uint8_t blk2[4096]; memcpy(blk2, blk, sizeof(blk2)); blk2[1024] = 255; blk2[0] = 191; TEST_THAT(::memcmp(blk2, blk, sizeof(blk)) != 0); RollingChecksum c2(blk2, sizeof(blk2)); f0.Write(blk2, sizeof(blk2)); // Check checksums TEST_THAT(c1.GetChecksum() == c2.GetChecksum()); // Another 4k block write_test_data(f0, (4*1024), 99209); // Offset block make_random_data(blk, 2048, 1234199); f0.Write(blk, 2048); f0.Write(blk, 2048); f0.Write(blk, 2048); make_random_data(blk, 2048, 1343278); f0.Write(blk, 2048); write_test_data(f0, INITIAL_FILE_LENGTH - (16*1024) - ((4*1024)*2) - (4*1024) - (2048*4), 202); } // Then... create the varients for(int l = 0; testfiles[l] != 0; ++l) { char n1[256]; char n2[256]; sprintf(n1, "testfiles/f%d", l + 1); sprintf(n2, "testfiles/f%d", l); FileStream f1(n1, O_WRONLY | O_CREAT); gen_varient(f1, n2, testfiles[l]); } } boxbackup/test/backupdiff/testextra0000664000175000017500000000004110347400657020336 0ustar siretartsiretartrm -rf testfiles mkdir testfiles boxbackup/config.guess0000775000175000017500000012502010347400657015640 0ustar siretartsiretart#! /bin/sh # Attempt to guess a canonical system name. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003 Free Software Foundation, Inc. timestamp='2004-03-03' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Per Bothner . # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # This script attempts to guess a canonical system name similar to # config.sub. If it succeeds, it prints the system name on stdout, and # exits with 0. Otherwise, it exits with 1. # # The plan is that this can be called by configure scripts if you # don't specify an explicit build system type. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit 0 ;; --version | -v ) echo "$version" ; exit 0 ;; --help | --h* | -h ) echo "$usage"; exit 0 ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > $dummy.c ; for c in cc gcc c89 c99 ; do if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ /usr/sbin/$sysctl 2>/dev/null || echo unknown)` case "${UNAME_MACHINE_ARCH}" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; *) machine=${UNAME_MACHINE_ARCH}-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently, or will in the future. case "${UNAME_MACHINE_ARCH}" in arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval $set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep __ELF__ >/dev/null then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "${UNAME_VERSION}" in Debian*) release='-gnu' ;; *) release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit 0 ;; amd64:OpenBSD:*:*) echo x86_64-unknown-openbsd${UNAME_RELEASE} exit 0 ;; amiga:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; arc:OpenBSD:*:*) echo mipsel-unknown-openbsd${UNAME_RELEASE} exit 0 ;; cats:OpenBSD:*:*) echo arm-unknown-openbsd${UNAME_RELEASE} exit 0 ;; hp300:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mac68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; macppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme68k:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvme88k:OpenBSD:*:*) echo m88k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; mvmeppc:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; pegasos:OpenBSD:*:*) echo powerpc-unknown-openbsd${UNAME_RELEASE} exit 0 ;; pmax:OpenBSD:*:*) echo mipsel-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sgi:OpenBSD:*:*) echo mipseb-unknown-openbsd${UNAME_RELEASE} exit 0 ;; sun3:OpenBSD:*:*) echo m68k-unknown-openbsd${UNAME_RELEASE} exit 0 ;; wgrisc:OpenBSD:*:*) echo mipsel-unknown-openbsd${UNAME_RELEASE} exit 0 ;; *:OpenBSD:*:*) echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} exit 0 ;; *:ekkoBSD:*:*) echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} exit 0 ;; macppc:MirBSD:*:*) echo powerppc-unknown-mirbsd${UNAME_RELEASE} exit 0 ;; *:MirBSD:*:*) echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} exit 0 ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE="alpha" ;; "EV4.5 (21064)") UNAME_MACHINE="alpha" ;; "LCA4 (21066/21068)") UNAME_MACHINE="alpha" ;; "EV5 (21164)") UNAME_MACHINE="alphaev5" ;; "EV5.6 (21164A)") UNAME_MACHINE="alphaev56" ;; "EV5.6 (21164PC)") UNAME_MACHINE="alphapca56" ;; "EV5.7 (21164PC)") UNAME_MACHINE="alphapca57" ;; "EV6 (21264)") UNAME_MACHINE="alphaev6" ;; "EV6.7 (21264A)") UNAME_MACHINE="alphaev67" ;; "EV6.8CB (21264C)") UNAME_MACHINE="alphaev68" ;; "EV6.8AL (21264B)") UNAME_MACHINE="alphaev68" ;; "EV6.8CX (21264D)") UNAME_MACHINE="alphaev68" ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE="alphaev69" ;; "EV7 (21364)") UNAME_MACHINE="alphaev7" ;; "EV7.9 (21364A)") UNAME_MACHINE="alphaev79" ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` exit 0 ;; Alpha*:OpenVMS:*:*) echo alpha-hp-vms exit 0 ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix exit 0 ;; 21064:Windows_NT:50:3) echo alpha-dec-winnt3.5 exit 0 ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit 0;; *:[Aa]miga[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-amigaos exit 0 ;; *:[Mm]orph[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-morphos exit 0 ;; *:OS/390:*:*) echo i370-ibm-openedition exit 0 ;; *:OS400:*:*) echo powerpc-ibm-os400 exit 0 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit 0;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit 0;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit 0 ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit 0 ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit 0 ;; DRS?6000:UNIX_SV:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7 && exit 0 ;; esac ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; i86pc:SunOS:5.*:*) echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` exit 0 ;; sun3*:SunOS:*:*) echo m68k-sun-sunos${UNAME_RELEASE} exit 0 ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos${UNAME_RELEASE} ;; sun4) echo sparc-sun-sunos${UNAME_RELEASE} ;; esac exit 0 ;; aushp:SunOS:*:*) echo sparc-auspex-sunos${UNAME_RELEASE} exit 0 ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit 0 ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint${UNAME_RELEASE} exit 0 ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint${UNAME_RELEASE} exit 0 ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint${UNAME_RELEASE} exit 0 ;; m68k:machten:*:*) echo m68k-apple-machten${UNAME_RELEASE} exit 0 ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} exit 0 ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit 0 ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix${UNAME_RELEASE} exit 0 ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix${UNAME_RELEASE} exit 0 ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix${UNAME_RELEASE} exit 0 ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c \ && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ && exit 0 echo mips-mips-riscos${UNAME_RELEASE} exit 0 ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit 0 ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit 0 ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit 0 ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit 0 ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit 0 ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit 0 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then echo m88k-dg-dgux${UNAME_RELEASE} else echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi exit 0 ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit 0 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit 0 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit 0 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit 0 ;; *:IRIX*:*:*) echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` exit 0 ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit 0 ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo rs6000-ibm-aix3.2.5 elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit 0 ;; *:AIX:*:[45]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${IBM_ARCH}-ibm-aix${IBM_REV} exit 0 ;; *:AIX:*:*) echo rs6000-ibm-aix exit 0 ;; ibmrt:4.4BSD:*|romp-ibm:BSD:*) echo romp-ibm-bsd4.4 exit 0 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to exit 0 ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit 0 ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit 0 ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit 0 ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit 0 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` case "${UNAME_MACHINE}" in 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "${sc_cpu_version}" in 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "${sc_kernel_bits}" in 32) HP_ARCH="hppa2.0n" ;; 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 esac ;; esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ ${HP_ARCH} = "hppa2.0w" ] then # avoid double evaluation of $set_cc_for_build test -n "$CC_FOR_BUILD" || eval $set_cc_for_build if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null then HP_ARCH="hppa2.0w" else HP_ARCH="hppa64" fi fi echo ${HP_ARCH}-hp-hpux${HPUX_REV} exit 0 ;; ia64:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux${HPUX_REV} exit 0 ;; 3050*:HI-UX:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 echo unknown-hitachi-hiuxwe2 exit 0 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) echo hppa1.1-hp-bsd exit 0 ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit 0 ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit 0 ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) echo hppa1.1-hp-osf exit 0 ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit 0 ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo ${UNAME_MACHINE}-unknown-osf1mk else echo ${UNAME_MACHINE}-unknown-osf1 fi exit 0 ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit 0 ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit 0 ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit 0 ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit 0 ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit 0 ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*TS:*:*:*) echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; *:UNICOS/mp:*:*) echo nv1-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit 0 ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit 0 ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit 0 ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit 0 ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit 0 ;; *:FreeBSD:*:*) # Determine whether the default compiler uses glibc. eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #if __GLIBC__ >= 2 LIBC=gnu #else LIBC= #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` # GNU/KFreeBSD systems have a "k" prefix to indicate we are using # FreeBSD's kernel, but not the complete OS. case ${LIBC} in gnu) kernel_only='k' ;; esac echo ${UNAME_MACHINE}-unknown-${kernel_only}freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`${LIBC:+-$LIBC} exit 0 ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit 0 ;; i*:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit 0 ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit 0 ;; x86:Interix*:[34]*) echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' exit 0 ;; [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) echo i${UNAME_MACHINE}-pc-mks exit 0 ;; i*:Windows_NT*:* | Pentium*:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we # UNAME_MACHINE based on the output of uname instead of i386? echo i586-pc-interix exit 0 ;; i*:UWIN*:*) echo ${UNAME_MACHINE}-pc-uwin exit 0 ;; p*:CYGWIN*:*) echo powerpcle-unknown-cygwin exit 0 ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit 0 ;; *:GNU:*:*) # the GNU system echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit 0 ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu exit 0 ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit 0 ;; arm*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; cris:Linux:*:*) echo cris-axis-linux-gnu exit 0 ;; ia64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; m68*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; mips:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips #undef mipsel #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mipsel #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips64 #undef mips64el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mips64el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips64 #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 ;; ppc:Linux:*:*) echo powerpc-unknown-linux-gnu exit 0 ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-gnu exit 0 ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} exit 0 ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-gnu ;; PA8*) echo hppa2.0-unknown-linux-gnu ;; *) echo hppa-unknown-linux-gnu ;; esac exit 0 ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-gnu exit 0 ;; s390:Linux:*:* | s390x:Linux:*:*) echo ${UNAME_MACHINE}-ibm-linux exit 0 ;; sh64*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sh*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit 0 ;; x86_64:Linux:*:*) echo x86_64-unknown-linux-gnu exit 0 ;; i*86:Linux:*:*) # The BFD linker knows what the default object file format is, so # first see if it will tell us. cd to the root directory to prevent # problems with other programs or directories called `ld' in the path. # Set LC_ALL=C to ensure ld outputs messages in English. ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ | sed -ne '/supported targets:/!d s/[ ][ ]*/ /g s/.*supported targets: *// s/ .*// p'` case "$ld_supported_targets" in elf32-i386) TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" ;; a.out-i386-linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" exit 0 ;; coff-i386) echo "${UNAME_MACHINE}-pc-linux-gnucoff" exit 0 ;; "") # Either a pre-BFD a.out linker (linux-gnuoldld) or # one that does not give us useful --help. echo "${UNAME_MACHINE}-pc-linux-gnuoldld" exit 0 ;; esac # Determine whether the default compiler is a.out or elf eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #ifdef __ELF__ # ifdef __GLIBC__ # if __GLIBC__ >= 2 LIBC=gnu # else LIBC=gnulibc1 # endif # else LIBC=gnulibc1 # endif #else #ifdef __INTEL_COMPILER LIBC=gnu #else LIBC=gnuaout #endif #endif #ifdef __dietlibc__ LIBC=dietlibc #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit 0 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit 0 ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo ${UNAME_MACHINE}-pc-os2-emx exit 0 ;; i*86:XTS-300:*:STOP) echo ${UNAME_MACHINE}-unknown-stop exit 0 ;; i*86:atheos:*:*) echo ${UNAME_MACHINE}-unknown-atheos exit 0 ;; i*86:syllable:*:*) echo ${UNAME_MACHINE}-pc-syllable exit 0 ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) echo i386-unknown-lynxos${UNAME_RELEASE} exit 0 ;; i*86:*DOS:*:*) echo ${UNAME_MACHINE}-pc-msdosdjgpp exit 0 ;; i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit 0 ;; i*86:*:5:[78]*) case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} exit 0 ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo ${UNAME_MACHINE}-pc-sco$UNAME_REL else echo ${UNAME_MACHINE}-pc-sysv32 fi exit 0 ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i386. echo i386-pc-msdosdjgpp exit 0 ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit 0 ;; paragon:*:*:*) echo i860-intel-osf1 exit 0 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 fi exit 0 ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit 0 ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit 0 ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit 0 ;; M68*:*:R3V[567]*:*) test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4.3${OS_REL} && exit 0 /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && echo i486-ncr-sysv4 && exit 0 ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos${UNAME_RELEASE} exit 0 ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit 0 ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos${UNAME_RELEASE} exit 0 ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) echo powerpc-unknown-lynxos${UNAME_RELEASE} exit 0 ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv${UNAME_RELEASE} exit 0 ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit 0 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit 0 ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit 0 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit 0 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit 0 ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit 0 ;; mc68*:A/UX:*:*) echo m68k-apple-aux${UNAME_RELEASE} exit 0 ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit 0 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv${UNAME_RELEASE} else echo mips-unknown-sysv${UNAME_RELEASE} fi exit 0 ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit 0 ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit 0 ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit 0 ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit 0 ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} exit 0 ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux${UNAME_RELEASE} exit 0 ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit 0 ;; *:Darwin:*:*) case `uname -p` in *86) UNAME_PROCESSOR=i686 ;; powerpc) UNAME_PROCESSOR=powerpc ;; esac echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit 0 ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} exit 0 ;; *:QNX:*:4*) echo i386-pc-qnx exit 0 ;; NSR-?:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit 0 ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit 0 ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit 0 ;; DS/*:UNIX_System_V:*:*) echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} exit 0 ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = "386"; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo ${UNAME_MACHINE}-unknown-plan9 exit 0 ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit 0 ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit 0 ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit 0 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit 0 ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit 0 ;; *:ITS:*:*) echo pdp10-unknown-its exit 0 ;; SEI:*:*:SEIUX) echo mips-sei-seiux${UNAME_RELEASE} exit 0 ;; *:DragonFly:*:*) echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit 0 ;; esac #echo '(No uname command or uname output not recognized.)' 1>&2 #echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 eval $set_cc_for_build cat >$dummy.c < # include #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (__arm) && defined (__acorn) && defined (__unix) printf ("arm-acorn-riscix"); exit (0); #endif #if defined (hp300) && !defined (hpux) printf ("m68k-hp-bsd\n"); exit (0); #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) # if !defined (ultrix) # include # if defined (BSD) # if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); # else # if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); # else printf ("vax-dec-bsd\n"); exit (0); # endif # endif # else printf ("vax-dec-bsd\n"); exit (0); # endif # else printf ("vax-dec-ultrix\n"); exit (0); # endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0 # Apollos put the system type in the environment. test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } # Convex versions that predate uname can use getsysinfo(1) if [ -x /usr/convex/getsysinfo ] then case `getsysinfo -f cpu_type` in c1*) echo c1-convex-bsd exit 0 ;; c2*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit 0 ;; c34*) echo c34-convex-bsd exit 0 ;; c38*) echo c38-convex-bsd exit 0 ;; c4*) echo c4-convex-bsd exit 0 ;; esac fi cat >&2 < in order to provide the needed information to handle your system. config.guess timestamp = $timestamp uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = ${UNAME_MACHINE} UNAME_RELEASE = ${UNAME_RELEASE} UNAME_SYSTEM = ${UNAME_SYSTEM} UNAME_VERSION = ${UNAME_VERSION} EOF exit 1 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: boxbackup/config.sub0000775000175000017500000007460410347400657015316 0ustar siretartsiretart#! /bin/sh # Configuration validation subroutine script. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003 Free Software Foundation, Inc. timestamp='2004-02-23' # This file is (in principle) common to ALL GNU software. # The presence of a machine in this file suggests that SOME GNU software # can handle that machine. It does not imply ALL GNU software can. # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS $0 [OPTION] ALIAS Canonicalize a configuration name. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit 0 ;; --version | -v ) echo "$version" ; exit 0 ;; --help | --h* | -h ) echo "$usage"; exit 0 ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" exit 1 ;; *local*) # First pass through any local machine types. echo $1 exit 0;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). # Here we must recognize all the valid KERNEL-OS combinations. maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` case $maybe_os in nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \ kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) os=-$maybe_os basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` ;; *) basic_machine=`echo $1 | sed 's/-[^-]*$//'` if [ $basic_machine != $1 ] then os=`echo $1 | sed 's/.*-/-/'` else os=; fi ;; esac ### Let's recognize common machines as not being operating systems so ### that things like config.sub decstation-3100 work. We also ### recognize some manufacturers as not being operating systems, so we ### can provide default operating systems below. case $os in -sun*os*) # Prevent following clause from handling this invalid input. ;; -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ -apple | -axis) os= basic_machine=$1 ;; -sim | -cisco | -oki | -wec | -winbond) os= basic_machine=$1 ;; -scout) ;; -wrs) os=-vxworks basic_machine=$1 ;; -chorusos*) os=-chorusos basic_machine=$1 ;; -chorusrdb) os=-chorusrdb basic_machine=$1 ;; -hiux*) os=-hiuxwe2 ;; -sco5) os=-sco3.2v5 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco4) os=-sco3.2v4 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2.[4-9]*) os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2v[4-9]*) # Don't forget version if it is 3.2v4 or newer. basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco*) os=-sco3.2v2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -udk*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -isc) os=-isc2.2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -clix*) basic_machine=clipper-intergraph ;; -isc*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -lynx*) os=-lynxos ;; -ptx*) basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` ;; -windowsnt*) os=`echo $os | sed -e 's/windowsnt/winnt/'` ;; -psos*) os=-psos ;; -mint | -mint[0-9]*) basic_machine=m68k-atari os=-mint ;; esac # Decode aliases for certain CPU-COMPANY combinations. case $basic_machine in # Recognize the basic CPU types without company name. # Some are omitted here because they have special meanings below. 1750a | 580 \ | a29k \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ | c4x | clipper \ | d10v | d30v | dlx | dsp16xx \ | fr30 | frv \ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ | i370 | i860 | i960 | ia64 \ | ip2k | iq2000 \ | m32r | m68000 | m68k | m88k | mcore \ | mips | mipsbe | mipseb | mipsel | mipsle \ | mips16 \ | mips64 | mips64el \ | mips64vr | mips64vrel \ | mips64orion | mips64orionel \ | mips64vr4100 | mips64vr4100el \ | mips64vr4300 | mips64vr4300el \ | mips64vr5000 | mips64vr5000el \ | mipsisa32 | mipsisa32el \ | mipsisa32r2 | mipsisa32r2el \ | mipsisa64 | mipsisa64el \ | mipsisa64r2 | mipsisa64r2el \ | mipsisa64sb1 | mipsisa64sb1el \ | mipsisa64sr71k | mipsisa64sr71kel \ | mipstx39 | mipstx39el \ | mn10200 | mn10300 \ | msp430 \ | ns16k | ns32k \ | openrisc | or32 \ | pdp10 | pdp11 | pj | pjl \ | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ | pyramid \ | sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ | sh64 | sh64le \ | sparc | sparc64 | sparc86x | sparclet | sparclite | sparcv9 | sparcv9b \ | strongarm \ | tahoe | thumb | tic4x | tic80 | tron \ | v850 | v850e \ | we32k \ | x86 | xscale | xstormy16 | xtensa \ | z8k) basic_machine=$basic_machine-unknown ;; m6811 | m68hc11 | m6812 | m68hc12) # Motorola 68HC11/12. basic_machine=$basic_machine-unknown os=-none ;; m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) ;; # We use `pc' rather than `unknown' # because (1) that's what they normally are, and # (2) the word "unknown" tends to confuse beginning users. i*86 | x86_64) basic_machine=$basic_machine-pc ;; # Object if more than one company name word. *-*-*) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ | avr-* \ | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ | clipper-* | cydra-* \ | d10v-* | d30v-* | dlx-* \ | elxsi-* \ | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ | h8300-* | h8500-* \ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ | i*86-* | i860-* | i960-* | ia64-* \ | ip2k-* | iq2000-* \ | m32r-* \ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ | m88110-* | m88k-* | mcore-* \ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ | mips16-* \ | mips64-* | mips64el-* \ | mips64vr-* | mips64vrel-* \ | mips64orion-* | mips64orionel-* \ | mips64vr4100-* | mips64vr4100el-* \ | mips64vr4300-* | mips64vr4300el-* \ | mips64vr5000-* | mips64vr5000el-* \ | mipsisa32-* | mipsisa32el-* \ | mipsisa32r2-* | mipsisa32r2el-* \ | mipsisa64-* | mipsisa64el-* \ | mipsisa64r2-* | mipsisa64r2el-* \ | mipsisa64sb1-* | mipsisa64sb1el-* \ | mipsisa64sr71k-* | mipsisa64sr71kel-* \ | mipstx39-* | mipstx39el-* \ | msp430-* \ | none-* | np1-* | nv1-* | ns16k-* | ns32k-* \ | orion-* \ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ | pyramid-* \ | romp-* | rs6000-* \ | sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ | sparc-* | sparc64-* | sparc86x-* | sparclet-* | sparclite-* \ | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ | tahoe-* | thumb-* \ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ | tron-* \ | v850-* | v850e-* | vax-* \ | we32k-* \ | x86-* | x86_64-* | xps100-* | xscale-* | xstormy16-* \ | xtensa-* \ | ymp-* \ | z8k-*) ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 386bsd) basic_machine=i386-unknown os=-bsd ;; 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) basic_machine=m68000-att ;; 3b*) basic_machine=we32k-att ;; a29khif) basic_machine=a29k-amd os=-udi ;; abacus) basic_machine=abacus-unknown ;; adobe68k) basic_machine=m68010-adobe os=-scout ;; alliant | fx80) basic_machine=fx80-alliant ;; altos | altos3068) basic_machine=m68k-altos ;; am29k) basic_machine=a29k-none os=-bsd ;; amd64) basic_machine=x86_64-pc ;; amd64-*) basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` ;; amdahl) basic_machine=580-amdahl os=-sysv ;; amiga | amiga-*) basic_machine=m68k-unknown ;; amigaos | amigados) basic_machine=m68k-unknown os=-amigaos ;; amigaunix | amix) basic_machine=m68k-unknown os=-sysv4 ;; apollo68) basic_machine=m68k-apollo os=-sysv ;; apollo68bsd) basic_machine=m68k-apollo os=-bsd ;; aux) basic_machine=m68k-apple os=-aux ;; balance) basic_machine=ns32k-sequent os=-dynix ;; c90) basic_machine=c90-cray os=-unicos ;; convex-c1) basic_machine=c1-convex os=-bsd ;; convex-c2) basic_machine=c2-convex os=-bsd ;; convex-c32) basic_machine=c32-convex os=-bsd ;; convex-c34) basic_machine=c34-convex os=-bsd ;; convex-c38) basic_machine=c38-convex os=-bsd ;; cray | j90) basic_machine=j90-cray os=-unicos ;; cr16c) basic_machine=cr16c-unknown os=-elf ;; crds | unos) basic_machine=m68k-crds ;; cris | cris-* | etrax*) basic_machine=cris-axis ;; crx) basic_machine=crx-unknown os=-elf ;; da30 | da30-*) basic_machine=m68k-da30 ;; decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) basic_machine=mips-dec ;; decsystem10* | dec10*) basic_machine=pdp10-dec os=-tops10 ;; decsystem20* | dec20*) basic_machine=pdp10-dec os=-tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) basic_machine=m68k-motorola ;; delta88) basic_machine=m88k-motorola os=-sysv3 ;; dpx20 | dpx20-*) basic_machine=rs6000-bull os=-bosx ;; dpx2* | dpx2*-bull) basic_machine=m68k-bull os=-sysv3 ;; ebmon29k) basic_machine=a29k-amd os=-ebmon ;; elxsi) basic_machine=elxsi-elxsi os=-bsd ;; encore | umax | mmax) basic_machine=ns32k-encore ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson os=-ose ;; fx2800) basic_machine=i860-alliant ;; genix) basic_machine=ns32k-ns ;; gmicro) basic_machine=tron-gmicro os=-sysv ;; go32) basic_machine=i386-pc os=-go32 ;; h3050r* | hiux*) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; h8300hms) basic_machine=h8300-hitachi os=-hms ;; h8300xray) basic_machine=h8300-hitachi os=-xray ;; h8500hms) basic_machine=h8500-hitachi os=-hms ;; harris) basic_machine=m88k-harris os=-sysv3 ;; hp300-*) basic_machine=m68k-hp ;; hp300bsd) basic_machine=m68k-hp os=-bsd ;; hp300hpux) basic_machine=m68k-hp os=-hpux ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) basic_machine=m68000-hp ;; hp9k3[2-9][0-9]) basic_machine=m68k-hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) basic_machine=hppa1.1-hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) basic_machine=hppa1.1-hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) basic_machine=hppa1.0-hp ;; hppa-next) os=-nextstep3 ;; hppaosf) basic_machine=hppa1.1-hp os=-osf ;; hppro) basic_machine=hppa1.1-hp os=-proelf ;; i370-ibm* | ibm*) basic_machine=i370-ibm ;; # I'm not sure what "Sysv32" means. Should this be sysv3.2? i*86v32) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv32 ;; i*86v4*) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv4 ;; i*86v) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv ;; i*86sol2) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-solaris2 ;; i386mach) basic_machine=i386-mach os=-mach ;; i386-vsta | vsta) basic_machine=i386-unknown os=-vsta ;; iris | iris4d) basic_machine=mips-sgi case $os in -irix*) ;; *) os=-irix4 ;; esac ;; isi68 | isi) basic_machine=m68k-isi os=-sysv ;; m88k-omron*) basic_machine=m88k-omron ;; magnum | m3230) basic_machine=mips-mips os=-sysv ;; merlin) basic_machine=ns32k-utek os=-sysv ;; mingw32) basic_machine=i386-pc os=-mingw32 ;; miniframe) basic_machine=m68000-convergent ;; *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) basic_machine=m68k-atari os=-mint ;; mips3*-*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` ;; mips3*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown ;; mmix*) basic_machine=mmix-knuth os=-mmixware ;; monitor) basic_machine=m68k-rom68k os=-coff ;; morphos) basic_machine=powerpc-unknown os=-morphos ;; msdos) basic_machine=i386-pc os=-msdos ;; mvs) basic_machine=i370-ibm os=-mvs ;; ncr3000) basic_machine=i486-ncr os=-sysv4 ;; netbsd386) basic_machine=i386-unknown os=-netbsd ;; netwinder) basic_machine=armv4l-rebel os=-linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony os=-newsos ;; news1000) basic_machine=m68030-sony os=-newsos ;; news-3600 | risc-news) basic_machine=mips-sony os=-newsos ;; necv70) basic_machine=v70-nec os=-sysv ;; next | m*-next ) basic_machine=m68k-next case $os in -nextstep* ) ;; -ns2*) os=-nextstep2 ;; *) os=-nextstep3 ;; esac ;; nh3000) basic_machine=m68k-harris os=-cxux ;; nh[45]000) basic_machine=m88k-harris os=-cxux ;; nindy960) basic_machine=i960-intel os=-nindy ;; mon960) basic_machine=i960-intel os=-mon960 ;; nonstopux) basic_machine=mips-compaq os=-nonstopux ;; np1) basic_machine=np1-gould ;; nv1) basic_machine=nv1-cray os=-unicosmp ;; nsr-tandem) basic_machine=nsr-tandem ;; op50n-* | op60c-*) basic_machine=hppa1.1-oki os=-proelf ;; or32 | or32-*) basic_machine=or32-unknown os=-coff ;; os400) basic_machine=powerpc-ibm os=-os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson os=-ose ;; os68k) basic_machine=m68k-none os=-os68k ;; pa-hitachi) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; paragon) basic_machine=i860-intel os=-osf ;; pbd) basic_machine=sparc-tti ;; pbb) basic_machine=m68k-tti ;; pc532 | pc532-*) basic_machine=ns32k-pc532 ;; pentium | p5 | k5 | k6 | nexgen | viac3) basic_machine=i586-pc ;; pentiumpro | p6 | 6x86 | athlon | athlon_*) basic_machine=i686-pc ;; pentiumii | pentium2 | pentiumiii | pentium3) basic_machine=i686-pc ;; pentium4) basic_machine=i786-pc ;; pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumpro-* | p6-* | 6x86-* | athlon-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentium4-*) basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pn) basic_machine=pn-gould ;; power) basic_machine=power-ibm ;; ppc) basic_machine=powerpc-unknown ;; ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppcle | powerpclittle | ppc-le | powerpc-little) basic_machine=powerpcle-unknown ;; ppcle-* | powerpclittle-*) basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64) basic_machine=powerpc64-unknown ;; ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64le | powerpc64little | ppc64-le | powerpc64-little) basic_machine=powerpc64le-unknown ;; ppc64le-* | powerpc64little-*) basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ps2) basic_machine=i386-ibm ;; pw32) basic_machine=i586-unknown os=-pw32 ;; rom68k) basic_machine=m68k-rom68k os=-coff ;; rm[46]00) basic_machine=mips-siemens ;; rtpc | rtpc-*) basic_machine=romp-ibm ;; s390 | s390-*) basic_machine=s390-ibm ;; s390x | s390x-*) basic_machine=s390x-ibm ;; sa29200) basic_machine=a29k-amd os=-udi ;; sb1) basic_machine=mipsisa64sb1-unknown ;; sb1el) basic_machine=mipsisa64sb1el-unknown ;; sei) basic_machine=mips-sei os=-seiux ;; sequent) basic_machine=i386-sequent ;; sh) basic_machine=sh-hitachi os=-hms ;; sh64) basic_machine=sh64-unknown ;; sparclite-wrs | simso-wrs) basic_machine=sparclite-wrs os=-vxworks ;; sps7) basic_machine=m68k-bull os=-sysv2 ;; spur) basic_machine=spur-unknown ;; st2000) basic_machine=m68k-tandem ;; stratus) basic_machine=i860-stratus os=-sysv4 ;; sun2) basic_machine=m68000-sun ;; sun2os3) basic_machine=m68000-sun os=-sunos3 ;; sun2os4) basic_machine=m68000-sun os=-sunos4 ;; sun3os3) basic_machine=m68k-sun os=-sunos3 ;; sun3os4) basic_machine=m68k-sun os=-sunos4 ;; sun4os3) basic_machine=sparc-sun os=-sunos3 ;; sun4os4) basic_machine=sparc-sun os=-sunos4 ;; sun4sol2) basic_machine=sparc-sun os=-solaris2 ;; sun3 | sun3-*) basic_machine=m68k-sun ;; sun4) basic_machine=sparc-sun ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun ;; sv1) basic_machine=sv1-cray os=-unicos ;; symmetry) basic_machine=i386-sequent os=-dynix ;; t3e) basic_machine=alphaev5-cray os=-unicos ;; t90) basic_machine=t90-cray os=-unicos ;; tic54x | c54x*) basic_machine=tic54x-unknown os=-coff ;; tic55x | c55x*) basic_machine=tic55x-unknown os=-coff ;; tic6x | c6x*) basic_machine=tic6x-unknown os=-coff ;; tx39) basic_machine=mipstx39-unknown ;; tx39el) basic_machine=mipstx39el-unknown ;; toad1) basic_machine=pdp10-xkl os=-tops20 ;; tower | tower-32) basic_machine=m68k-ncr ;; tpf) basic_machine=s390x-ibm os=-tpf ;; udi29k) basic_machine=a29k-amd os=-udi ;; ultra3) basic_machine=a29k-nyu os=-sym1 ;; v810 | necv810) basic_machine=v810-nec os=-none ;; vaxv) basic_machine=vax-dec os=-sysv ;; vms) basic_machine=vax-dec os=-vms ;; vpp*|vx|vx-*) basic_machine=f301-fujitsu ;; vxworks960) basic_machine=i960-wrs os=-vxworks ;; vxworks68) basic_machine=m68k-wrs os=-vxworks ;; vxworks29k) basic_machine=a29k-wrs os=-vxworks ;; w65*) basic_machine=w65-wdc os=-none ;; w89k-*) basic_machine=hppa1.1-winbond os=-proelf ;; xps | xps100) basic_machine=xps100-honeywell ;; ymp) basic_machine=ymp-cray os=-unicos ;; z8k-*-coff) basic_machine=z8k-unknown os=-sim ;; none) basic_machine=none-none os=-none ;; # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) basic_machine=hppa1.1-winbond ;; op50n) basic_machine=hppa1.1-oki ;; op60c) basic_machine=hppa1.1-oki ;; romp) basic_machine=romp-ibm ;; rs6000) basic_machine=rs6000-ibm ;; vax) basic_machine=vax-dec ;; pdp10) # there are many clones, so DEC is not a safe bet basic_machine=pdp10-unknown ;; pdp11) basic_machine=pdp11-dec ;; we32k) basic_machine=we32k-att ;; sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele) basic_machine=sh-unknown ;; sh64) basic_machine=sh64-unknown ;; sparc | sparcv9 | sparcv9b) basic_machine=sparc-sun ;; cydra) basic_machine=cydra-cydrome ;; orion) basic_machine=orion-highlevel ;; orion105) basic_machine=clipper-highlevel ;; mac | mpw | mac-mpw) basic_machine=m68k-apple ;; pmac | pmac-mpw) basic_machine=powerpc-apple ;; *-unknown) # Make sure to match an already-canonicalized machine name. ;; *) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; esac # Here we canonicalize certain aliases for manufacturers. case $basic_machine in *-digital*) basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` ;; *-commodore*) basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if [ x"$os" != x"" ] then case $os in # First match some system type aliases # that might get confused with valid system types. # -solaris* is a basic system type, with this one exception. -solaris1 | -solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; -solaris) os=-solaris2 ;; -svr4*) os=-sysv4 ;; -unixware*) os=-sysv4.2uw ;; -gnu/linux*) os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` ;; # First accept the basic system types. # The portable systems comes first. # Each alternative MUST END IN A *, to match a version number. # -sysv* is not here because it comes later, after sysvr4. -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ | -aos* \ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \ | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ | -chorusos* | -chorusrdb* \ | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly*) # Remember, each alternative MUST END IN *, to match a version number. ;; -qnx*) case $basic_machine in x86-* | i*86-*) ;; *) os=-nto$os ;; esac ;; -nto-qnx*) ;; -nto*) os=`echo $os | sed -e 's|nto|nto-qnx|'` ;; -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) ;; -mac*) os=`echo $os | sed -e 's|mac|macos|'` ;; -linux-dietlibc) os=-linux-dietlibc ;; -linux*) os=`echo $os | sed -e 's|linux|linux-gnu|'` ;; -sunos5*) os=`echo $os | sed -e 's|sunos5|solaris2|'` ;; -sunos6*) os=`echo $os | sed -e 's|sunos6|solaris3|'` ;; -opened*) os=-openedition ;; -os400*) os=-os400 ;; -wince*) os=-wince ;; -osfrose*) os=-osfrose ;; -osf*) os=-osf ;; -utek*) os=-bsd ;; -dynix*) os=-bsd ;; -acis*) os=-aos ;; -atheos*) os=-atheos ;; -syllable*) os=-syllable ;; -386bsd) os=-bsd ;; -ctix* | -uts*) os=-sysv ;; -nova*) os=-rtmk-nova ;; -ns2 ) os=-nextstep2 ;; -nsk*) os=-nsk ;; # Preserve the version number of sinix5. -sinix5.*) os=`echo $os | sed -e 's|sinix|sysv|'` ;; -sinix*) os=-sysv4 ;; -tpf*) os=-tpf ;; -triton*) os=-sysv3 ;; -oss*) os=-sysv3 ;; -svr4) os=-sysv4 ;; -svr3) os=-sysv3 ;; -sysvr4) os=-sysv4 ;; # This must come after -sysvr4. -sysv*) ;; -ose*) os=-ose ;; -es1800*) os=-ose ;; -xenix) os=-xenix ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) os=-mint ;; -aros*) os=-aros ;; -kaos*) os=-kaos ;; -none) ;; *) # Get rid of the `-' at the beginning of $os. os=`echo $os | sed 's/[^-]*-//'` echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 exit 1 ;; esac else # Here we handle the default operating systems that come with various machines. # The value should be what the vendor currently ships out the door with their # machine or put another way, the most popular os provided with the machine. # Note that if you're going to try to match "-MANUFACTURER" here (say, # "-sun"), then you have to tell the case statement up towards the top # that MANUFACTURER isn't an operating system. Otherwise, code above # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. case $basic_machine in *-acorn) os=-riscix1.2 ;; arm*-rebel) os=-linux ;; arm*-semi) os=-aout ;; c4x-* | tic4x-*) os=-coff ;; # This must come before the *-dec entry. pdp10-*) os=-tops20 ;; pdp11-*) os=-none ;; *-dec | vax-*) os=-ultrix4.2 ;; m68*-apollo) os=-domain ;; i386-sun) os=-sunos4.0.2 ;; m68000-sun) os=-sunos3 # This also exists in the configure program, but was not the # default. # os=-sunos4 ;; m68*-cisco) os=-aout ;; mips*-cisco) os=-elf ;; mips*-*) os=-elf ;; or32-*) os=-coff ;; *-tti) # must be before sparc entry or we get the wrong os. os=-sysv3 ;; sparc-* | *-sun) os=-sunos4.1.1 ;; *-be) os=-beos ;; *-ibm) os=-aix ;; *-wec) os=-proelf ;; *-winbond) os=-proelf ;; *-oki) os=-proelf ;; *-hp) os=-hpux ;; *-hitachi) os=-hiux ;; i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) os=-sysv ;; *-cbm) os=-amigaos ;; *-dg) os=-dgux ;; *-dolphin) os=-sysv3 ;; m68k-ccur) os=-rtu ;; m88k-omron*) os=-luna ;; *-next ) os=-nextstep ;; *-sequent) os=-ptx ;; *-crds) os=-unos ;; *-ns) os=-genix ;; i370-*) os=-mvs ;; *-next) os=-nextstep3 ;; *-gould) os=-sysv ;; *-highlevel) os=-bsd ;; *-encore) os=-bsd ;; *-sgi) os=-irix ;; *-siemens) os=-sysv4 ;; *-masscomp) os=-rtu ;; f30[01]-fujitsu | f700-fujitsu) os=-uxpv ;; *-rom68k) os=-coff ;; *-*bug) os=-coff ;; *-apple) os=-macos ;; *-atari*) os=-mint ;; *) os=-none ;; esac fi # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. vendor=unknown case $basic_machine in *-unknown) case $os in -riscix*) vendor=acorn ;; -sunos*) vendor=sun ;; -aix*) vendor=ibm ;; -beos*) vendor=be ;; -hpux*) vendor=hp ;; -mpeix*) vendor=hp ;; -hiux*) vendor=hitachi ;; -unos*) vendor=crds ;; -dgux*) vendor=dg ;; -luna*) vendor=omron ;; -genix*) vendor=ns ;; -mvs* | -opened*) vendor=ibm ;; -os400*) vendor=ibm ;; -ptx*) vendor=sequent ;; -tpf*) vendor=ibm ;; -vxsim* | -vxworks* | -windiss*) vendor=wrs ;; -aux*) vendor=apple ;; -hms*) vendor=hitachi ;; -mpw* | -macos*) vendor=apple ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) vendor=atari ;; -vos*) vendor=stratus ;; esac basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` ;; esac echo $basic_machine$os exit 0 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: boxbackup/LICENSE-GPL.txt0000664000175000017500000000373711345265741015577 0ustar siretartsiretartBox 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. boxbackup/modules.txt0000664000175000017500000000335711170703462015534 0ustar siretartsiretart # first entry is module name, next entries are dependencies or -l library includes # put !, after a module / library to exclude it from a particular platform # put !+,... to include it only on those platforms # -l libaries must be in the order they should appear on the command line. # Note that order is important on platforms which do not have shared libraries. # Generic support code and modules lib/raidfile lib/crypto lib/server lib/compress lib/intercept test/common test/crypto lib/crypto test/compress lib/compress test/raidfile lib/raidfile lib/intercept test/basicserver lib/server # IF_DISTRIBUTION(boxbackup) # Backup system lib/backupclient lib/server lib/crypto lib/compress lib/backupstore lib/server lib/raidfile lib/backupclient bin/bbackupobjdump lib/backupclient lib/backupstore bin/bbstored lib/raidfile lib/server lib/backupstore lib/backupclient bin/bbstoreaccounts lib/raidfile lib/backupstore bin/bbackupd lib/server lib/backupclient bin/bbackupquery lib/server lib/backupclient bin/bbackupctl lib/server lib/backupclient test/backupstore bin/bbstored bin/bbstoreaccounts lib/server lib/backupstore lib/backupclient lib/raidfile test/backupstorefix bin/bbstored bin/bbstoreaccounts lib/backupstore lib/raidfile bin/bbackupquery bin/bbackupd bin/bbackupctl test/backupstorepatch bin/bbstored bin/bbstoreaccounts lib/backupstore lib/raidfile test/backupdiff lib/backupclient test/bbackupd bin/bbackupd bin/bbstored bin/bbstoreaccounts bin/bbackupquery bin/bbackupctl lib/server lib/backupstore lib/backupclient lib/intercept # HTTP server system lib/httpserver lib/server test/httpserver lib/httpserver bin/s3simulator lib/httpserver # END_IF_DISTRIBUTION boxbackup/VERSION.txt0000664000175000017500000000003210377414467015210 0ustar siretartsiretartUSE_SVN_VERSION boxbackup boxbackup/bin/0000775000175000017500000000000011652362374014073 5ustar siretartsiretartboxbackup/bin/bbackupctl/0000775000175000017500000000000011652362374016205 5ustar siretartsiretartboxbackup/bin/bbackupctl/bbackupctl.cpp0000664000175000017500000001672211165365160021026 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: bbackupctl.cpp // Purpose: bbackupd daemon control program // Created: 18/2/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include "MainHelper.h" #include "BoxPortsAndFiles.h" #include "BackupDaemonConfigVerify.h" #include "Socket.h" #include "SocketStream.h" #include "IOStreamGetLine.h" #ifdef WIN32 #include "WinNamedPipeStream.h" #endif #include "MemLeakFindOn.h" enum Command { Default, WaitForSyncStart, WaitForSyncEnd, SyncAndWaitForEnd, }; void PrintUsageAndExit() { printf("Usage: bbackupctl [-q] [-c config_file] \n" "Commands are:\n" " sync -- start a synchronisation (backup) run now\n" " force-sync -- force the start of a synchronisation run, " "even if SyncAllowScript says no\n" " reload -- reload daemon configuration\n" " terminate -- terminate daemon now\n" " wait-for-sync -- wait until the next sync starts, then exit\n" " wait-for-end -- wait until the next sync finishes, then exit\n" " sync-and-wait -- start sync, wait until it finishes, then exit\n" ); exit(1); } int main(int argc, const char *argv[]) { int returnCode = 0; MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupctl.memleaks", "bbackupctl") MAINHELPER_START Logging::SetProgramName("bbackupctl"); // Filename for configuration file? std::string configFilename; #ifdef WIN32 configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; #else configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG; #endif // Quiet? bool quiet = false; // See if there's another entry on the command line int c; while((c = getopt(argc, (char * const *)argv, "qc:l:")) != -1) { switch(c) { case 'q': // Quiet mode quiet = true; break; case 'c': // store argument configFilename = optarg; break; case '?': default: PrintUsageAndExit(); } } // Adjust arguments argc -= optind; argv += optind; // Check there's a command if(argc != 1) { PrintUsageAndExit(); } // Read in the configuration file if(!quiet) BOX_NOTICE("Using configuration file " << configFilename); std::string errs; std::auto_ptr config( Configuration::LoadAndVerify (configFilename, &BackupDaemonConfigVerify, errs)); if(config.get() == 0 || !errs.empty()) { BOX_ERROR("Invalid configuration file: " << errs); return 1; } // Easier coding const Configuration &conf(*config); // Check there's a socket defined in the config file if(!conf.KeyExists("CommandSocket")) { BOX_ERROR("Daemon isn't using a control socket, " "could not execute command.\n" "Add a CommandSocket declaration to the " "bbackupd.conf file."); return 1; } // Connect to socket #ifndef WIN32 SocketStream connection; #else /* WIN32 */ WinNamedPipeStream connection; #endif /* ! WIN32 */ try { #ifdef WIN32 std::string socket = conf.GetKeyValue("CommandSocket"); connection.Connect(socket); #else connection.Open(Socket::TypeUNIX, conf.GetKeyValue("CommandSocket").c_str()); #endif } catch(...) { BOX_ERROR("Failed to connect to daemon control socket.\n" "Possible causes:\n" " * Daemon not running\n" " * Daemon busy syncing with store server\n" " * Another bbackupctl process is communicating with the daemon\n" " * Daemon is waiting to recover from an error" ); return 1; } // For receiving data IOStreamGetLine getLine(connection); // Wait for the configuration summary std::string configSummary; if(!getLine.GetLine(configSummary)) { BOX_ERROR("Failed to receive configuration summary " "from daemon"); return 1; } // Was the connection rejected by the server? if(getLine.IsEOF()) { BOX_ERROR("Server rejected the connection. Are you running " "bbackupctl as the same user as the daemon?"); return 1; } // Decode it int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", &autoBackup, &updateStoreInterval, &minimumFileAge, &maxUploadWait) != 4) { BOX_ERROR("Config summary didn't decode."); return 1; } // Print summary? if(!quiet) { BOX_INFO("Daemon configuration summary:\n" " AutomaticBackup = " << (autoBackup?"true":"false") << "\n" " UpdateStoreInterval = " << updateStoreInterval << " seconds\n" " MinimumFileAge = " << minimumFileAge << " seconds\n" " MaxUploadWait = " << maxUploadWait << " seconds"); } std::string stateLine; if(!getLine.GetLine(stateLine) || getLine.IsEOF()) { BOX_ERROR("Failed to receive state line from daemon"); return 1; } // Decode it int currentState; if(::sscanf(stateLine.c_str(), "state %d", ¤tState) != 1) { BOX_ERROR("Received invalid state line from daemon"); return 1; } Command command = Default; std::string commandName(argv[0]); if (commandName == "wait-for-sync") { command = WaitForSyncStart; } else if (commandName == "wait-for-end") { command = WaitForSyncEnd; } else if (commandName == "sync-and-wait") { command = SyncAndWaitForEnd; } switch (command) { case WaitForSyncStart: case WaitForSyncEnd: { // Check that it's in automatic mode, // because otherwise it'll never start if(!autoBackup) { BOX_ERROR("Daemon is not in automatic mode, " "sync will never start!"); return 1; } } break; case SyncAndWaitForEnd: { // send a sync command commandName = "force-sync"; std::string cmd = commandName + "\n"; connection.Write(cmd.c_str(), cmd.size()); connection.WriteAllBuffered(); if (currentState != 0) { BOX_INFO("Waiting for current sync/error state " "to finish..."); } } break; default: { // Normal case, just send the command given // plus a quit command. std::string cmd = commandName; cmd += "\nquit\n"; connection.Write(cmd.c_str(), cmd.size()); } } // Read the response std::string line; bool syncIsRunning = false; bool finished = false; while(!finished && !getLine.IsEOF() && getLine.GetLine(line)) { switch (command) { case WaitForSyncStart: { // Need to wait for the state change... if(line == "start-sync") { // Send a quit command to finish nicely connection.Write("quit\n", 5); // And we're done finished = true; } } break; case WaitForSyncEnd: case SyncAndWaitForEnd: { if(line == "start-sync") { if (!quiet) BOX_INFO("Sync started..."); syncIsRunning = true; } else if(line == "finish-sync") { if (syncIsRunning) { if (!quiet) BOX_INFO("Sync finished."); // Send a quit command to finish nicely connection.Write("quit\n", 5); // And we're done finished = true; } else { if (!quiet) BOX_INFO("Previous sync finished."); } // daemon must still be busy } } break; default: { // Is this an OK or error line? if(line == "ok") { if(!quiet) { BOX_INFO("Control command " "sent: " << commandName); } finished = true; } else if(line == "error") { BOX_ERROR("Control command failed: " << commandName << ". Check " "command spelling."); returnCode = 1; finished = true; } } } } MAINHELPER_END #if defined WIN32 && ! defined BOX_RELEASE_BUILD closelog(); #endif return returnCode; } boxbackup/bin/bbackupquery/0000775000175000017500000000000011652362374016570 5ustar siretartsiretartboxbackup/bin/bbackupquery/makedocumentation.pl.in0000775000175000017500000000165510745727417023260 0ustar siretartsiretart#!@PERL@ use strict; print "Creating built-in documentation for bbackupquery...\n"; open DOC,"documentation.txt" or die "Can't open documentation.txt file"; my $section; my %help; my @in_order; while() { if(m/\A>\s+(\w+)/) { $section = $1; m/\A>\s+(.+)\Z/; $help{$section} = $1."\n"; push @in_order,$section; } elsif(m/\Aautogen_Documentation.cpp" or die "Can't open output file for writing"; print OUT <<__E; // // Automatically generated file, do not edit. // #include "Box.h" #include "MemLeakFindOn.h" const char *help_commands[] = { __E for(@in_order) { print OUT qq:\t"$_",\n:; } print OUT <<__E; 0 }; const char *help_text[] = { __E for(@in_order) { my $t = $help{$_}; $t =~ s/\t/ /g; $t =~ s/\n/\\n/g; $t =~ s/"/\\"/g; print OUT qq:\t"$t",\n:; } print OUT <<__E; 0 }; __E close OUT; boxbackup/bin/bbackupquery/BackupQueries.h0000664000175000017500000002234411127732543021505 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupQueries.h // Purpose: Perform various queries on the backup store server. // Created: 2003/10/10 // // -------------------------------------------------------------------------- #ifndef BACKUPQUERIES__H #define BACKUPQUERIES__H #include #include #include "BoxTime.h" #include "BoxBackupCompareParams.h" class BackupProtocolClient; class Configuration; class ExcludeList; // -------------------------------------------------------------------------- // // Class // Name: BackupQueries // Purpose: Perform various queries on the backup store server. // Created: 2003/10/10 // // -------------------------------------------------------------------------- class BackupQueries { public: BackupQueries(BackupProtocolClient &rConnection, const Configuration &rConfiguration, bool readWrite); ~BackupQueries(); private: BackupQueries(const BackupQueries &); public: void DoCommand(const char *Command, bool isFromCommandLine); // Ready to stop? bool Stop() {return mQuitNow;} // Return code? int GetReturnCode() {return mReturnCode;} private: // Commands void CommandList(const std::vector &args, const bool *opts); void CommandChangeDir(const std::vector &args, const bool *opts); void CommandChangeLocalDir(const std::vector &args); void CommandGetObject(const std::vector &args, const bool *opts); void CommandGet(std::vector args, const bool *opts); void CommandCompare(const std::vector &args, const bool *opts); void CommandRestore(const std::vector &args, const bool *opts); void CommandUndelete(const std::vector &args, const bool *opts); void CommandDelete(const std::vector &args, const bool *opts); void CommandUsage(const bool *opts); void CommandUsageDisplayEntry(const char *Name, int64_t Size, int64_t HardLimit, int32_t BlockSize, bool MachineReadable); void CommandHelp(const std::vector &args); // Implementations void List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel); public: class CompareParams : public BoxBackupCompareParams { public: CompareParams(bool QuickCompare, bool IgnoreExcludes, bool IgnoreAttributes, box_time_t LatestFileUploadTime); bool mQuietCompare; int mDifferences; int mDifferencesExplainedByModTime; int mUncheckedFiles; int mExcludedDirs; int mExcludedFiles; std::string ConvertForConsole(const std::string& rUtf8String) { #ifdef WIN32 std::string output; if(!ConvertUtf8ToConsole(rUtf8String.c_str(), output)) { BOX_WARNING("Character set conversion failed " "on string: " << rUtf8String); return rUtf8String; } return output; #else return rUtf8String; #endif } virtual void NotifyLocalDirMissing(const std::string& rLocalPath, const std::string& rRemotePath) { BOX_WARNING("Local directory '" << ConvertForConsole(rLocalPath) << "' " "does not exist, but remote directory does."); mDifferences ++; } virtual void NotifyLocalDirAccessFailed( const std::string& rLocalPath, const std::string& rRemotePath) { BOX_LOG_SYS_WARNING("Failed to access local directory " "'" << ConvertForConsole(rLocalPath) << "'"); mUncheckedFiles ++; } virtual void NotifyStoreDirMissingAttributes( const std::string& rLocalPath, const std::string& rRemotePath) { BOX_WARNING("Store directory '" << ConvertForConsole(rRemotePath) << "' " "doesn't have attributes."); } virtual void NotifyRemoteFileMissing( const std::string& rLocalPath, const std::string& rRemotePath, bool modifiedAfterLastSync) { BOX_WARNING("Local file '" << ConvertForConsole(rLocalPath) << "' " "exists, but remote file '" << ConvertForConsole(rRemotePath) << "' " "does not."); mDifferences ++; if(modifiedAfterLastSync) { mDifferencesExplainedByModTime ++; BOX_INFO("(the file above was modified after " "the last sync time -- might be " "reason for difference)"); } } virtual void NotifyLocalFileMissing( const std::string& rLocalPath, const std::string& rRemotePath) { BOX_WARNING("Remote file '" << ConvertForConsole(rRemotePath) << "' " "exists, but local file '" << ConvertForConsole(rLocalPath) << "' does not."); mDifferences ++; } virtual void NotifyExcludedFileNotDeleted( const std::string& rLocalPath, const std::string& rRemotePath) { BOX_WARNING("Local file '" << ConvertForConsole(rLocalPath) << "' " "is excluded, but remote file '" << ConvertForConsole(rRemotePath) << "' " "still exists."); mDifferences ++; } virtual void NotifyDownloadFailed(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes, BoxException& rException) { BOX_ERROR("Failed to download remote file '" << ConvertForConsole(rRemotePath) << "': " << rException.what() << " (" << rException.GetType() << "/" << rException.GetSubType() << ")"); mUncheckedFiles ++; } virtual void NotifyDownloadFailed(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes, std::exception& rException) { BOX_ERROR("Failed to download remote file '" << ConvertForConsole(rRemotePath) << "': " << rException.what()); mUncheckedFiles ++; } virtual void NotifyDownloadFailed(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes) { BOX_ERROR("Failed to download remote file '" << ConvertForConsole(rRemotePath)); mUncheckedFiles ++; } virtual void NotifyExcludedFile(const std::string& rLocalPath, const std::string& rRemotePath) { mExcludedFiles ++; } virtual void NotifyExcludedDir(const std::string& rLocalPath, const std::string& rRemotePath) { mExcludedDirs ++; } virtual void NotifyDirComparing(const std::string& rLocalPath, const std::string& rRemotePath) { } virtual void NotifyDirCompared( const std::string& rLocalPath, const std::string& rRemotePath, bool HasDifferentAttributes, bool modifiedAfterLastSync) { if(HasDifferentAttributes) { BOX_WARNING("Local directory '" << ConvertForConsole(rLocalPath) << "' " "has different attributes to " "store directory '" << ConvertForConsole(rRemotePath) << "'."); mDifferences ++; if(modifiedAfterLastSync) { mDifferencesExplainedByModTime ++; BOX_INFO("(the directory above was " "modified after the last sync " "time -- might be reason for " "difference)"); } } } virtual void NotifyFileComparing(const std::string& rLocalPath, const std::string& rRemotePath) { } virtual void NotifyFileCompared(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes, bool HasDifferentAttributes, bool HasDifferentContents, bool ModifiedAfterLastSync, bool NewAttributesApplied) { int NewDifferences = 0; if(HasDifferentAttributes) { BOX_WARNING("Local file '" << ConvertForConsole(rLocalPath) << "' " "has different attributes to " "store file '" << ConvertForConsole(rRemotePath) << "'."); NewDifferences ++; } if(HasDifferentContents) { BOX_WARNING("Local file '" << ConvertForConsole(rLocalPath) << "' " "has different contents to " "store file '" << ConvertForConsole(rRemotePath) << "'."); NewDifferences ++; } if(HasDifferentAttributes || HasDifferentContents) { if(ModifiedAfterLastSync) { mDifferencesExplainedByModTime += NewDifferences; BOX_INFO("(the file above was modified " "after the last sync time -- " "might be reason for difference)"); } else if(NewAttributesApplied) { BOX_INFO("(the file above has had new " "attributes applied)\n"); } } mDifferences += NewDifferences; } }; void CompareLocation(const std::string &rLocation, BoxBackupCompareParams &rParams); void Compare(const std::string &rStoreDir, const std::string &rLocalDir, BoxBackupCompareParams &rParams); void Compare(int64_t DirID, const std::string &rStoreDir, const std::string &rLocalDir, BoxBackupCompareParams &rParams); public: class ReturnCode { public: enum { Command_OK = 0, Compare_Same = 1, Compare_Different, Compare_Error, Command_Error, } Type; }; private: // Utility functions int64_t FindDirectoryObjectID(const std::string &rDirName, bool AllowOldVersion = false, bool AllowDeletedDirs = false, std::vector > *pStack = 0); int64_t FindFileID(const std::string& rNameOrIdString, const bool *opts, int64_t *pDirIdOut, std::string* pFileNameOut, int16_t flagsInclude, int16_t flagsExclude, int16_t* pFlagsOut); int64_t GetCurrentDirectoryID(); std::string GetCurrentDirectoryName(); void SetReturnCode(int code) {mReturnCode = code;} private: bool mReadWrite; BackupProtocolClient &mrConnection; const Configuration &mrConfiguration; bool mQuitNow; std::vector > mDirStack; bool mRunningAsRoot; bool mWarnedAboutOwnerAttributes; int mReturnCode; }; #endif // BACKUPQUERIES__H boxbackup/bin/bbackupquery/documentation.txt0000664000175000017500000001241111345271706022176 0ustar siretartsiretart bbackupquery utility -- examine store, compare files, restore, etc. This file has markers for automatic help generation script -- '>' marks a start of a command/help topic, and '<' marks the end of a section. Command line: ============= > bbackupquery [-q] [-c configfile] [commands ...] -q -- quiet, no information prompts -c -- specify another bbackupd configuation file The commands following the options are executed, then (if there was no quit command) an interactive mode is entered. If a command contains a space, enclose it in quotes. Example bbackupquery "list testdir1" quit to list the contents of testdir1, and then exit without interactive mode. < Commands: ========= All directory names relative to a "current" directory, or from root if they start with '/'. The initial directory is always the root directory. > list [options] [directory-name] List contents of current directory, or specified directory. -r -- recursively list all files -d -- list deleted files/directories -o -- list old versions of files/directories -I -- don't display object ID -F -- don't display flags -t -- show file modification time in local time (and attr mod time if has the object has attributes, ~ separated) -T -- show file modification time in GMT -a -- show updated attribute instead of file modification time -s -- show file size in blocks used on server (only very approximate indication of size locally) -h -- show file attributes hash ls can be used as an alias. < > ls Alias for 'list'. Type 'help list' for options. < > cd [options] Change directory -d -- consider deleted directories for traversal -o -- consider old versions of directories for traversal (this option should never be useful in a correctly formed store) < > pwd Print current directory, always root relative. < > lcd Change local directory. Type "sh ls" to list the contents. < > sh All of the parameters after the "sh" are run as a shell command. For example, to list the contents of the location directory, type "sh ls" < > get [] get -i Gets a file from the store. Object is specified as the filename within the current directory, and local filename is optional. Ignores old and deleted files when searching the directory for the file to retrieve. To get an old or deleted file, use the -i option and select the object as a hex object ID (first column in listing). The local filename must be specified. < > compare -a compare -l compare Compares the (current) data on the store with the data on the disc. All the data will be downloaded -- this is potentially a very long operation. -a -- compare all locations -l -- compare one backup location as specified in the configuration file. -c -- set return code -q -- quick compare. Only checks file contents against checksums, doesn't do a full download -A -- ignore attribute differences -E -- ignore exclusion settings Comparing with the root directory is an error, use -a option instead. If -c is set, then the return code (if quit is the next command) will be 1 Comparison was exact 2 Differences were found 3 An error occured This can be used for automated tests. < > restore [-drif] Restores a directory to the local disc. The local directory specified must not exist (unless a previous restore is being restarted). The root cannot be restored -- restore locations individually. -d -- restore a deleted directory or deleted files inside -r -- resume an interrupted restoration -i -- directory name is actually an ID -f -- force restore to continue if errors are encountered If a restore operation is interrupted for any reason, it can be restarted using the -r switch. Restore progress information is saved in a file at regular intervals during the restore operation to allow restarts. < > getobject Gets the object specified by the object id (in hex) and stores the raw contents in the local file specified. This is only useful for debugging as it does not decode files from the stored format, which is encrypted and compressed. < > usage [-m] Show space used on the server for this account. -m -- display the output in machine-readable form Used: Total amount of space used on the server. Old files: Space used by old files Deleted files: Space used by deleted files Directories: Space used by the directory structure. When Used exceeds the soft limit, the server will start to remove old and deleted files until the usage drops below the soft limit. After a while, you would expect to see the usage stay at just below the soft limit. You only need more space if the space used by old and deleted files is near zero. < > undelete undelete -i Removes the deleted flag from the specified directory name (in the current directory) or hex object ID. Be careful not to use this command where a directory already exists with the same name which is not marked as deleted. < > delete Sets the deleted flag on the specified file name (in the current directory, or with a relative path). < > quit End session and exit. < boxbackup/bin/bbackupquery/BackupQueries.cpp0000664000175000017500000016442011457627300022042 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupQueries.cpp // Purpose: Perform various queries on the backup store server. // Created: 2003/10/10 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #ifdef HAVE_DIRENT_H #include #endif #include #include #include #include #include #include "BackupClientFileAttributes.h" #include "BackupClientMakeExcludeList.h" #include "BackupClientRestore.h" #include "BackupQueries.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" #include "BackupStoreFilenameClear.h" #include "BoxTimeToText.h" #include "CommonException.h" #include "Configuration.h" #include "ExcludeList.h" #include "FileModificationTime.h" #include "FileStream.h" #include "IOStream.h" #include "Logging.h" #include "PathUtils.h" #include "SelfFlushingStream.h" #include "TemporaryDirectory.h" #include "Utils.h" #include "autogen_BackupProtocolClient.h" #include "MemLeakFindOn.h" // min() and max() macros from stdlib.h break numeric_limits<>::min(), etc. #undef min #undef max #define COMPARE_RETURN_SAME 1 #define COMPARE_RETURN_DIFFERENT 2 #define COMPARE_RETURN_ERROR 3 #define COMMAND_RETURN_ERROR 4 // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::BackupQueries() // Purpose: Constructor // Created: 2003/10/10 // // -------------------------------------------------------------------------- BackupQueries::BackupQueries(BackupProtocolClient &rConnection, const Configuration &rConfiguration, bool readWrite) : mReadWrite(readWrite), mrConnection(rConnection), mrConfiguration(rConfiguration), mQuitNow(false), mRunningAsRoot(false), mWarnedAboutOwnerAttributes(false), mReturnCode(0) // default return code { #ifdef WIN32 mRunningAsRoot = TRUE; #else mRunningAsRoot = (::geteuid() == 0); #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::~BackupQueries() // Purpose: Destructor // Created: 2003/10/10 // // -------------------------------------------------------------------------- BackupQueries::~BackupQueries() { } typedef struct { const char* name; const char* opts; } QueryCommandSpecification; // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::DoCommand(const char *, bool) // Purpose: Perform a command // Created: 2003/10/10 // // -------------------------------------------------------------------------- void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) { // is the command a shell command? if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0') { // Yes, run shell command int result = ::system(Command + 3); if(result != 0) { BOX_WARNING("System command returned error code " << result); SetReturnCode(ReturnCode::Command_Error); } return; } // split command into components std::vector cmdElements; std::string options; { const char *c = Command; bool inQuoted = false; bool inOptions = false; std::string s; while(*c != 0) { // Terminating char? if(*c == ((inQuoted)?'"':' ')) { if(!s.empty()) cmdElements.push_back(s); s.resize(0); inQuoted = false; inOptions = false; } else { // No. Start of quoted parameter? if(s.empty() && *c == '"') { inQuoted = true; } // Start of options? else if(s.empty() && *c == '-') { inOptions = true; } else { if(inOptions) { // Option char options += *c; } else { // Normal string char s += *c; } } } ++c; } if(!s.empty()) cmdElements.push_back(s); } #ifdef WIN32 if (isFromCommandLine) { for (std::vector::iterator i = cmdElements.begin(); i != cmdElements.end(); i++) { std::string converted; if (!ConvertEncoding(*i, CP_ACP, converted, GetConsoleCP())) { BOX_ERROR("Failed to convert encoding"); return; } *i = converted; } } #endif // Check... if(cmdElements.size() < 1) { // blank command return; } // Data about commands static QueryCommandSpecification commands[] = { { "quit", "" }, { "exit", "" }, { "list", "rodIFtTash", }, { "pwd", "" }, { "cd", "od" }, { "lcd", "" }, { "sh", "" }, { "getobject", "" }, { "get", "i" }, { "compare", "alcqAEQ" }, { "restore", "drif" }, { "help", "" }, { "usage", "m" }, { "undelete", "i" }, { "delete", "i" }, { NULL, NULL } }; typedef enum { Command_Quit = 0, Command_Exit, Command_List, Command_pwd, Command_cd, Command_lcd, Command_sh, Command_GetObject, Command_Get, Command_Compare, Command_Restore, Command_Help, Command_Usage, Command_Undelete, Command_Delete, } CommandType; static const char *alias[] = {"ls", 0}; static const int aliasIs[] = {Command_List, 0}; // Work out which command it is... int cmd = 0; while(commands[cmd].name != 0 && ::strcmp(cmdElements[0].c_str(), commands[cmd].name) != 0) { cmd++; } if(commands[cmd].name == 0) { // Check for aliases int a; for(a = 0; alias[a] != 0; ++a) { if(::strcmp(cmdElements[0].c_str(), alias[a]) == 0) { // Found an alias cmd = aliasIs[a]; break; } } // No such command if(alias[a] == 0) { BOX_ERROR("Unrecognised command: " << Command); return; } } // Arguments std::vector args(cmdElements.begin() + 1, cmdElements.end()); // Set up options bool opts[256]; for(int o = 0; o < 256; ++o) opts[o] = false; // BLOCK { // options const char *c = options.c_str(); while(*c != 0) { // Valid option? if(::strchr(commands[cmd].opts, *c) == NULL) { BOX_ERROR("Invalid option '" << *c << "' for " "command " << commands[cmd].name); return; } opts[(int)*c] = true; ++c; } } if(cmd != Command_Quit && cmd != Command_Exit) { // If not a quit command, set the return code to zero SetReturnCode(ReturnCode::Command_OK); } // Handle command switch(cmd) { case Command_Quit: case Command_Exit: mQuitNow = true; break; case Command_List: CommandList(args, opts); break; case Command_pwd: { // Simple implementation, so do it here BOX_NOTICE(GetCurrentDirectoryName() << " (" << BOX_FORMAT_OBJECTID(GetCurrentDirectoryID()) << ")"); } break; case Command_cd: CommandChangeDir(args, opts); break; case Command_lcd: CommandChangeLocalDir(args); break; case Command_sh: BOX_ERROR("The command to run must be specified as an argument."); break; case Command_GetObject: CommandGetObject(args, opts); break; case Command_Get: CommandGet(args, opts); break; case Command_Compare: CommandCompare(args, opts); break; case Command_Restore: CommandRestore(args, opts); break; case Command_Usage: CommandUsage(opts); break; case Command_Help: CommandHelp(args); break; case Command_Undelete: CommandUndelete(args, opts); break; case Command_Delete: CommandDelete(args, opts); break; default: BOX_ERROR("Unknown command: " << Command); break; } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandList(const std::vector &, const bool *) // Purpose: List directories (optionally recursive) // Created: 2003/10/10 // // -------------------------------------------------------------------------- void BackupQueries::CommandList(const std::vector &args, const bool *opts) { #define LIST_OPTION_RECURSIVE 'r' #define LIST_OPTION_ALLOWOLD 'o' #define LIST_OPTION_ALLOWDELETED 'd' #define LIST_OPTION_NOOBJECTID 'I' #define LIST_OPTION_NOFLAGS 'F' #define LIST_OPTION_TIMES_LOCAL 't' #define LIST_OPTION_TIMES_UTC 'T' #define LIST_OPTION_TIMES_ATTRIBS 'a' #define LIST_OPTION_SIZEINBLOCKS 's' #define LIST_OPTION_DISPLAY_HASH 'h' // default to using the current directory int64_t rootDir = GetCurrentDirectoryID(); // name of base directory std::string listRoot; // blank // Got a directory in the arguments? if(args.size() > 0) { #ifdef WIN32 std::string storeDirEncoded; if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; #else const std::string& storeDirEncoded(args[0]); #endif // Attempt to find the directory rootDir = FindDirectoryObjectID(storeDirEncoded, opts[LIST_OPTION_ALLOWOLD], opts[LIST_OPTION_ALLOWDELETED]); if(rootDir == 0) { BOX_ERROR("Directory '" << args[0] << "' not found " "on store."); SetReturnCode(ReturnCode::Command_Error); return; } } // List it List(rootDir, listRoot, opts, true /* first level to list */); } static std::string GetTimeString(BackupStoreDirectory::Entry& en, bool useLocalTime, bool showAttrModificationTimes) { std::ostringstream out; box_time_t originalTime, newAttributesTime; // there is no attribute modification time in the directory // entry, unfortunately, so we can't display it. originalTime = en.GetModificationTime(); out << BoxTimeToISO8601String(originalTime, useLocalTime); if(en.HasAttributes()) { const StreamableMemBlock &storeAttr(en.GetAttributes()); BackupClientFileAttributes attr(storeAttr); box_time_t NewModificationTime, NewAttrModificationTime; attr.GetModificationTimes(&NewModificationTime, &NewAttrModificationTime); if (showAttrModificationTimes) { newAttributesTime = NewAttrModificationTime; } else { newAttributesTime = NewModificationTime; } if (newAttributesTime == originalTime) { out << "*"; } else { out << "~" << BoxTimeToISO8601String(newAttributesTime, useLocalTime); } } else { out << " "; } return out.str(); } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::List(int64_t, const std::string &, const bool *, bool) // Purpose: Do the actual listing of directories and files // Created: 2003/10/10 // // -------------------------------------------------------------------------- void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel) { // Generate exclude flags int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING; if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion; if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted; // Do communication try { mrConnection.QueryListDirectory( DirID, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // both files and directories excludeFlags, true /* want attributes */); } catch (std::exception &e) { BOX_ERROR("Failed to list directory: " << e.what()); SetReturnCode(ReturnCode::Command_Error); return; } catch (...) { BOX_ERROR("Failed to list directory: unknown error"); SetReturnCode(ReturnCode::Command_Error); return; } // Retrieve the directory from the stream following BackupStoreDirectory dir; std::auto_ptr dirstream(mrConnection.ReceiveStream()); dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); // Then... display everything BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { // Display this entry BackupStoreFilenameClear clear(en->GetName()); // Object ID? if(!opts[LIST_OPTION_NOOBJECTID]) { // add object ID to line #ifdef _MSC_VER printf("%08I64x ", (int64_t)en->GetObjectID()); #else printf("%08llx ", (long long)en->GetObjectID()); #endif } // Flags? if(!opts[LIST_OPTION_NOFLAGS]) { static const char *flags = BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES; char displayflags[16]; // make sure f is big enough ASSERT(sizeof(displayflags) >= sizeof(BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES) + 3); // Insert flags char *f = displayflags; const char *t = flags; int16_t en_flags = en->GetFlags(); while(*t != 0) { *f = ((en_flags&1) == 0)?'-':*t; en_flags >>= 1; f++; t++; } // attributes flags *(f++) = (en->HasAttributes())?'a':'-'; // terminate *(f++) = ' '; *(f++) = '\0'; printf(displayflags); if(en_flags != 0) { printf("[ERROR: Entry has additional flags set] "); } } if(opts[LIST_OPTION_TIMES_UTC]) { // Show UTC times... printf("%s ", GetTimeString(*en, false, opts[LIST_OPTION_TIMES_ATTRIBS]).c_str()); } if(opts[LIST_OPTION_TIMES_LOCAL]) { // Show local times... printf("%s ", GetTimeString(*en, true, opts[LIST_OPTION_TIMES_ATTRIBS]).c_str()); } if(opts[LIST_OPTION_DISPLAY_HASH]) { #ifdef _MSC_VER printf("%016I64x ", (int64_t)en->GetAttributesHash()); #else printf("%016llx ", (long long)en->GetAttributesHash()); #endif } if(opts[LIST_OPTION_SIZEINBLOCKS]) { #ifdef _MSC_VER printf("%05I64d ", (int64_t)en->GetSizeInBlocks()); #else printf("%05lld ", (long long)en->GetSizeInBlocks()); #endif } // add name if(!FirstLevel) { #ifdef WIN32 std::string listRootDecoded; if(!ConvertUtf8ToConsole(rListRoot.c_str(), listRootDecoded)) return; printf("%s/", listRootDecoded.c_str()); #else printf("%s/", rListRoot.c_str()); #endif } #ifdef WIN32 { std::string fileName; if(!ConvertUtf8ToConsole( clear.GetClearFilename().c_str(), fileName)) return; printf("%s", fileName.c_str()); } #else printf("%s", clear.GetClearFilename().c_str()); #endif if(!en->GetName().IsEncrypted()) { printf("[FILENAME NOT ENCRYPTED]"); } printf("\n"); // Directory? if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0) { // Recurse? if(opts[LIST_OPTION_RECURSIVE]) { std::string subroot(rListRoot); if(!FirstLevel) subroot += '/'; subroot += clear.GetClearFilename(); List(en->GetObjectID(), subroot, opts, false /* not the first level to list */); } } } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::FindDirectoryObjectID(const // std::string &) // Purpose: Find the object ID of a directory on the store, // or return 0 for not found. If pStack != 0, the // object is set to the stack of directories. // Will start from the current directory stack. // Created: 2003/10/10 // // -------------------------------------------------------------------------- int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName, bool AllowOldVersion, bool AllowDeletedDirs, std::vector > *pStack) { // Split up string into elements std::vector dirElements; SplitString(rDirName, '/', dirElements); // Start from current stack, or root, whichever is required std::vector > stack; int64_t dirID = BackupProtocolClientListDirectory::RootDirectory; if(rDirName.size() > 0 && rDirName[0] == '/') { // Root, do nothing } else { // Copy existing stack stack = mDirStack; if(stack.size() > 0) { dirID = stack[stack.size() - 1].second; } } // Generate exclude flags int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING; if(!AllowOldVersion) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion; if(!AllowDeletedDirs) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted; // Read directories for(unsigned int e = 0; e < dirElements.size(); ++e) { if(dirElements[e].size() > 0) { if(dirElements[e] == ".") { // Ignore. } else if(dirElements[e] == "..") { // Up one! if(stack.size() > 0) { // Remove top element stack.pop_back(); // New dir ID dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolClientListDirectory::RootDirectory; } else { // At root anyway dirID = BackupProtocolClientListDirectory::RootDirectory; } } else { // Not blank element. Read current directory. std::auto_ptr dirreply(mrConnection.QueryListDirectory( dirID, BackupProtocolClientListDirectory::Flags_Dir, // just directories excludeFlags, true /* want attributes */)); // Retrieve the directory from the stream following BackupStoreDirectory dir; std::auto_ptr dirstream(mrConnection.ReceiveStream()); dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); // Then... find the directory within it BackupStoreDirectory::Iterator i(dir); BackupStoreFilenameClear dirname(dirElements[e]); BackupStoreDirectory::Entry *en = i.FindMatchingClearName(dirname); if(en == 0) { // Not found return 0; } // Object ID for next round of searching dirID = en->GetObjectID(); // Push onto stack stack.push_back(std::pair(dirElements[e], dirID)); } } } // If required, copy the new stack to the caller if(pStack) { *pStack = stack; } return dirID; } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::GetCurrentDirectoryID() // Purpose: Returns the ID of the current directory // Created: 2003/10/10 // // -------------------------------------------------------------------------- int64_t BackupQueries::GetCurrentDirectoryID() { // Special case for root if(mDirStack.size() == 0) { return BackupProtocolClientListDirectory::RootDirectory; } // Otherwise, get from the last entry on the stack return mDirStack[mDirStack.size() - 1].second; } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::GetCurrentDirectoryName() // Purpose: Gets the name of the current directory // Created: 2003/10/10 // // -------------------------------------------------------------------------- std::string BackupQueries::GetCurrentDirectoryName() { // Special case for root if(mDirStack.size() == 0) { return std::string("/"); } // Build path std::string r; for(unsigned int l = 0; l < mDirStack.size(); ++l) { r += "/"; #ifdef WIN32 std::string dirName; if(!ConvertUtf8ToConsole(mDirStack[l].first.c_str(), dirName)) return "error"; r += dirName; #else r += mDirStack[l].first; #endif } return r; } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandChangeDir(const std::vector &) // Purpose: Change directory command // Created: 2003/10/10 // // -------------------------------------------------------------------------- void BackupQueries::CommandChangeDir(const std::vector &args, const bool *opts) { if(args.size() != 1 || args[0].size() == 0) { BOX_ERROR("Incorrect usage. cd [-o] [-d] "); SetReturnCode(ReturnCode::Command_Error); return; } #ifdef WIN32 std::string dirName; if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return; #else const std::string& dirName(args[0]); #endif std::vector > newStack; int64_t id = FindDirectoryObjectID(dirName, opts['o'], opts['d'], &newStack); if(id == 0) { BOX_ERROR("Directory '" << args[0] << "' not found."); SetReturnCode(ReturnCode::Command_Error); return; } // Store new stack mDirStack = newStack; } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandChangeLocalDir(const std::vector &) // Purpose: Change local directory command // Created: 2003/10/11 // // -------------------------------------------------------------------------- void BackupQueries::CommandChangeLocalDir(const std::vector &args) { if(args.size() != 1 || args[0].size() == 0) { BOX_ERROR("Incorrect usage. lcd "); SetReturnCode(ReturnCode::Command_Error); return; } // Try changing directory #ifdef WIN32 std::string dirName; if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) { BOX_ERROR("Failed to convert path from console encoding."); SetReturnCode(ReturnCode::Command_Error); return; } int result = ::chdir(dirName.c_str()); #else int result = ::chdir(args[0].c_str()); #endif if(result != 0) { if(errno == ENOENT || errno == ENOTDIR) { BOX_ERROR("Directory '" << args[0] << "' does not exist."); } else { BOX_LOG_SYS_ERROR("Failed to change to directory " "'" << args[0] << "'"); } SetReturnCode(ReturnCode::Command_Error); return; } // Report current dir char wd[PATH_MAX]; if(::getcwd(wd, PATH_MAX) == 0) { BOX_LOG_SYS_ERROR("Error getting current directory"); SetReturnCode(ReturnCode::Command_Error); return; } #ifdef WIN32 if(!ConvertUtf8ToConsole(wd, dirName)) { BOX_ERROR("Failed to convert new path from console encoding."); SetReturnCode(ReturnCode::Command_Error); return; } BOX_INFO("Local current directory is now '" << dirName << "'."); #else BOX_INFO("Local current directory is now '" << wd << "'."); #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandGetObject(const std::vector &, const bool *) // Purpose: Gets an object without any translation. // Created: 2003/10/11 // // -------------------------------------------------------------------------- void BackupQueries::CommandGetObject(const std::vector &args, const bool *opts) { // Check args if(args.size() != 2) { BOX_ERROR("Incorrect usage. getobject " ""); return; } int64_t id = ::strtoll(args[0].c_str(), 0, 16); if(id == std::numeric_limits::min() || id == std::numeric_limits::max() || id == 0) { BOX_ERROR("Not a valid object ID (specified in hex)."); return; } // Does file exist? EMU_STRUCT_STAT st; if(EMU_STAT(args[1].c_str(), &st) == 0 || errno != ENOENT) { BOX_ERROR("The local file '" << args[1] << " already exists."); return; } // Open file FileStream out(args[1].c_str(), O_WRONLY | O_CREAT | O_EXCL); // Request that object try { // Request object std::auto_ptr getobj(mrConnection.QueryGetObject(id)); if(getobj->GetObjectID() != BackupProtocolClientGetObject::NoObject) { // Stream that object out to the file std::auto_ptr objectStream(mrConnection.ReceiveStream()); objectStream->CopyStreamTo(out); BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(id) << " fetched successfully."); } else { BOX_ERROR("Object ID " << BOX_FORMAT_OBJECTID(id) << " does not exist on store."); ::unlink(args[1].c_str()); } } catch(...) { ::unlink(args[1].c_str()); BOX_ERROR("Error occured fetching object."); } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::FindFileID(const std::string& // rNameOrIdString, const bool *options, // int64_t *pDirIdOut, std::string* pFileNameOut) // Purpose: Locate a file on the store (either by name or by // object ID, depending on opts['i'], where name can // include a path) and return the file ID, placing the // directory ID in *pDirIdOut and the filename part // of the path in *pFileNameOut (if not NULL). // Created: 2008-09-12 // // -------------------------------------------------------------------------- int64_t BackupQueries::FindFileID(const std::string& rNameOrIdString, const bool *opts, int64_t *pDirIdOut, std::string* pFileNameOut, int16_t flagsInclude, int16_t flagsExclude, int16_t* pFlagsOut) { // Find object ID somehow int64_t fileId; int64_t dirId = GetCurrentDirectoryID(); std::string fileName = rNameOrIdString; if(!opts['i']) { // does this remote filename include a path? std::string::size_type index = fileName.rfind('/'); if(index != std::string::npos) { std::string dirName(fileName.substr(0, index)); fileName = fileName.substr(index + 1); dirId = FindDirectoryObjectID(dirName); if(dirId == 0) { BOX_ERROR("Directory '" << dirName << "' not found."); return 0; } } } BackupStoreFilenameClear fn(fileName); // Need to look it up in the current directory mrConnection.QueryListDirectory( dirId, flagsInclude, flagsExclude, true /* do want attributes */); // Retrieve the directory from the stream following BackupStoreDirectory dir; std::auto_ptr dirstream(mrConnection.ReceiveStream()); dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); BackupStoreDirectory::Entry *en; if(opts['i']) { // Specified as ID. fileId = ::strtoll(rNameOrIdString.c_str(), 0, 16); if(fileId == std::numeric_limits::min() || fileId == std::numeric_limits::max() || fileId == 0) { BOX_ERROR("Not a valid object ID (specified in hex)."); return 0; } // Check that the item is actually in the directory en = dir.FindEntryByID(fileId); if(en == 0) { BOX_ERROR("File ID " << BOX_FORMAT_OBJECTID(fileId) << " not found in current directory on store.\n" "(You can only access files by ID from the " "current directory.)"); return 0; } } else { // Specified by name, find the object in the directory to get the ID BackupStoreDirectory::Iterator i(dir); en = i.FindMatchingClearName(fn); if(en == 0) { BOX_ERROR("Filename '" << rNameOrIdString << "' " "not found in current directory on store.\n" "(Subdirectories in path not searched.)"); return 0; } fileId = en->GetObjectID(); } *pDirIdOut = dirId; if(pFlagsOut) { *pFlagsOut = en->GetFlags(); } if(pFileNameOut) { BackupStoreFilenameClear entryName(en->GetName()); *pFileNameOut = entryName.GetClearFilename(); } return fileId; } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandGet(const std::vector &, const bool *) // Purpose: Command to get a file from the store // Created: 2003/10/12 // // -------------------------------------------------------------------------- void BackupQueries::CommandGet(std::vector args, const bool *opts) { // At least one argument? // Check args if(args.size() < 1 || (opts['i'] && args.size() != 2) || args.size() > 2) { BOX_ERROR("Incorrect usage.\n" "get [] or\n" "get -i "); return; } // Find object ID somehow int64_t fileId, dirId; std::string localName; #ifdef WIN32 for (std::vector::iterator i = args.begin(); i != args.end(); i++) { std::string out; if(!ConvertConsoleToUtf8(i->c_str(), out)) { BOX_ERROR("Failed to convert encoding."); return; } *i = out; } #endif int16_t flagsExclude; if(opts['i']) { // can retrieve anything by ID flagsExclude = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING; } else { // only current versions by name flagsExclude = BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted; } fileId = FindFileID(args[0], opts, &dirId, &localName, BackupProtocolClientListDirectory::Flags_File, // just files flagsExclude, NULL /* don't care about flags found */); if (fileId == 0) { // error already reported return; } if(opts['i']) { // Specified as ID. Must have a local name in the arguments // (check at beginning of function ensures this) localName = args[1]; } else { // Specified by name. Local name already set by FindFileID, // but may be overridden by user supplying a second argument. if(args.size() == 2) { localName = args[1]; } } // Does local file already exist? (don't want to overwrite) EMU_STRUCT_STAT st; if(EMU_STAT(localName.c_str(), &st) == 0 || errno != ENOENT) { BOX_ERROR("The local file " << localName << " already exists, " "will not overwrite it."); SetReturnCode(ReturnCode::Command_Error); return; } // Request it from the store try { // Request object mrConnection.QueryGetFile(dirId, fileId); // Stream containing encoded file std::auto_ptr objectStream(mrConnection.ReceiveStream()); // Decode it BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout()); // Done. BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(fileId) << " fetched successfully."); } catch (BoxException &e) { BOX_ERROR("Failed to fetch file: " << e.what()); ::unlink(localName.c_str()); } catch(std::exception &e) { BOX_ERROR("Failed to fetch file: " << e.what()); ::unlink(localName.c_str()); } catch(...) { BOX_ERROR("Failed to fetch file: unknown error"); ::unlink(localName.c_str()); } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CompareParams::CompareParams() // Purpose: Constructor // Created: 29/1/04 // // -------------------------------------------------------------------------- BackupQueries::CompareParams::CompareParams(bool QuickCompare, bool IgnoreExcludes, bool IgnoreAttributes, box_time_t LatestFileUploadTime) : BoxBackupCompareParams(QuickCompare, IgnoreExcludes, IgnoreAttributes, LatestFileUploadTime), mDifferences(0), mDifferencesExplainedByModTime(0), mUncheckedFiles(0), mExcludedDirs(0), mExcludedFiles(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandCompare(const std::vector &, const bool *) // Purpose: Command to compare data on the store with local data // Created: 2003/10/12 // // -------------------------------------------------------------------------- void BackupQueries::CommandCompare(const std::vector &args, const bool *opts) { box_time_t LatestFileUploadTime = GetCurrentBoxTime(); // Try and work out the time before which all files should be on the server { std::string syncTimeFilename(mrConfiguration.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR); syncTimeFilename += "last_sync_start"; // Stat it to get file time EMU_STRUCT_STAT st; if(EMU_STAT(syncTimeFilename.c_str(), &st) == 0) { // Files modified after this time shouldn't be on the server, so report errors slightly differently LatestFileUploadTime = FileModificationTime(st) - SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge")); } else { BOX_WARNING("Failed to determine the time of the last " "synchronisation -- checks not performed."); } } // Parameters, including count of differences BackupQueries::CompareParams params(opts['q'], // quick compare? opts['E'], // ignore excludes opts['A'], // ignore attributes LatestFileUploadTime); params.mQuietCompare = opts['Q']; // Quick compare? if(params.QuickCompare()) { BOX_WARNING("Quick compare used -- file attributes are not " "checked."); } if(!opts['l'] && opts['a'] && args.size() == 0) { // Compare all locations const Configuration &rLocations( mrConfiguration.GetSubConfiguration("BackupLocations")); std::vector locNames = rLocations.GetSubConfigurationNames(); for(std::vector::iterator pLocName = locNames.begin(); pLocName != locNames.end(); pLocName++) { CompareLocation(*pLocName, params); } } else if(opts['l'] && !opts['a'] && args.size() == 1) { // Compare one location CompareLocation(args[0], params); } else if(!opts['l'] && !opts['a'] && args.size() == 2) { // Compare directory to directory // Can't be bothered to do all the hard work to work out which location it's on, and hence which exclude list if(!params.IgnoreExcludes()) { BOX_ERROR("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes."); return; } else { // Do compare Compare(args[0], args[1], params); } } else { BOX_ERROR("Incorrect usage.\ncompare -a\n or compare -l \n or compare "); return; } if (!params.mQuietCompare) { BOX_INFO("[ " << params.mDifferencesExplainedByModTime << " (of " << params.mDifferences << ") differences probably " "due to file modifications after the last upload ]"); } BOX_INFO("Differences: " << params.mDifferences << " (" << params.mExcludedDirs << " dirs excluded, " << params.mExcludedFiles << " files excluded, " << params.mUncheckedFiles << " files not checked)"); // Set return code? if(opts['c']) { if (params.mUncheckedFiles != 0) { SetReturnCode(ReturnCode::Compare_Error); } else if (params.mDifferences != 0) { SetReturnCode(ReturnCode::Compare_Different); } else { SetReturnCode(ReturnCode::Compare_Same); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CompareLocation(const std::string &, BackupQueries::CompareParams &) // Purpose: Compare a location // Created: 2003/10/13 // // -------------------------------------------------------------------------- void BackupQueries::CompareLocation(const std::string &rLocation, BoxBackupCompareParams &rParams) { // Find the location's sub configuration const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations")); if(!locations.SubConfigurationExists(rLocation.c_str())) { BOX_ERROR("Location " << rLocation << " does not exist."); return; } const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str())); #ifdef WIN32 { std::string path = loc.GetKeyValue("Path"); if (path.size() > 0 && path[path.size()-1] == DIRECTORY_SEPARATOR_ASCHAR) { BOX_WARNING("Location '" << rLocation << "' path ends " "with '" DIRECTORY_SEPARATOR "', " "compare may fail!"); } } #endif // Generate the exclude lists if(!rParams.IgnoreExcludes()) { rParams.LoadExcludeLists(loc); } // Then get it compared Compare(std::string("/") + rLocation, loc.GetKeyValue("Path"), rParams); } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::Compare(const std::string &, // const std::string &, BackupQueries::CompareParams &) // Purpose: Compare a store directory against a local directory // Created: 2003/10/13 // // -------------------------------------------------------------------------- void BackupQueries::Compare(const std::string &rStoreDir, const std::string &rLocalDir, BoxBackupCompareParams &rParams) { #ifdef WIN32 std::string localDirEncoded; std::string storeDirEncoded; if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return; if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return; #else const std::string& localDirEncoded(rLocalDir); const std::string& storeDirEncoded(rStoreDir); #endif // Get the directory ID of the directory -- only use current data int64_t dirID = FindDirectoryObjectID(storeDirEncoded); // Found? if(dirID == 0) { bool modifiedAfterLastSync = false; EMU_STRUCT_STAT st; if(EMU_STAT(rLocalDir.c_str(), &st) == 0) { if(FileAttrModificationTime(st) > rParams.LatestFileUploadTime()) { modifiedAfterLastSync = true; } } rParams.NotifyRemoteFileMissing(localDirEncoded, storeDirEncoded, modifiedAfterLastSync); return; } // Go! Compare(dirID, storeDirEncoded, localDirEncoded, rParams); } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::Compare(int64_t, const std::string &, // const std::string &, BackupQueries::CompareParams &) // Purpose: Compare a store directory against a local directory // Created: 2003/10/13 // // -------------------------------------------------------------------------- void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const std::string &rLocalDir, BoxBackupCompareParams &rParams) { rParams.NotifyDirComparing(rLocalDir, rStoreDir); // Get info on the local directory EMU_STRUCT_STAT st; if(EMU_LSTAT(rLocalDir.c_str(), &st) != 0) { // What kind of error? if(errno == ENOTDIR || errno == ENOENT) { rParams.NotifyLocalDirMissing(rLocalDir, rStoreDir); } else { rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); } return; } // Get the directory listing from the store mrConnection.QueryListDirectory( DirID, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // get everything BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted, // except for old versions and deleted files true /* want attributes */); // Retrieve the directory from the stream following BackupStoreDirectory dir; std::auto_ptr dirstream(mrConnection.ReceiveStream()); dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); // Test out the attributes if(!dir.HasAttributes()) { rParams.NotifyStoreDirMissingAttributes(rLocalDir, rStoreDir); } else { // Fetch the attributes const StreamableMemBlock &storeAttr(dir.GetAttributes()); BackupClientFileAttributes attr(storeAttr); // Get attributes of local directory BackupClientFileAttributes localAttr; localAttr.ReadAttributes(rLocalDir.c_str(), true /* directories have zero mod times */); if(attr.Compare(localAttr, true, true /* ignore modification times */)) { rParams.NotifyDirCompared(rLocalDir, rStoreDir, false, false /* actually we didn't check :) */); } else { bool modifiedAfterLastSync = false; EMU_STRUCT_STAT st; if(EMU_STAT(rLocalDir.c_str(), &st) == 0) { if(FileAttrModificationTime(st) > rParams.LatestFileUploadTime()) { modifiedAfterLastSync = true; } } rParams.NotifyDirCompared(rLocalDir, rStoreDir, true, modifiedAfterLastSync); } } // Open the local directory DIR *dirhandle = ::opendir(rLocalDir.c_str()); if(dirhandle == 0) { rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); return; } try { // Read the files and directories into sets std::set localFiles; std::set localDirs; struct dirent *localDirEn = 0; while((localDirEn = readdir(dirhandle)) != 0) { // Not . and ..! if(localDirEn->d_name[0] == '.' && (localDirEn->d_name[1] == '\0' || (localDirEn->d_name[1] == '.' && localDirEn->d_name[2] == '\0'))) { // ignore, it's . or .. #ifdef HAVE_VALID_DIRENT_D_TYPE if (localDirEn->d_type != DT_DIR) { BOX_ERROR("d_type does not really " "work on your platform. " "Reconfigure Box!"); return; } #endif continue; } std::string localDirPath(MakeFullPath(rLocalDir, localDirEn->d_name)); std::string storeDirPath(rStoreDir + "/" + localDirEn->d_name); #ifndef HAVE_VALID_DIRENT_D_TYPE EMU_STRUCT_STAT st; if(EMU_LSTAT(localDirPath.c_str(), &st) != 0) { // Check whether dir is excluded before trying // to stat it, to fix problems with .gvfs // directories that are not readable by root // causing compare to crash: // http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000013.html if(rParams.IsExcludedDir(localDirPath)) { rParams.NotifyExcludedDir(localDirPath, storeDirPath); continue; } else { THROW_EXCEPTION_MESSAGE(CommonException, OSFileError, localDirPath); } } // Entry -- file or dir? if(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { // File or symbolic link localFiles.insert(std::string(localDirEn->d_name)); } else if(S_ISDIR(st.st_mode)) { // Directory localDirs.insert(std::string(localDirEn->d_name)); } #else // Entry -- file or dir? if(localDirEn->d_type == DT_REG || localDirEn->d_type == DT_LNK) { // File or symbolic link localFiles.insert(std::string(localDirEn->d_name)); } else if(localDirEn->d_type == DT_DIR) { // Directory localDirs.insert(std::string(localDirEn->d_name)); } #endif } // Close directory if(::closedir(dirhandle) != 0) { BOX_LOG_SYS_ERROR("Failed to close local directory " "'" << rLocalDir << "'"); } dirhandle = 0; // Do the same for the store directories std::set > storeFiles; std::set > storeDirs; BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *storeDirEn = 0; while((storeDirEn = i.Next()) != 0) { // Decrypt filename BackupStoreFilenameClear name(storeDirEn->GetName()); // What is it? if((storeDirEn->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == BackupStoreDirectory::Entry::Flags_File) { // File storeFiles.insert(std::pair(name.GetClearFilename(), storeDirEn)); } else { // Dir storeDirs.insert(std::pair(name.GetClearFilename(), storeDirEn)); } } #ifdef _MSC_VER typedef std::set::iterator string_set_iter_t; #else typedef std::set::const_iterator string_set_iter_t; #endif // Now compare files. for(std::set >::const_iterator i = storeFiles.begin(); i != storeFiles.end(); ++i) { const std::string& fileName(i->first); std::string localPath(MakeFullPath(rLocalDir, fileName)); std::string storePath(rStoreDir + "/" + fileName); rParams.NotifyFileComparing(localPath, storePath); // Does the file exist locally? string_set_iter_t local(localFiles.find(fileName)); if(local == localFiles.end()) { // Not found -- report rParams.NotifyLocalFileMissing(localPath, storePath); } else { int64_t fileSize = 0; EMU_STRUCT_STAT st; if(EMU_STAT(localPath.c_str(), &st) == 0) { fileSize = st.st_size; } try { // Files the same flag? bool equal = true; // File modified after last sync flag bool modifiedAfterLastSync = false; bool hasDifferentAttribs = false; if(rParams.QuickCompare()) { // Compare file -- fetch it mrConnection.QueryGetBlockIndexByID(i->second->GetObjectID()); // Stream containing block index std::auto_ptr blockIndexStream(mrConnection.ReceiveStream()); // Compare equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(localPath.c_str(), *blockIndexStream, mrConnection.GetTimeout()); } else { // Compare file -- fetch it mrConnection.QueryGetFile(DirID, i->second->GetObjectID()); // Stream containing encoded file std::auto_ptr objectStream(mrConnection.ReceiveStream()); // Decode it std::auto_ptr fileOnServerStream; // Got additional attributes? if(i->second->HasAttributes()) { // Use these attributes const StreamableMemBlock &storeAttr(i->second->GetAttributes()); BackupClientFileAttributes attr(storeAttr); fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout(), &attr).release()); } else { // Use attributes stored in file fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout()).release()); } // Should always be something in the auto_ptr, it's how the interface is defined. But be paranoid. if(!fileOnServerStream.get()) { THROW_EXCEPTION(BackupStoreException, Internal) } // Compare attributes BackupClientFileAttributes localAttr; box_time_t fileModTime = 0; localAttr.ReadAttributes(localPath.c_str(), false /* don't zero mod times */, &fileModTime); modifiedAfterLastSync = (fileModTime > rParams.LatestFileUploadTime()); bool ignoreAttrModTime = true; #ifdef WIN32 // attr mod time is really // creation time, so check it ignoreAttrModTime = false; #endif if(!rParams.IgnoreAttributes() && #ifdef PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE !fileOnServerStream->IsSymLink() && #endif !localAttr.Compare(fileOnServerStream->GetAttributes(), ignoreAttrModTime, fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */)) { hasDifferentAttribs = true; } // Compare contents, if it's a regular file not a link // Remember, we MUST read the entire stream from the server. SelfFlushingStream flushObject(*objectStream); if(!fileOnServerStream->IsSymLink()) { SelfFlushingStream flushFile(*fileOnServerStream); // Open the local file FileStream l(localPath.c_str()); equal = l.CompareWith(*fileOnServerStream, mrConnection.GetTimeout()); } } rParams.NotifyFileCompared(localPath, storePath, fileSize, hasDifferentAttribs, !equal, modifiedAfterLastSync, i->second->HasAttributes()); } catch(BoxException &e) { rParams.NotifyDownloadFailed(localPath, storePath, fileSize, e); } catch(std::exception &e) { rParams.NotifyDownloadFailed(localPath, storePath, fileSize, e); } catch(...) { rParams.NotifyDownloadFailed(localPath, storePath, fileSize); } // Remove from set so that we know it's been compared localFiles.erase(local); } } // Report any files which exist locally, but not on the store for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i) { std::string localPath(MakeFullPath(rLocalDir, *i)); std::string storePath(rStoreDir + "/" + *i); // Should this be ignored (ie is excluded)? if(!rParams.IsExcludedFile(localPath)) { bool modifiedAfterLastSync = false; EMU_STRUCT_STAT st; if(EMU_STAT(localPath.c_str(), &st) == 0) { if(FileModificationTime(st) > rParams.LatestFileUploadTime()) { modifiedAfterLastSync = true; } } rParams.NotifyRemoteFileMissing(localPath, storePath, modifiedAfterLastSync); } else { rParams.NotifyExcludedFile(localPath, storePath); } } // Finished with the files, clear the sets to reduce memory usage slightly localFiles.clear(); storeFiles.clear(); // Now do the directories, recursively to check subdirectories for(std::set >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i) { std::string localPath(MakeFullPath(rLocalDir, i->first)); std::string storePath(rStoreDir + "/" + i->first); // Does the directory exist locally? string_set_iter_t local(localDirs.find(i->first)); if(local == localDirs.end() && rParams.IsExcludedDir(localPath)) { rParams.NotifyExcludedFileNotDeleted(localPath, storePath); } else if(local == localDirs.end()) { // Not found -- report rParams.NotifyLocalFileMissing(localPath, storePath); } else if(rParams.IsExcludedDir(localPath)) { // don't recurse into excluded directories } else { // Compare directory Compare(i->second->GetObjectID(), storePath, localPath, rParams); // Remove from set so that we know it's been compared localDirs.erase(local); } } // Report any directories which exist locally, but not on the store for(std::set::const_iterator i = localDirs.begin(); i != localDirs.end(); ++i) { std::string localPath(MakeFullPath(rLocalDir, *i)); std::string storePath(rStoreDir + "/" + *i); // Should this be ignored (ie is excluded)? if(!rParams.IsExcludedDir(localPath)) { bool modifiedAfterLastSync = false; // Check the dir modification time EMU_STRUCT_STAT st; if(EMU_STAT(localPath.c_str(), &st) == 0 && FileModificationTime(st) > rParams.LatestFileUploadTime()) { modifiedAfterLastSync = true; } rParams.NotifyRemoteFileMissing(localPath, storePath, modifiedAfterLastSync); } else { rParams.NotifyExcludedDir(localPath, storePath); } } } catch(...) { if(dirhandle != 0) { ::closedir(dirhandle); } throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandRestore(const std::vector &, const bool *) // Purpose: Restore a directory // Created: 23/11/03 // // -------------------------------------------------------------------------- void BackupQueries::CommandRestore(const std::vector &args, const bool *opts) { // Check arguments if(args.size() != 2) { BOX_ERROR("Incorrect usage. restore [-drif] "); return; } // Restoring deleted things? bool restoreDeleted = opts['d']; std::string storeDirEncoded; // Get directory ID int64_t dirID = 0; if(opts['i']) { // Specified as ID. dirID = ::strtoll(args[0].c_str(), 0, 16); if(dirID == std::numeric_limits::min() || dirID == std::numeric_limits::max() || dirID == 0) { BOX_ERROR("Not a valid object ID (specified in hex)"); return; } std::ostringstream oss; oss << BOX_FORMAT_OBJECTID(args[0]); storeDirEncoded = oss.str(); } else { #ifdef WIN32 if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; #else storeDirEncoded = args[0]; #endif // Look up directory ID dirID = FindDirectoryObjectID(storeDirEncoded, false /* no old versions */, restoreDeleted /* find deleted dirs */); } // Allowable? if(dirID == 0) { BOX_ERROR("Directory '" << args[0] << "' not found on server"); return; } if(dirID == BackupProtocolClientListDirectory::RootDirectory) { BOX_ERROR("Cannot restore the root directory -- restore locations individually."); return; } #ifdef WIN32 std::string localName; if(!ConvertConsoleToUtf8(args[1].c_str(), localName)) return; #else std::string localName(args[1]); #endif // Go and restore... int result; try { // At TRACE level, we print a line for each file and // directory, so we don't need dots. bool printDots = ! Logging::IsEnabled(Log::TRACE); result = BackupClientRestore(mrConnection, dirID, storeDirEncoded.c_str(), localName.c_str(), printDots /* print progress dots */, restoreDeleted, false /* don't undelete after restore! */, opts['r'] /* resume? */, opts['f'] /* force continue after errors */); } catch(std::exception &e) { BOX_ERROR("Failed to restore: " << e.what()); SetReturnCode(ReturnCode::Command_Error); return; } catch(...) { BOX_ERROR("Failed to restore: unknown exception"); SetReturnCode(ReturnCode::Command_Error); return; } switch(result) { case Restore_Complete: BOX_INFO("Restore complete."); break; case Restore_CompleteWithErrors: BOX_WARNING("Restore complete, but some files could not be " "restored."); break; case Restore_ResumePossible: BOX_ERROR("Resume possible -- repeat command with -r flag " "to resume."); SetReturnCode(ReturnCode::Command_Error); break; case Restore_TargetExists: BOX_ERROR("The target directory exists. You cannot restore " "over an existing directory."); SetReturnCode(ReturnCode::Command_Error); break; case Restore_TargetPathNotFound: BOX_ERROR("The target directory path does not exist.\n" "To restore to a directory whose parent " "does not exist, create the parent first."); SetReturnCode(ReturnCode::Command_Error); break; case Restore_UnknownError: BOX_ERROR("Unknown error during restore."); SetReturnCode(ReturnCode::Command_Error); break; default: BOX_ERROR("Unknown restore result " << result << "."); SetReturnCode(ReturnCode::Command_Error); break; } } // These are autogenerated by a script. extern char *help_commands[]; extern char *help_text[]; // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandHelp(const std::vector &args) // Purpose: Display help on commands // Created: 15/2/04 // // -------------------------------------------------------------------------- void BackupQueries::CommandHelp(const std::vector &args) { if(args.size() == 0) { // Display a list of all commands printf("Available commands are:\n"); for(int c = 0; help_commands[c] != 0; ++c) { printf(" %s\n", help_commands[c]); } printf("Type \"help \" for more information on a command.\n\n"); } else { // Display help on a particular command int c; for(c = 0; help_commands[c] != 0; ++c) { if(::strcmp(help_commands[c], args[0].c_str()) == 0) { // Found the command, print help printf("\n%s\n", help_text[c]); break; } } if(help_commands[c] == 0) { printf("No help found for command '%s'\n", args[0].c_str()); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandUsage() // Purpose: Display storage space used on server // Created: 19/4/04 // // -------------------------------------------------------------------------- void BackupQueries::CommandUsage(const bool *opts) { bool MachineReadable = opts['m']; // Request full details from the server std::auto_ptr usage(mrConnection.QueryGetAccountUsage()); // Display each entry in turn int64_t hardLimit = usage->GetBlocksHardLimit(); int32_t blockSize = usage->GetBlockSize(); CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit, blockSize, MachineReadable); CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(), hardLimit, blockSize, MachineReadable); CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(), hardLimit, blockSize, MachineReadable); CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(), hardLimit, blockSize, MachineReadable); CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(), hardLimit, blockSize, MachineReadable); CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize, MachineReadable); } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandUsageDisplayEntry(const char *, // int64_t, int64_t, int32_t, bool) // Purpose: Display an entry in the usage table // Created: 19/4/04 // // -------------------------------------------------------------------------- void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, int64_t HardLimit, int32_t BlockSize, bool MachineReadable) { std::cout << FormatUsageLineStart(Name, MachineReadable) << FormatUsageBar(Size, Size * BlockSize, HardLimit * BlockSize, MachineReadable) << std::endl; } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandUndelete(const std::vector &, const bool *) // Purpose: Undelete a directory // Created: 23/11/03 // // -------------------------------------------------------------------------- void BackupQueries::CommandUndelete(const std::vector &args, const bool *opts) { if (!mReadWrite) { BOX_ERROR("This command requires a read-write connection. " "Please reconnect with the -w option."); return; } // Check arguments if(args.size() != 1) { BOX_ERROR("Incorrect usage. undelete or undelete -i "); return; } #ifdef WIN32 std::string storeDirEncoded; if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; #else const std::string& storeDirEncoded(args[0]); #endif // Find object ID somehow int64_t fileId, parentId; std::string fileName; int16_t flagsOut; fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, /* include files and directories */ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, /* include old and deleted files */ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, &flagsOut); if (fileId == 0) { // error already reported return; } // Undelete it on the store try { // Undelete object if(flagsOut & BackupProtocolClientListDirectory::Flags_File) { mrConnection.QueryUndeleteFile(parentId, fileId); } else { mrConnection.QueryUndeleteDirectory(fileId); } } catch (BoxException &e) { BOX_ERROR("Failed to undelete object: " << e.what()); } catch(std::exception &e) { BOX_ERROR("Failed to undelete object: " << e.what()); } catch(...) { BOX_ERROR("Failed to undelete object: unknown error"); } } // -------------------------------------------------------------------------- // // Function // Name: BackupQueries::CommandDelete(const // std::vector &, const bool *) // Purpose: Deletes a file // Created: 23/11/03 // // -------------------------------------------------------------------------- void BackupQueries::CommandDelete(const std::vector &args, const bool *opts) { if (!mReadWrite) { BOX_ERROR("This command requires a read-write connection. " "Please reconnect with the -w option."); return; } // Check arguments if(args.size() != 1) { BOX_ERROR("Incorrect usage. delete "); return; } #ifdef WIN32 std::string storeDirEncoded; if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; #else const std::string& storeDirEncoded(args[0]); #endif // Find object ID somehow int64_t fileId, parentId; std::string fileName; int16_t flagsOut; fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, /* include files and directories */ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, /* exclude old and deleted files */ BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted, &flagsOut); if (fileId == 0) { // error already reported return; } BackupStoreFilenameClear fn(fileName); // Delete it on the store try { // Delete object if(flagsOut & BackupProtocolClientListDirectory::Flags_File) { mrConnection.QueryDeleteFile(parentId, fn); } else { mrConnection.QueryDeleteDirectory(fileId); } } catch (BoxException &e) { BOX_ERROR("Failed to delete object: " << e.what()); } catch(std::exception &e) { BOX_ERROR("Failed to delete object: " << e.what()); } catch(...) { BOX_ERROR("Failed to delete object: unknown error"); } } boxbackup/bin/bbackupquery/Makefile.extra0000664000175000017500000000016611345266370021353 0ustar siretartsiretart # AUTOGEN SEEDING autogen_Documentation.cpp: makedocumentation.pl documentation.txt $(_PERL) makedocumentation.pl boxbackup/bin/bbackupquery/BoxBackupCompareParams.h0000664000175000017500000000765011162266306023274 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BoxBackupCompareParams.h // Purpose: Parameters and notifiers for a compare operation // Created: 2008/12/30 // // -------------------------------------------------------------------------- #ifndef BOXBACKUPCOMPAREPARAMS__H #define BOXBACKUPCOMPAREPARAMS__H #include #include #include "BoxTime.h" #include "ExcludeList.h" #include "BackupClientMakeExcludeList.h" // -------------------------------------------------------------------------- // // Class // Name: BoxBackupCompareParams // Purpose: Parameters and notifiers for a compare operation // Created: 2003/10/10 // // -------------------------------------------------------------------------- class BoxBackupCompareParams { private: std::auto_ptr mapExcludeFiles, mapExcludeDirs; bool mQuickCompare; bool mIgnoreExcludes; bool mIgnoreAttributes; box_time_t mLatestFileUploadTime; public: BoxBackupCompareParams(bool QuickCompare, bool IgnoreExcludes, bool IgnoreAttributes, box_time_t LatestFileUploadTime) : mQuickCompare(QuickCompare), mIgnoreExcludes(IgnoreExcludes), mIgnoreAttributes(IgnoreAttributes), mLatestFileUploadTime(LatestFileUploadTime) { } virtual ~BoxBackupCompareParams() { } bool QuickCompare() { return mQuickCompare; } bool IgnoreExcludes() { return mIgnoreExcludes; } bool IgnoreAttributes() { return mIgnoreAttributes; } box_time_t LatestFileUploadTime() { return mLatestFileUploadTime; } void LoadExcludeLists(const Configuration& rLoc) { mapExcludeFiles.reset(BackupClientMakeExcludeList_Files(rLoc)); mapExcludeDirs.reset(BackupClientMakeExcludeList_Dirs(rLoc)); } bool IsExcludedFile(const std::string& rLocalPath) { if (!mapExcludeFiles.get()) return false; return mapExcludeFiles->IsExcluded(rLocalPath); } bool IsExcludedDir(const std::string& rLocalPath) { if (!mapExcludeDirs.get()) return false; return mapExcludeDirs->IsExcluded(rLocalPath); } virtual void NotifyLocalDirMissing(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyLocalDirAccessFailed(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyStoreDirMissingAttributes(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyRemoteFileMissing(const std::string& rLocalPath, const std::string& rRemotePath, bool modifiedAfterLastSync) = 0; virtual void NotifyLocalFileMissing(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyExcludedFileNotDeleted(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyDownloadFailed(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes, BoxException& rException) = 0; virtual void NotifyDownloadFailed(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes, std::exception& rException) = 0; virtual void NotifyDownloadFailed(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes) = 0; virtual void NotifyExcludedFile(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyExcludedDir(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyDirComparing(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyDirCompared(const std::string& rLocalPath, const std::string& rRemotePath, bool HasDifferentAttributes, bool modifiedAfterLastSync) = 0; virtual void NotifyFileComparing(const std::string& rLocalPath, const std::string& rRemotePath) = 0; virtual void NotifyFileCompared(const std::string& rLocalPath, const std::string& rRemotePath, int64_t NumBytes, bool HasDifferentAttributes, bool HasDifferentContents, bool modifiedAfterLastSync, bool newAttributesApplied) = 0; }; #endif // BOXBACKUPCOMPAREPARAMS__H boxbackup/bin/bbackupquery/bbackupquery.cpp0000664000175000017500000002231511345271627021773 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: bbackupquery.cpp // Purpose: Backup query utility // Created: 2003/10/10 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_LIBREADLINE #ifdef HAVE_READLINE_READLINE_H #include #elif defined(HAVE_EDITLINE_READLINE_H) #include #elif defined(HAVE_READLINE_H) #include #endif #endif #ifdef HAVE_READLINE_HISTORY #ifdef HAVE_READLINE_HISTORY_H #include #elif defined(HAVE_HISTORY_H) #include #endif #endif #include #include "MainHelper.h" #include "BoxPortsAndFiles.h" #include "BackupDaemonConfigVerify.h" #include "SocketStreamTLS.h" #include "Socket.h" #include "TLSContext.h" #include "SSLLib.h" #include "BackupStoreConstants.h" #include "BackupStoreException.h" #include "autogen_BackupProtocolClient.h" #include "BackupQueries.h" #include "FdGetLine.h" #include "BackupClientCryptoKeys.h" #include "BannerText.h" #include "Logging.h" #include "MemLeakFindOn.h" void PrintUsageAndExit() { printf("Usage: bbackupquery [-q*|v*|V|W] [-w] " #ifdef WIN32 "[-u] " #endif "\n" "\t[-c config_file] [-o log_file] [-O log_file_level]\n" "\t[-l protocol_log_file] [commands]\n" "\n" "As many commands as you require.\n" "If commands are multiple words, remember to enclose the command in quotes.\n" "Remember to use the quit command unless you want to end up in interactive mode.\n"); exit(1); } int main(int argc, const char *argv[]) { int returnCode = 0; MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupquery.memleaks", "bbackupquery") MAINHELPER_START #ifdef WIN32 WSADATA info; // Under Win32 we must initialise the Winsock library // before using it. if (WSAStartup(0x0101, &info) == SOCKET_ERROR) { // throw error? perhaps give it its own id in the future THROW_EXCEPTION(BackupStoreException, Internal) } #endif // Really don't want trace statements happening, even in debug mode #ifndef BOX_RELEASE_BUILD BoxDebugTraceOn = false; #endif FILE *logFile = 0; // Filename for configuration file? std::string configFilename; #ifdef WIN32 configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; #else configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG; #endif // Flags bool readWrite = false; Logging::SetProgramName("bbackupquery"); #ifdef BOX_RELEASE_BUILD int masterLevel = Log::NOTICE; // need an int to do math with #else int masterLevel = Log::INFO; // need an int to do math with #endif #ifdef WIN32 const char* validOpts = "qvVwuc:l:o:O:W:"; bool unicodeConsole = false; #else const char* validOpts = "qvVwc:l:o:O:W:"; #endif std::string fileLogFile; Log::Level fileLogLevel = Log::INVALID; // See if there's another entry on the command line int c; while((c = getopt(argc, (char * const *)argv, validOpts)) != -1) { switch(c) { case 'q': { if(masterLevel == Log::NOTHING) { BOX_FATAL("Too many '-q': " "Cannot reduce logging " "level any more"); return 2; } masterLevel--; } break; case 'v': { if(masterLevel == Log::EVERYTHING) { BOX_FATAL("Too many '-v': " "Cannot increase logging " "level any more"); return 2; } masterLevel++; } break; case 'V': { masterLevel = Log::EVERYTHING; } break; case 'W': { masterLevel = Logging::GetNamedLevel(optarg); if (masterLevel == Log::INVALID) { BOX_FATAL("Invalid logging level"); return 2; } } break; case 'w': // Read/write mode readWrite = true; break; case 'c': // store argument configFilename = optarg; break; case 'l': // open log file logFile = ::fopen(optarg, "w"); if(logFile == 0) { BOX_LOG_SYS_ERROR("Failed to open log file " "'" << optarg << "'"); } break; case 'o': fileLogFile = optarg; fileLogLevel = Log::EVERYTHING; break; case 'O': { fileLogLevel = Logging::GetNamedLevel(optarg); if (fileLogLevel == Log::INVALID) { BOX_FATAL("Invalid logging level"); return 2; } } break; #ifdef WIN32 case 'u': unicodeConsole = true; break; #endif case '?': default: PrintUsageAndExit(); } } // Adjust arguments argc -= optind; argv += optind; Logging::SetGlobalLevel((Log::Level)masterLevel); std::auto_ptr fileLogger; if (fileLogLevel != Log::INVALID) { fileLogger.reset(new FileLogger(fileLogFile, fileLogLevel)); } bool quiet = false; if (masterLevel < Log::NOTICE) { // Quiet mode quiet = true; } // Print banner? if(!quiet) { const char *banner = BANNER_TEXT("Backup Query Tool"); BOX_NOTICE(banner); } #ifdef WIN32 if (unicodeConsole) { if (!SetConsoleCP(CP_UTF8)) { BOX_ERROR("Failed to set input codepage: " << GetErrorMessage(GetLastError())); } if (!SetConsoleOutputCP(CP_UTF8)) { BOX_ERROR("Failed to set output codepage: " << GetErrorMessage(GetLastError())); } // enable input of Unicode characters if (_fileno(stdin) != -1 && _setmode(_fileno(stdin), _O_TEXT) == -1) { perror("Failed to set the console input to " "binary mode"); } } #endif // WIN32 // Read in the configuration file if(!quiet) BOX_INFO("Using configuration file " << configFilename); std::string errs; std::auto_ptr config( Configuration::LoadAndVerify (configFilename, &BackupDaemonConfigVerify, errs)); if(config.get() == 0 || !errs.empty()) { BOX_FATAL("Invalid configuration file: " << errs); return 1; } // Easier coding const Configuration &conf(*config); // Setup and connect // 1. TLS context SSLLib::Initialise(); // Read in the certificates creating a TLS context TLSContext tlsContext; std::string certFile(conf.GetKeyValue("CertificateFile")); std::string keyFile(conf.GetKeyValue("PrivateKeyFile")); std::string caFile(conf.GetKeyValue("TrustedCAsFile")); tlsContext.Initialise(false /* as client */, certFile.c_str(), keyFile.c_str(), caFile.c_str()); // Initialise keys BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); // 2. Connect to server if(!quiet) BOX_INFO("Connecting to store..."); SocketStreamTLS socket; socket.Open(tlsContext, Socket::TypeINET, conf.GetKeyValue("StoreHostname").c_str(), conf.GetKeyValueInt("StorePort")); // 3. Make a protocol, and handshake if(!quiet) BOX_INFO("Handshake with store..."); BackupProtocolClient connection(socket); connection.Handshake(); // logging? if(logFile != 0) { connection.SetLogToFile(logFile); } // 4. Log in to server if(!quiet) BOX_INFO("Login to store..."); // Check the version of the server { std::auto_ptr serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION)); if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION) { THROW_EXCEPTION(BackupStoreException, WrongServerVersion) } } // Login -- if this fails, the Protocol will exception connection.QueryLogin(conf.GetKeyValueInt("AccountNumber"), (readWrite)?0:(BackupProtocolClientLogin::Flags_ReadOnly)); // 5. Tell user. if(!quiet) printf("Login complete.\n\nType \"help\" for a list of commands.\n\n"); // Set up a context for our work BackupQueries context(connection, conf, readWrite); // Start running commands... first from the command line { int c = 0; while(c < argc && !context.Stop()) { context.DoCommand(argv[c++], true); } } // Get commands from input #ifdef HAVE_LIBREADLINE // Must initialise the locale before using editline's readline(), // otherwise cannot enter international characters. if (setlocale(LC_ALL, "") == NULL) { BOX_ERROR("Failed to initialise locale. International " "character support may not work."); } #ifdef HAVE_READLINE_HISTORY using_history(); #endif char *last_cmd = 0; while(!context.Stop()) { char *command = readline("query > "); if(command == NULL) { // Ctrl-D pressed -- terminate now break; } context.DoCommand(command, false); if(last_cmd != 0 && ::strcmp(last_cmd, command) == 0) { free(command); } else { #ifdef HAVE_READLINE_HISTORY add_history(command); #else free(last_cmd); #endif last_cmd = command; } } #ifndef HAVE_READLINE_HISTORY free(last_cmd); last_cmd = 0; #endif #else // Version for platforms which don't have readline by default if(fileno(stdin) >= 0) { FdGetLine getLine(fileno(stdin)); while(!context.Stop()) { printf("query > "); fflush(stdout); std::string command(getLine.GetLine()); context.DoCommand(command.c_str(), false); } } #endif // Done... stop nicely if(!quiet) BOX_INFO("Logging off..."); connection.QueryFinished(); if(!quiet) BOX_INFO("Session finished."); // Return code returnCode = context.GetReturnCode(); // Close log file? if(logFile) { ::fclose(logFile); } // Let everything be cleaned up on exit. #ifdef WIN32 // Clean up our sockets // FIXME we should do this, but I get an abort() when I try // WSACleanup(); #endif MAINHELPER_END return returnCode; } boxbackup/bin/bbackupobjdump/0000775000175000017500000000000011652362374017063 5ustar siretartsiretartboxbackup/bin/bbackupobjdump/bbackupobjdump.cpp0000664000175000017500000000353111165365160022554 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: bbackupobjdump.cpp // Purpose: Dump contents of backup objects // Created: 3/5/04 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "MainHelper.h" #include "FileStream.h" #include "BackupStoreDirectory.h" #include "BackupStoreFile.h" #include "BackupStoreObjectMagic.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: int main(int, const char *[]) // Purpose: Main fn for bbackupobjdump // Created: 3/5/04 // // -------------------------------------------------------------------------- int main(int argc, const char *argv[]) { MAINHELPER_START if(argc != 2) { ::printf("Input file not specified.\nUsage: bbackupobjdump \n"); return 1; } // Open file FileStream file(argv[1]); // Read magic number uint32_t signature; if(file.Read(&signature, sizeof(signature)) != sizeof(signature)) { // Too short, can't read signature from it return false; } // Seek back to beginning file.Seek(0, IOStream::SeekType_Absolute); // Then... check depending on the type switch(ntohl(signature)) { case OBJECTMAGIC_FILE_MAGIC_VALUE_V1: #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE case OBJECTMAGIC_FILE_MAGIC_VALUE_V0: #endif BackupStoreFile::DumpFile(stdout, false, file); break; case OBJECTMAGIC_DIR_MAGIC_VALUE: { BackupStoreDirectory dir; dir.ReadFromStream(file, IOStream::TimeOutInfinite); dir.Dump(stdout, false); if(dir.CheckAndFix()) { ::printf("Directory didn't pass checking\n"); } } break; default: ::printf("File does not appear to be a valid box backup object.\n"); break; } MAINHELPER_END } boxbackup/bin/bbackupd/0000775000175000017500000000000011652362374015646 5ustar siretartsiretartboxbackup/bin/bbackupd/BackupClientDirectoryRecord.h0000664000175000017500000001226011126430530023372 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientDirectoryRecord.h // Purpose: Implementation of record about directory for backup client // Created: 2003/10/08 // // -------------------------------------------------------------------------- #ifndef BACKUPCLIENTDIRECTORYRECORD__H #define BACKUPCLIENTDIRECTORYRECORD__H #include #include #include "BackupClientFileAttributes.h" #include "BackupDaemonInterface.h" #include "BackupStoreDirectory.h" #include "BoxTime.h" #include "MD5Digest.h" #include "ReadLoggingStream.h" #include "RunStatusProvider.h" class Archive; class BackupClientContext; class BackupDaemon; // -------------------------------------------------------------------------- // // Class // Name: BackupClientDirectoryRecord // Purpose: Implementation of record about directory for backup client // Created: 2003/10/08 // // -------------------------------------------------------------------------- class BackupClientDirectoryRecord { public: BackupClientDirectoryRecord(int64_t ObjectID, const std::string &rSubDirName); ~BackupClientDirectoryRecord(); void Deserialize(Archive & rArchive); void Serialize(Archive & rArchive) const; private: BackupClientDirectoryRecord(const BackupClientDirectoryRecord &); public: enum { UnknownDirectoryID = 0 }; // -------------------------------------------------------------------------- // // Class // Name: BackupClientDirectoryRecord::SyncParams // Purpose: Holds parameters etc for directory syncing. Not passed as // const, some parameters may be modified during sync. // Created: 8/3/04 // // -------------------------------------------------------------------------- class SyncParams : public ReadLoggingStream::Logger { public: SyncParams( RunStatusProvider &rRunStatusProvider, SysadminNotifier &rSysadminNotifier, ProgressNotifier &rProgressNotifier, BackupClientContext &rContext); ~SyncParams(); private: // No copying SyncParams(const SyncParams&); SyncParams &operator=(const SyncParams&); public: // Data members are public, as accessors are not justified here box_time_t mSyncPeriodStart; box_time_t mSyncPeriodEnd; box_time_t mMaxUploadWait; box_time_t mMaxFileTimeInFuture; int32_t mFileTrackingSizeThreshold; int32_t mDiffingUploadSizeThreshold; RunStatusProvider &mrRunStatusProvider; SysadminNotifier &mrSysadminNotifier; ProgressNotifier &mrProgressNotifier; BackupClientContext &mrContext; bool mReadErrorsOnFilesystemObjects; // Member variables modified by syncing process box_time_t mUploadAfterThisTimeInTheFuture; bool mHaveLoggedWarningAboutFutureFileTimes; bool StopRun() { return mrRunStatusProvider.StopRun(); } void NotifySysadmin(SysadminNotifier::EventCode Event) { mrSysadminNotifier.NotifySysadmin(Event); } ProgressNotifier& GetProgressNotifier() const { return mrProgressNotifier; } /* ReadLoggingStream::Logger implementation */ virtual void Log(int64_t readSize, int64_t offset, int64_t length, box_time_t elapsed, box_time_t finish) { mrProgressNotifier.NotifyReadProgress(readSize, offset, length, elapsed, finish); } virtual void Log(int64_t readSize, int64_t offset, int64_t length) { mrProgressNotifier.NotifyReadProgress(readSize, offset, length); } virtual void Log(int64_t readSize, int64_t offset) { mrProgressNotifier.NotifyReadProgress(readSize, offset); } }; void SyncDirectory(SyncParams &rParams, int64_t ContainingDirectoryID, const std::string &rLocalPath, const std::string &rRemotePath, bool ThisDirHasJustBeenCreated = false); private: void DeleteSubDirectories(); BackupStoreDirectory *FetchDirectoryListing(SyncParams &rParams); void UpdateAttributes(SyncParams &rParams, BackupStoreDirectory *pDirOnStore, const std::string &rLocalPath); bool UpdateItems(SyncParams &rParams, const std::string &rLocalPath, const std::string &rRemotePath, BackupStoreDirectory *pDirOnStore, std::vector &rEntriesLeftOver, std::vector &rFiles, const std::vector &rDirs); int64_t UploadFile(SyncParams &rParams, const std::string &rFilename, const BackupStoreFilename &rStoreFilename, int64_t FileSize, box_time_t ModificationTime, box_time_t AttributesHash, bool NoPreviousVersionOnServer); void SetErrorWhenReadingFilesystemObject(SyncParams &rParams, const char *Filename); void RemoveDirectoryInPlaceOfFile(SyncParams &rParams, BackupStoreDirectory* pDirOnStore, BackupStoreDirectory::Entry* pEntry, const std::string &rFilename); private: int64_t mObjectID; std::string mSubDirName; bool mInitialSyncDone; bool mSyncDone; // Checksum of directory contents and attributes, used to detect changes uint8_t mStateChecksum[MD5Digest::DigestLength]; std::map *mpPendingEntries; std::map mSubDirectories; // mpPendingEntries is a pointer rather than simple a member // variable, because most of the time it'll be empty. This would // waste a lot of memory because of STL allocation policies. }; #endif // BACKUPCLIENTDIRECTORYRECORD__H boxbackup/bin/bbackupd/win32/0000775000175000017500000000000011652362374016610 5ustar siretartsiretartboxbackup/bin/bbackupd/win32/NotifySysAdmin.vbs0000664000175000017500000000715611106152504022237 0ustar siretartsiretartDim 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 boxbackup/bin/bbackupd/win32/installer.iss0000664000175000017500000000514210347400657021324 0ustar siretartsiretart; 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 boxbackup/bin/bbackupd/win32/bbackupd.conf0000664000175000017500000002116611064031072021221 0ustar siretartsiretart 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\ } } boxbackup/bin/bbackupd/bbackupd-config.in0000775000175000017500000004017711167477470021235 0ustar siretartsiretart#!@PERL@ use strict; # should be running as root if($> != 0) { printf "\nWARNING: this should be run as root\n\n" } sub error_print_usage { print <<__E; Setup bbackupd config utility. Bad command line parameters. Usage: bbackupd-config config-dir backup-mode account-num server-hostname working-dir [backup directories] Parameters: config-dir is usually @sysconfdir_expanded@/boxbackup backup-mode is lazy or snapshot: lazy mode runs continously, uploading files over a specified age snapshot mode uploads a snapshot of the filesystem when instructed explicitly, using bbackupctl sync account-num (hexdecimal) and server-hostname are supplied by the server administrator working-dir is usually @localstatedir_expanded@/bbackupd backup directories is list of directories to back up __E print "=========\nERROR:\n",$_[0],"\n\n" if $_[0] ne ''; exit(1); } # check and get command line parameters if($#ARGV < 4) { error_print_usage(); } # check for OPENSSL_CONF environment var being set if(exists $ENV{'OPENSSL_CONF'}) { print <<__E; --------------------------------------- WARNING: You have the OPENSSL_CONF environment variable set. Use of non-standard openssl configs may cause problems. --------------------------------------- __E } # default locations my $default_config_location = '@sysconfdir_expanded@/boxbackup/bbackupd.conf'; # command line parameters my ($config_dir,$backup_mode,$account_num,$server,$working_dir,@tobackup) = @ARGV; # check backup mode is valid if($backup_mode ne 'lazy' && $backup_mode ne 'snapshot') { error_print_usage("ERROR: backup mode must be 'lazy' or 'snapshot'"); } # check server exists { my @r = gethostbyname($server); if($#r < 0) { error_print_usage("Backup server specified as '$server', but it could not found.\n(A test DNS lookup failed -- check arguments)"); } } if($working_dir !~ m~\A/~) { error_print_usage("Working directory $working_dir is not specified as an absolute path"); } # ssl stuff my $private_key = "$config_dir/bbackupd/$account_num-key.pem"; my $certificate_request = "$config_dir/bbackupd/$account_num-csr.pem"; my $certificate = "$config_dir/bbackupd/$account_num-cert.pem"; my $ca_root_cert = "$config_dir/bbackupd/serverCA.pem"; # encryption keys my $enc_key_file = "$config_dir/bbackupd/$account_num-FileEncKeys.raw"; # other files my $config_file = "$config_dir/bbackupd.conf"; my $notify_script = "$config_dir/bbackupd/NotifySysadmin.sh"; # check that the directories are allowable for(@tobackup) { if($_ eq '/') { die "It is not recommended that you backup the root directory of your disc"; } if($_ !~ m/\A\//) { die "Directory $_ is not specified as an absolute path"; } if(!-d $_) { die "$_ is not a directory"; } } # summarise configuration print <<__E; Setup bbackupd config utility. Configuration: Writing configuration file: $config_file Account: $account_num Server hostname: $server Directories to back up: __E print ' ',$_,"\n" for(@tobackup); print <<__E; Note: If other file systems are mounted inside these directories, then they will NOT be backed up. You will have to create separate locations for any mounted filesystems inside your backup locations. __E # create directories if(!-d $config_dir) { printf "Creating $config_dir...\n"; mkdir $config_dir,0755 or die "Can't create $config_dir"; } if(!-d "$config_dir/bbackupd") { printf "Creating $config_dir/bbackupd\n"; mkdir "$config_dir/bbackupd",0700 or die "Can't create $config_dir/bbackupd"; } if(!-d "$working_dir") { printf "Creating $working_dir\n"; if(!mkdir($working_dir,0700)) { die "Couldn't create $working_dir -- create this manually and try again\n"; } } # generate the private key for the server if(!-f $private_key) { print "Generating private key...\n"; if(system("openssl genrsa -out $private_key 2048") != 0) { die "Couldn't generate private key." } } # generate a certificate request if(!-f $certificate_request) { die "Couldn't run openssl for CSR generation" unless open(CSR,"|openssl req -new -key $private_key -sha1 -out $certificate_request"); print CSR <<__E; . . . . . BACKUP-$account_num . . . __E close CSR; print "\n\n"; die "Certificate request wasn't created.\n" unless -f $certificate_request } # generate the key material for the file if(!-f $enc_key_file) { print "Generating keys for file backup\n"; if(system("openssl rand -out $enc_key_file 1024") != 0) { die "Couldn't generate file backup keys." } } # write the notify when store full script print "Writing notify script $notify_script\n"; open NOTIFY,">$notify_script" or die "Can't open for writing"; my $hostname = `hostname`; chomp $hostname; my $current_username = `whoami`; chomp $current_username; my $sendmail = `whereis sendmail`; chomp $sendmail; $sendmail =~ s/\n.\Z//s; # for Linux style whereis $sendmail = $1 if $sendmail =~ /^sendmail:\s+([\S]+)/; # last ditch guess $sendmail = 'sendmail' if $sendmail !~ m/\S/; print NOTIFY <<__EOS; #!/bin/sh # This script is run whenever bbackupd changes state or encounters a # problem which requires the system administrator to assist: # # 1) The store is full, and no more data can be uploaded. # 2) Some files or directories were not readable. # 3) A backup run starts or finishes. # # The default script emails the system administrator, except for backups # starting and stopping, where it does nothing. SUBJECT="BACKUP PROBLEM on host $hostname" SENDTO="$current_username" if [ "\$1" = "" ]; then echo "Usage: \$0 " >&2 exit 2 elif [ "\$1" = store-full ]; then $sendmail \$SENDTO <$config_file" or die "Can't open config file for writing"; print CONFIG <<__E; StoreHostname = $server AccountNumber = 0x$account_num KeysFile = $enc_key_file CertificateFile = $certificate PrivateKeyFile = $private_key TrustedCAsFile = $ca_root_cert DataDirectory = $working_dir # This script is run whenever bbackupd changes state or encounters a # problem which requires the system administrator to assist: # # 1) The store is full, and no more data can be uploaded. # 2) Some files or directories were not readable. # 3) A backup run starts or finishes. # # The default script emails the system administrator, except for backups # starting and stopping, where it does nothing. NotifyScript = $notify_script __E if($backup_mode eq 'lazy') { # lazy mode configuration print CONFIG <<__E; # The number of seconds between backup runs under normal conditions. To avoid # cycles of load on the server, this time is randomly adjusted by a small # percentage as the daemon runs. UpdateStoreInterval = 3600 # The minimum age of a file, in seconds, that will be uploaded. Avoids # repeated uploads of a file which is constantly being modified. MinimumFileAge = 21600 # If a file is modified repeated, it won't be uploaded immediately in case # it's modified again, due to the MinimumFileAge specified above. However, it # should be uploaded eventually even if it is being modified repeatedly. This # is how long we should wait, in seconds, after first noticing a change. # (86400 seconds = 1 day) MaxUploadWait = 86400 # If the connection is idle for some time (e.g. over 10 minutes or 600 # seconds, not sure exactly how long) then the server will give up and # disconnect the client, resulting in Connection Protocol_Timeout errors # on the server and TLSReadFailed or TLSWriteFailed errors on the client. # Also, some firewalls and NAT gateways will kill idle connections after # similar lengths of time. # # This can happen for example when most files are backed up already and # don't need to be sent to the store again, while scanning a large # directory, or while calculating diffs of a large file. To avoid this, # KeepAliveTime specifies that special keep-alive messages should be sent # when the connection is otherwise idle for a certain length of time, # specified here in seconds. # # The default is that these messages are never sent, equivalent to setting # this option to zero, but we recommend that all users enable this. KeepAliveTime = 120 __E } else { # snapshot configuration print CONFIG <<__E; # This configuration file is written for snapshot mode. # You will need to run bbackupctl to instruct the daemon to upload files. AutomaticBackup = no UpdateStoreInterval = 0 MinimumFileAge = 0 MaxUploadWait = 0 __E } print CONFIG <<__E; # Files above this size (in bytes) are tracked, and if they are renamed they will simply be # renamed on the server, rather than being uploaded again. (64k - 1) FileTrackingSizeThreshold = 65535 # The daemon does "changes only" uploads for files above this size (in bytes). # Files less than it are uploaded whole without this extra processing. DiffingUploadSizeThreshold = 8192 # The limit on how much time is spent diffing files, in seconds. Most files # shouldn't take very long, but if you have really big files you can use this # to limit the time spent diffing them. # # * Reduce if you are having problems with processor usage. # # * Increase if you have large files, and think the upload of changes is too # large and you want bbackupd to spend more time searching for unchanged # blocks. MaximumDiffingTime = 120 # Uncomment this line to see exactly what the daemon is going when it's connected to the server. # ExtendedLogging = yes # This specifies a program or script script which is run just before each # sync, and ideally the full path to the interpreter. It will be run as the # same user bbackupd is running as, usually root. # # The script must output (print) either "now" or a number to STDOUT (and a # terminating newline, no quotes). # # If the result was "now", then the sync will happen. If it's a number, then # no backup will happen for that number of seconds (bbackupd will pause) and # then the script will be run again. # # Use this to temporarily stop bbackupd from syncronising or connecting to the # store. For example, you could use this on a laptop to only backup when on a # specific network, or when it has a working Internet connection. # SyncAllowScript = /path/to/intepreter/or/exe script-name parameters etc # Where the command socket is created in the filesystem. CommandSocket = $working_dir/bbackupd.sock # Uncomment the StoreObjectInfoFile to enable the experimental archiving # of the daemon's state (including client store marker and configuration) # between backup runs. This saves time and increases efficiency when # bbackupd is frequently stopped and started, since it removes the need # to rescan all directories on the remote server. However, it is new and # not yet heavily tested, so use with caution. # StoreObjectInfoFile = $working_dir/bbackupd.state Server { PidFile = $working_dir/bbackupd.pid } # BackupLocations specifies which locations on disc should be backed up. Each # directory is in the format # # name # { # Path = /path/of/directory # (optional exclude directives) # } # # 'name' is derived from the Path by the config script, but should merely be # unique. # # The exclude directives are of the form # # [Exclude|AlwaysInclude][File|Dir][|sRegex] = regex or full pathname # # (The regex suffix is shown as 'sRegex' to make File or Dir plural) # # For example: # # ExcludeDir = /home/guest-user # ExcludeFilesRegex = \.(mp3|MP3)\$ # AlwaysIncludeFile = /home/username/veryimportant.mp3 # # This excludes the directory /home/guest-user from the backup along with all mp3 # files, except one MP3 file in particular. # # In general, Exclude excludes a file or directory, unless the directory is # explicitly mentioned in a AlwaysInclude directive. However, Box Backup # does NOT scan inside excluded directories and will never back up an # AlwaysIncluded file or directory inside an excluded directory or any # subdirectory thereof. # # To back up a directory inside an excluded directory, use a configuration # like this, to ensure that each directory in the path to the important # files is included, but none of their contents will be backed up except # the directories further down that path to the important one. # # ExcludeDirsRegex = ^/home/user/bigfiles/ # ExcludeFilesRegex = ^/home/user/bigfiles/ # AlwaysIncludeDir = /home/user/bigfiles/path # AlwaysIncludeDir = /home/user/bigfiles/path/to # AlwaysIncludeDir = /home/user/bigfiles/path/important # AlwaysIncludeDir = /home/user/bigfiles/path/important/files # AlwaysIncludeDirsRegex = ^/home/user/bigfiles/path/important/files/ # AlwaysIncludeFilesRegex = ^/home/user/bigfiles/path/important/files/ # # If a directive ends in Regex, then it is a regular expression rather than a # explicit full pathname. See # # man 7 re_format # # for the regex syntax on your platform. BackupLocations { __E # write the dirs to backup for my $d (@tobackup) { $d =~ m/\A.(.+)\Z/; my $n = $1; $n =~ tr`/`-`; my $excludekeys = ''; if(substr($enc_key_file, 0, length($d)+1) eq $d.'/') { $excludekeys = "\t\tExcludeFile = $enc_key_file\n"; print <<__E; NOTE: Keys file has been explicitly excluded from the backup. __E } print CONFIG <<__E $n { Path = $d $excludekeys } __E } print CONFIG "}\n\n"; close CONFIG; # explain to the user what they need to do next my $daemon_args = ($config_file eq $default_config_location)?'':" $config_file"; my $ctl_daemon_args = ($config_file eq $default_config_location)?'':" -c $config_file"; print <<__E; =================================================================== bbackupd basic configuration complete. What you need to do now... 1) Make a backup of $enc_key_file This should be a secure offsite backup. Without it, you cannot restore backups. Everything else can be replaced. But this cannot. KEEP IT IN A SAFE PLACE, OTHERWISE YOUR BACKUPS ARE USELESS. 2) Send $certificate_request to the administrator of the backup server, and ask for it to be signed. 3) The administrator will send you two files. Install them as $certificate $ca_root_cert after checking their authenticity. 4) You may wish to read the configuration file $config_file and adjust as appropriate. There are some notes in it on excluding files you do not wish to be backed up. 5) Review the script $notify_script and check that it will email the right person when the store becomes full. This is important -- when the store is full, no more files will be backed up. You want to know about this. 6) Start the backup daemon with the command @sbindir_expanded@/bbackupd$daemon_args in /etc/rc.local, or your local equivalent. Note that bbackupd must run as root. __E if($backup_mode eq 'snapshot') { print <<__E; 7) Set up a cron job to run whenever you want a snapshot of the file system to be taken. Run the command @bindir_expanded@/bbackupctl -q$ctl_daemon_args sync __E } print <<__E; =================================================================== Remember to make a secure, offsite backup of your backup keys, as described in step 1 above. If you do not, you have no backups. __E boxbackup/bin/bbackupd/BackupClientContext.cpp0000664000175000017500000003461311436002351022254 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientContext.cpp // Purpose: Keep track of context // Created: 2003/10/08 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_SIGNAL_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #include "BoxPortsAndFiles.h" #include "BoxTime.h" #include "BackupClientContext.h" #include "SocketStreamTLS.h" #include "Socket.h" #include "BackupStoreConstants.h" #include "BackupStoreException.h" #include "BackupDaemon.h" #include "autogen_BackupProtocolClient.h" #include "BackupStoreFile.h" #include "Logging.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::BackupClientContext(BackupDaemon &, TLSContext &, const std::string &, int32_t, bool, bool, std::string) // Purpose: Constructor // Created: 2003/10/08 // // -------------------------------------------------------------------------- BackupClientContext::BackupClientContext ( LocationResolver &rResolver, TLSContext &rTLSContext, const std::string &rHostname, int Port, uint32_t AccountNumber, bool ExtendedLogging, bool ExtendedLogToFile, std::string ExtendedLogFile, ProgressNotifier& rProgressNotifier ) : mrResolver(rResolver), mrTLSContext(rTLSContext), mHostname(rHostname), mPort(Port), mAccountNumber(AccountNumber), mpSocket(0), mpConnection(0), mExtendedLogging(ExtendedLogging), mExtendedLogToFile(ExtendedLogToFile), mExtendedLogFile(ExtendedLogFile), mpExtendedLogFileHandle(NULL), mClientStoreMarker(ClientStoreMarker_NotKnown), mpDeleteList(0), mpCurrentIDMap(0), mpNewIDMap(0), mStorageLimitExceeded(false), mpExcludeFiles(0), mpExcludeDirs(0), mKeepAliveTimer(0, "KeepAliveTime"), mbIsManaged(false), mrProgressNotifier(rProgressNotifier) { } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::~BackupClientContext() // Purpose: Destructor // Created: 2003/10/08 // // -------------------------------------------------------------------------- BackupClientContext::~BackupClientContext() { CloseAnyOpenConnection(); // Delete delete list if(mpDeleteList != 0) { delete mpDeleteList; mpDeleteList = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::GetConnection() // Purpose: Returns the connection, making the connection and logging into // the backup store if necessary. // Created: 2003/10/08 // // -------------------------------------------------------------------------- BackupProtocolClient &BackupClientContext::GetConnection() { // Already got it? Just return it. if(mpConnection != 0) { return *mpConnection; } // Get a socket connection if(mpSocket == 0) { mpSocket = new SocketStreamTLS; ASSERT(mpSocket != 0); // will have exceptioned if this was a problem } try { // Defensive. if(mpConnection != 0) { delete mpConnection; mpConnection = 0; } // Log intention BOX_INFO("Opening connection to server '" << mHostname << "'..."); // Connect! mpSocket->Open(mrTLSContext, Socket::TypeINET, mHostname.c_str(), mPort); // And create a procotol object mpConnection = new BackupProtocolClient(*mpSocket); // Set logging option mpConnection->SetLogToSysLog(mExtendedLogging); if (mExtendedLogToFile) { ASSERT(mpExtendedLogFileHandle == NULL); mpExtendedLogFileHandle = fopen( mExtendedLogFile.c_str(), "a+"); if (!mpExtendedLogFileHandle) { BOX_LOG_SYS_ERROR("Failed to open extended " "log file: " << mExtendedLogFile); } else { mpConnection->SetLogToFile(mpExtendedLogFileHandle); } } // Handshake mpConnection->Handshake(); // Check the version of the server { std::auto_ptr serverVersion(mpConnection->QueryVersion(BACKUP_STORE_SERVER_VERSION)); if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION) { THROW_EXCEPTION(BackupStoreException, WrongServerVersion) } } // Login -- if this fails, the Protocol will exception std::auto_ptr loginConf(mpConnection->QueryLogin(mAccountNumber, 0 /* read/write */)); // Check that the client store marker is the one we expect if(mClientStoreMarker != ClientStoreMarker_NotKnown) { if(loginConf->GetClientStoreMarker() != mClientStoreMarker) { // Not good... finish the connection, abort, etc, ignoring errors try { mpConnection->QueryFinished(); mpSocket->Shutdown(); mpSocket->Close(); } catch(...) { // IGNORE } // Then throw an exception about this THROW_EXCEPTION(BackupStoreException, ClientMarkerNotAsExpected) } } // Log success BOX_INFO("Connection made, login successful"); // Check to see if there is any space available on the server if(loginConf->GetBlocksUsed() >= loginConf->GetBlocksHardLimit()) { // no -- flag so only things like deletions happen mStorageLimitExceeded = true; // Log BOX_WARNING("Exceeded storage hard-limit on server, " "not uploading changes to files"); } } catch(...) { // Clean up. delete mpConnection; mpConnection = 0; delete mpSocket; mpSocket = 0; throw; } return *mpConnection; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::CloseAnyOpenConnection() // Purpose: Closes a connection, if it's open // Created: 2003/10/08 // // -------------------------------------------------------------------------- void BackupClientContext::CloseAnyOpenConnection() { if(mpConnection) { try { // Need to set a client store marker? if(mClientStoreMarker == ClientStoreMarker_NotKnown) { // Yes, choose one, the current time will do box_time_t marker = GetCurrentBoxTime(); // Set it on the store mpConnection->QuerySetClientStoreMarker(marker); // Record it so that it can be picked up later. mClientStoreMarker = marker; } // Quit nicely mpConnection->QueryFinished(); } catch(...) { // Ignore errors here } // Delete it anyway. delete mpConnection; mpConnection = 0; } if(mpSocket) { try { // Be nice about closing the socket mpSocket->Shutdown(); mpSocket->Close(); } catch(...) { // Ignore errors } // Delete object delete mpSocket; mpSocket = 0; } // Delete any pending list if(mpDeleteList != 0) { delete mpDeleteList; mpDeleteList = 0; } if (mpExtendedLogFileHandle != NULL) { fclose(mpExtendedLogFileHandle); mpExtendedLogFileHandle = NULL; } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::GetTimeout() // Purpose: Gets the current timeout time. // Created: 2003/10/08 // // -------------------------------------------------------------------------- int BackupClientContext::GetTimeout() const { if(mpConnection) { return mpConnection->GetTimeout(); } return (15*60*1000); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::GetDeleteList() // Purpose: Returns the delete list, creating one if necessary // Created: 10/11/03 // // -------------------------------------------------------------------------- BackupClientDeleteList &BackupClientContext::GetDeleteList() { // Already created? if(mpDeleteList == 0) { mpDeleteList = new BackupClientDeleteList; } // Return reference to object return *mpDeleteList; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::PerformDeletions() // Purpose: Perform any pending file deletions. // Created: 10/11/03 // // -------------------------------------------------------------------------- void BackupClientContext::PerformDeletions() { // Got a list? if(mpDeleteList == 0) { // Nothing to do return; } // Delegate to the delete list object mpDeleteList->PerformDeletions(*this); // Delete the object delete mpDeleteList; mpDeleteList = 0; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::GetCurrentIDMap() const // Purpose: Return a (const) reference to the current ID map // Created: 11/11/03 // // -------------------------------------------------------------------------- const BackupClientInodeToIDMap &BackupClientContext::GetCurrentIDMap() const { ASSERT(mpCurrentIDMap != 0); if(mpCurrentIDMap == 0) { THROW_EXCEPTION(CommonException, Internal) } return *mpCurrentIDMap; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::GetNewIDMap() const // Purpose: Return a reference to the new ID map // Created: 11/11/03 // // -------------------------------------------------------------------------- BackupClientInodeToIDMap &BackupClientContext::GetNewIDMap() const { ASSERT(mpNewIDMap != 0); if(mpNewIDMap == 0) { THROW_EXCEPTION(CommonException, Internal) } return *mpNewIDMap; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::FindFilename(int64_t, int64_t, std::string &, bool &) const // Purpose: Attempts to find the pathname of an object with a given ID on the server. // Returns true if it can be found, in which case rPathOut is the local filename, // and rIsDirectoryOut == true if the local object is a directory. // Created: 12/11/03 // // -------------------------------------------------------------------------- bool BackupClientContext::FindFilename(int64_t ObjectID, int64_t ContainingDirectory, std::string &rPathOut, bool &rIsDirectoryOut, bool &rIsCurrentVersionOut, box_time_t *pModTimeOnServer, box_time_t *pAttributesHashOnServer, BackupStoreFilenameClear *pLeafname) { // Make a connection to the server BackupProtocolClient &connection(GetConnection()); // Request filenames from the server, in a "safe" manner to ignore errors properly { BackupProtocolClientGetObjectName send(ObjectID, ContainingDirectory); connection.Send(send); } std::auto_ptr preply(connection.Receive()); // Is it of the right type? if(preply->GetType() != BackupProtocolClientObjectName::TypeID) { // Was an error or something return false; } // Cast to expected type. BackupProtocolClientObjectName *names = (BackupProtocolClientObjectName *)(preply.get()); // Anything found? int32_t numElements = names->GetNumNameElements(); if(numElements <= 0) { // No. return false; } // Get the stream containing all the names std::auto_ptr nameStream(connection.ReceiveStream()); // Path std::string path; // Remember this is in reverse order! for(int l = 0; l < numElements; ++l) { BackupStoreFilenameClear elementName; elementName.ReadFromStream(*nameStream, GetTimeout()); // Store leafname for caller? if(l == 0 && pLeafname) { *pLeafname = elementName; } // Is it part of the filename in the location? if(l < (numElements - 1)) { // Part of filename within path = (path.empty())?(elementName.GetClearFilename()):(elementName.GetClearFilename() + DIRECTORY_SEPARATOR_ASCHAR + path); } else { // Location name -- look up in daemon's records std::string locPath; if(!mrResolver.FindLocationPathName(elementName.GetClearFilename(), locPath)) { // Didn't find the location... so can't give the local filename return false; } // Add in location path path = (path.empty())?(locPath):(locPath + DIRECTORY_SEPARATOR_ASCHAR + path); } } // Is it a directory? rIsDirectoryOut = ((names->GetFlags() & BackupProtocolClientListDirectory::Flags_Dir) == BackupProtocolClientListDirectory::Flags_Dir); // Is it the current version? rIsCurrentVersionOut = ((names->GetFlags() & (BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted)) == 0); // And other information which may be required if(pModTimeOnServer) *pModTimeOnServer = names->GetModificationTime(); if(pAttributesHashOnServer) *pAttributesHashOnServer = names->GetAttributesHash(); // Tell caller about the pathname rPathOut = path; // Found return true; } void BackupClientContext::SetMaximumDiffingTime(int iSeconds) { mMaximumDiffingTime = iSeconds < 0 ? 0 : iSeconds; BOX_TRACE("Set maximum diffing time to " << mMaximumDiffingTime << " seconds"); } void BackupClientContext::SetKeepAliveTime(int iSeconds) { mKeepAliveTime = iSeconds < 0 ? 0 : iSeconds; BOX_TRACE("Set keep-alive time to " << mKeepAliveTime << " seconds"); mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime"); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::ManageDiffProcess() // Purpose: Initiates a file diff control timer // Created: 04/19/2005 // // -------------------------------------------------------------------------- void BackupClientContext::ManageDiffProcess() { ASSERT(!mbIsManaged); mbIsManaged = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::UnManageDiffProcess() // Purpose: suspends file diff control timer // Created: 04/19/2005 // // -------------------------------------------------------------------------- void BackupClientContext::UnManageDiffProcess() { // ASSERT(mbIsManaged); mbIsManaged = false; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::DoKeepAlive() // Purpose: Check whether it's time to send a KeepAlive // message over the SSL link, and if so, send it. // Created: 04/19/2005 // // -------------------------------------------------------------------------- void BackupClientContext::DoKeepAlive() { if (!mpConnection) { return; } if (mKeepAliveTime == 0) { return; } if (!mKeepAliveTimer.HasExpired()) { return; } BOX_TRACE("KeepAliveTime reached, sending keep-alive message"); mpConnection->QueryGetIsAlive(); mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime"); } int BackupClientContext::GetMaximumDiffingTime() { return mMaximumDiffingTime; } boxbackup/bin/bbackupd/BackupClientInodeToIDMap.h0000664000175000017500000000362410347400657022522 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientInodeToIDMap.h // Purpose: Map of inode numbers to file IDs on the store // Created: 11/11/03 // // -------------------------------------------------------------------------- #ifndef BACKUPCLIENTINODETOIDMAP_H #define BACKUPCLIENTINODETOIDMAP__H #include #include #include // Use in memory implementation if there isn't access to the Berkely DB on this platform #ifndef HAVE_DB #define BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION #endif // avoid having to include the DB files when not necessary #ifndef BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION #ifdef BERKELY_V4 class Db; #else class DB; #endif #endif // -------------------------------------------------------------------------- // // Class // Name: BackupClientInodeToIDMap // Purpose: Map of inode numbers to file IDs on the store // Created: 11/11/03 // // -------------------------------------------------------------------------- class BackupClientInodeToIDMap { public: BackupClientInodeToIDMap(); ~BackupClientInodeToIDMap(); private: BackupClientInodeToIDMap(const BackupClientInodeToIDMap &rToCopy); // not allowed public: void Open(const char *Filename, bool ReadOnly, bool CreateNew); void OpenEmpty(); void AddToMap(InodeRefType InodeRef, int64_t ObjectID, int64_t InDirectory); bool Lookup(InodeRefType InodeRef, int64_t &rObjectIDOut, int64_t &rInDirectoryOut) const; void Close(); private: #ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION std::map > mMap; #else bool mReadOnly; bool mEmpty; #ifdef BERKELY_V4 Db *dbp; // c++ style implimentation #else DB *dbp; // C style interface, use notation from documentation #endif // BERKELY_V4 #endif // BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION }; #endif // BACKUPCLIENTINODETOIDMAP__H boxbackup/bin/bbackupd/Win32ServiceFunctions.cpp0000664000175000017500000002120111161233163022446 0ustar siretartsiretart//*************************************************************** // From the book "Win32 System Services: The Heart of Windows 98 // and Windows 2000" // by Marshall Brain // Published by Prentice Hall // Copyright 1995 Prentice Hall. // // This code implements the Windows API Service interface // for the Box Backup for Windows native port. // Adapted for Box Backup by Nick Knight. //*************************************************************** #ifdef WIN32 #include "Box.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_PROCESS_H #include #endif extern void TerminateService(void); extern unsigned int WINAPI RunService(LPVOID lpParameter); // Global variables TCHAR* gServiceName = TEXT("Box Backup Service"); SERVICE_STATUS gServiceStatus; SERVICE_STATUS_HANDLE gServiceStatusHandle = 0; HANDLE gStopServiceEvent = 0; DWORD gServiceReturnCode = 0; #define SERVICE_NAME "boxbackup" void ShowMessage(char *s) { MessageBox(0, s, "Box Backup Message", MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY); } void ErrorHandler(char *s, DWORD err) { char buf[256]; memset(buf, 0, sizeof(buf)); _snprintf(buf, sizeof(buf)-1, "%s: %s", s, GetErrorMessage(err).c_str()); BOX_ERROR(buf); MessageBox(0, buf, "Error", MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY); ExitProcess(err); } void WINAPI ServiceControlHandler( DWORD controlCode ) { switch ( controlCode ) { case SERVICE_CONTROL_INTERROGATE: break; case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: Beep(1000,100); TerminateService(); gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus(gServiceStatusHandle, &gServiceStatus); SetEvent(gStopServiceEvent); return; case SERVICE_CONTROL_PAUSE: break; case SERVICE_CONTROL_CONTINUE: break; default: if ( controlCode >= 128 && controlCode <= 255 ) // user defined control code break; else // unrecognised control code break; } SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); } // ServiceMain is called when the SCM wants to // start the service. When it returns, the service // has stopped. It therefore waits on an event // just before the end of the function, and // that event gets set when it is time to stop. // It also returns on any error because the // service cannot start if there is an eror. static char* spConfigFileName; VOID ServiceMain(DWORD argc, LPTSTR *argv) { // initialise service status gServiceStatus.dwServiceType = SERVICE_WIN32; gServiceStatus.dwCurrentState = SERVICE_STOPPED; gServiceStatus.dwControlsAccepted = 0; gServiceStatus.dwWin32ExitCode = NO_ERROR; gServiceStatus.dwServiceSpecificExitCode = NO_ERROR; gServiceStatus.dwCheckPoint = 0; gServiceStatus.dwWaitHint = 0; gServiceStatusHandle = RegisterServiceCtrlHandler(gServiceName, ServiceControlHandler); if (gServiceStatusHandle) { // service is starting gServiceStatus.dwCurrentState = SERVICE_START_PENDING; SetServiceStatus(gServiceStatusHandle, &gServiceStatus); // do initialisation here gStopServiceEvent = CreateEvent(0, TRUE, FALSE, 0); if (!gStopServiceEvent) { gServiceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); gServiceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(gServiceStatusHandle, &gServiceStatus); return; } HANDLE ourThread = (HANDLE)_beginthreadex( NULL, 0, RunService, spConfigFileName, CREATE_SUSPENDED, NULL); SetThreadPriority(ourThread, THREAD_PRIORITY_LOWEST); ResumeThread(ourThread); // we are now running so tell the SCM gServiceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); gServiceStatus.dwCurrentState = SERVICE_RUNNING; SetServiceStatus(gServiceStatusHandle, &gServiceStatus); // do cleanup here WaitForSingleObject(gStopServiceEvent, INFINITE); CloseHandle(gStopServiceEvent); gStopServiceEvent = 0; // service was stopped gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus(gServiceStatusHandle, &gServiceStatus); // service is now stopped gServiceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); gServiceStatus.dwCurrentState = SERVICE_STOPPED; if (gServiceReturnCode != 0) { gServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; gServiceStatus.dwServiceSpecificExitCode = gServiceReturnCode; } SetServiceStatus(gServiceStatusHandle, &gServiceStatus); } } int OurService(const char* pConfigFileName) { spConfigFileName = strdup(pConfigFileName); SERVICE_TABLE_ENTRY serviceTable[] = { { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain }, { NULL, NULL } }; BOOL success; // Register with the SCM success = StartServiceCtrlDispatcher(serviceTable); free(spConfigFileName); spConfigFileName = NULL; if (!success) { ErrorHandler("Failed to start service. Did you start " "Box Backup from the Service Control Manager? " "(StartServiceCtrlDispatcher)", GetLastError()); return 1; } return 0; } int InstallService(const char* pConfigFileName, const std::string& rServiceName) { if (pConfigFileName != NULL) { EMU_STRUCT_STAT st; if (emu_stat(pConfigFileName, &st) != 0) { BOX_LOG_SYS_ERROR("Failed to open configuration file " "'" << pConfigFileName << "'"); return 1; } if (!(st.st_mode & S_IFREG)) { BOX_ERROR("Failed to open configuration file '" << pConfigFileName << "': not a file"); return 1; } } SC_HANDLE scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); if (!scm) { BOX_ERROR("Failed to open service control manager: " << GetErrorMessage(GetLastError())); return 1; } char cmd[MAX_PATH]; GetModuleFileName(NULL, cmd, sizeof(cmd)-1); cmd[sizeof(cmd)-1] = 0; std::string cmdWithArgs(cmd); cmdWithArgs += " -s -S \"" + rServiceName + "\""; if (pConfigFileName != NULL) { cmdWithArgs += " \""; cmdWithArgs += pConfigFileName; cmdWithArgs += "\""; } std::string serviceDesc = "Box Backup (" + rServiceName + ")"; SC_HANDLE newService = CreateService( scm, rServiceName.c_str(), serviceDesc.c_str(), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, cmdWithArgs.c_str(), 0,0,0,0,0); DWORD err = GetLastError(); CloseServiceHandle(scm); if (!newService) { switch (err) { case ERROR_SERVICE_EXISTS: { BOX_ERROR("Failed to create Box Backup " "service: it already exists"); } break; case ERROR_SERVICE_MARKED_FOR_DELETE: { BOX_ERROR("Failed to create Box Backup " "service: it is waiting to be deleted"); } break; case ERROR_DUPLICATE_SERVICE_NAME: { BOX_ERROR("Failed to create Box Backup " "service: a service with this name " "already exists"); } break; default: { BOX_ERROR("Failed to create Box Backup " "service: error " << GetErrorMessage(GetLastError())); } } return 1; } BOX_INFO("Created Box Backup service"); SERVICE_DESCRIPTION desc; desc.lpDescription = "Backs up your data files over the Internet"; if (!ChangeServiceConfig2(newService, SERVICE_CONFIG_DESCRIPTION, &desc)) { BOX_WARNING("Failed to set description for Box Backup " "service: " << GetErrorMessage(GetLastError())); } CloseServiceHandle(newService); return 0; } int RemoveService(const std::string& rServiceName) { SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); if (!scm) { BOX_ERROR("Failed to open service control manager: " << GetErrorMessage(GetLastError())); return 1; } SC_HANDLE service = OpenService(scm, rServiceName.c_str(), SERVICE_ALL_ACCESS|DELETE); DWORD err = GetLastError(); CloseServiceHandle(scm); if (!service) { if (err == ERROR_SERVICE_DOES_NOT_EXIST || err == ERROR_IO_PENDING) // hello microsoft? anyone home? { BOX_ERROR("Failed to open Box Backup service: " "not installed or not found"); } else { BOX_ERROR("Failed to open Box Backup service: " << GetErrorMessage(err)); } return 1; } SERVICE_STATUS status; if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) { err = GetLastError(); if (err != ERROR_SERVICE_NOT_ACTIVE) { BOX_WARNING("Failed to stop Box Backup service: " << GetErrorMessage(err)); } } BOOL deleted = DeleteService(service); err = GetLastError(); CloseServiceHandle(service); if (deleted) { BOX_INFO("Box Backup service deleted"); return 0; } else if (err == ERROR_SERVICE_MARKED_FOR_DELETE) { BOX_ERROR("Failed to remove Box Backup service: " "it is already being deleted"); } else { BOX_ERROR("Failed to remove Box Backup service: " << GetErrorMessage(err)); } return 1; } #endif // WIN32 boxbackup/bin/bbackupd/Win32BackupService.h0000664000175000017500000000055010475351646021372 0ustar siretartsiretart// 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 boxbackup/bin/bbackupd/BackupClientDirectoryRecord.cpp0000664000175000017500000015437511435772455023765 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientDirectoryRecord.cpp // Purpose: Implementation of record about directory for // backup client // Created: 2003/10/08 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_DIRENT_H #include #endif #include #include #include "BackupClientDirectoryRecord.h" #include "autogen_BackupProtocolClient.h" #include "BackupClientContext.h" #include "IOStream.h" #include "MemBlockStream.h" #include "CommonException.h" #include "CollectInBufferStream.h" #include "BackupStoreFile.h" #include "BackupClientInodeToIDMap.h" #include "FileModificationTime.h" #include "BackupDaemon.h" #include "BackupStoreException.h" #include "Archive.h" #include "PathUtils.h" #include "Logging.h" #include "ReadLoggingStream.h" #include "MemLeakFindOn.h" typedef std::map DecryptedEntriesMap_t; // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::BackupClientDirectoryRecord() // Purpose: Constructor // Created: 2003/10/08 // // -------------------------------------------------------------------------- BackupClientDirectoryRecord::BackupClientDirectoryRecord(int64_t ObjectID, const std::string &rSubDirName) : mObjectID(ObjectID), mSubDirName(rSubDirName), mInitialSyncDone(false), mSyncDone(false), mpPendingEntries(0) { ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::~BackupClientDirectoryRecord() // Purpose: Destructor // Created: 2003/10/08 // // -------------------------------------------------------------------------- BackupClientDirectoryRecord::~BackupClientDirectoryRecord() { // Make deletion recursive DeleteSubDirectories(); // Delete maps if(mpPendingEntries != 0) { delete mpPendingEntries; mpPendingEntries = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::DeleteSubDirectories(); // Purpose: Delete all sub directory entries // Created: 2003/10/09 // // -------------------------------------------------------------------------- void BackupClientDirectoryRecord::DeleteSubDirectories() { // Delete all pointers for(std::map::iterator i = mSubDirectories.begin(); i != mSubDirectories.end(); ++i) { delete i->second; } // Empty list mSubDirectories.clear(); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::SyncDirectory(i // BackupClientDirectoryRecord::SyncParams &, // int64_t, const std::string &, // const std::string &, bool) // Purpose: Recursively synchronise a local directory // with the server. // Created: 2003/10/08 // // -------------------------------------------------------------------------- void BackupClientDirectoryRecord::SyncDirectory( BackupClientDirectoryRecord::SyncParams &rParams, int64_t ContainingDirectoryID, const std::string &rLocalPath, const std::string &rRemotePath, bool ThisDirHasJustBeenCreated) { BackupClientContext& rContext(rParams.mrContext); ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); // Signal received by daemon? if(rParams.mrRunStatusProvider.StopRun()) { // Yes. Stop now. THROW_EXCEPTION(BackupStoreException, SignalReceived) } // Start by making some flag changes, marking this sync as not done, // and on the immediate sub directories. mSyncDone = false; for(std::map::iterator i = mSubDirectories.begin(); i != mSubDirectories.end(); ++i) { i->second->mSyncDone = false; } // Work out the time in the future after which the file should // be uploaded regardless. This is a simple way to avoid having // too many problems with file servers when they have clients // with badly out of sync clocks. rParams.mUploadAfterThisTimeInTheFuture = GetCurrentBoxTime() + rParams.mMaxFileTimeInFuture; // Build the current state checksum to compare against while // getting info from dirs. Note checksum is used locally only, // so byte order isn't considered. MD5Digest currentStateChecksum; EMU_STRUCT_STAT dest_st; // Stat the directory, to get attribute info // If it's a symbolic link, we want the link target here // (as we're about to back up the contents of the directory) { if(EMU_STAT(rLocalPath.c_str(), &dest_st) != 0) { // The directory has probably been deleted, so // just ignore this error. In a future scan, this // deletion will be noticed, deleted from server, // and this object deleted. rNotifier.NotifyDirStatFailed(this, rLocalPath, strerror(errno)); return; } // Store inode number in map so directories are tracked // in case they're renamed { BackupClientInodeToIDMap &idMap( rParams.mrContext.GetNewIDMap()); idMap.AddToMap(dest_st.st_ino, mObjectID, ContainingDirectoryID); } // Add attributes to checksum currentStateChecksum.Add(&dest_st.st_mode, sizeof(dest_st.st_mode)); currentStateChecksum.Add(&dest_st.st_uid, sizeof(dest_st.st_uid)); currentStateChecksum.Add(&dest_st.st_gid, sizeof(dest_st.st_gid)); // Inode to be paranoid about things moving around currentStateChecksum.Add(&dest_st.st_ino, sizeof(dest_st.st_ino)); #ifdef HAVE_STRUCT_STAT_ST_FLAGS currentStateChecksum.Add(&dest_st.st_flags, sizeof(dest_st.st_flags)); #endif StreamableMemBlock xattr; BackupClientFileAttributes::FillExtendedAttr(xattr, rLocalPath.c_str()); currentStateChecksum.Add(xattr.GetBuffer(), xattr.GetSize()); } // Read directory entries, building arrays of names // First, need to read the contents of the directory. std::vector dirs; std::vector files; bool downloadDirectoryRecordBecauseOfFutureFiles = false; EMU_STRUCT_STAT link_st; if(EMU_LSTAT(rLocalPath.c_str(), &link_st) != 0) { // Report the error (logs and // eventual email to administrator) rNotifier.NotifyFileStatFailed(this, rLocalPath, strerror(errno)); // FIXME move to NotifyFileStatFailed() SetErrorWhenReadingFilesystemObject(rParams, rLocalPath.c_str()); // This shouldn't happen, so we'd better not continue THROW_EXCEPTION(CommonException, OSFileError) } // BLOCK { // read the contents... DIR *dirHandle = 0; try { rNotifier.NotifyScanDirectory(this, rLocalPath); dirHandle = ::opendir(rLocalPath.c_str()); if(dirHandle == 0) { // Report the error (logs and // eventual email to administrator) if (errno == EACCES) { rNotifier.NotifyDirListFailed(this, rLocalPath, "Access denied"); } else { rNotifier.NotifyDirListFailed(this, rLocalPath, strerror(errno)); } // Report the error (logs and eventual email // to administrator) SetErrorWhenReadingFilesystemObject(rParams, rLocalPath.c_str()); // Ignore this directory for now. return; } // Basic structure for checksum info struct { box_time_t mModificationTime; box_time_t mAttributeModificationTime; int64_t mSize; // And then the name follows } checksum_info; // Be paranoid about structure packing ::memset(&checksum_info, 0, sizeof(checksum_info)); struct dirent *en = 0; EMU_STRUCT_STAT file_st; std::string filename; while((en = ::readdir(dirHandle)) != 0) { rParams.mrContext.DoKeepAlive(); // Don't need to use // LinuxWorkaround_FinishDirentStruct(en, // rLocalPath.c_str()); // on Linux, as a stat is performed to // get all this info if(en->d_name[0] == '.' && (en->d_name[1] == '\0' || (en->d_name[1] == '.' && en->d_name[2] == '\0'))) { // ignore, it's . or .. continue; } // Stat file to get info filename = MakeFullPath(rLocalPath, en->d_name); #ifdef WIN32 // Don't stat the file just yet, to ensure // that users can exclude unreadable files // to suppress warnings that they are // not accessible. // // Our emulated readdir() abuses en->d_type, // which would normally contain DT_REG, // DT_DIR, etc, but we only use it here and // prefer S_IFREG, S_IFDIR... int type = en->d_type; #else if(EMU_LSTAT(filename.c_str(), &file_st) != 0) { if(!(rParams.mrContext.ExcludeDir( filename))) { // Report the error (logs and // eventual email to // administrator) rNotifier.NotifyFileStatFailed( this, filename, strerror(errno)); // FIXME move to // NotifyFileStatFailed() SetErrorWhenReadingFilesystemObject( rParams, filename.c_str()); } // Ignore this entry for now. continue; } if(file_st.st_dev != dest_st.st_dev) { if(!(rParams.mrContext.ExcludeDir( filename))) { rNotifier.NotifyMountPointSkipped( this, filename); } continue; } int type = file_st.st_mode & S_IFMT; #endif if(type == S_IFREG || type == S_IFLNK) { // File or symbolic link // Exclude it? if(rParams.mrContext.ExcludeFile(filename)) { rNotifier.NotifyFileExcluded( this, filename); // Next item! continue; } // Store on list files.push_back(std::string(en->d_name)); } else if(type == S_IFDIR) { // Directory // Exclude it? if(rParams.mrContext.ExcludeDir(filename)) { rNotifier.NotifyDirExcluded( this, filename); // Next item! continue; } // Store on list dirs.push_back(std::string(en->d_name)); } else { if (type == S_IFSOCK || type == S_IFIFO) { // removed notification for these types // see Debian bug 479145, no objections } else if(rParams.mrContext.ExcludeFile(filename)) { rNotifier.NotifyFileExcluded( this, filename); } else { rNotifier.NotifyUnsupportedFileType( this, filename); SetErrorWhenReadingFilesystemObject( rParams, filename.c_str()); } continue; } // Here if the object is something to back up (file, symlink or dir, not excluded) // So make the information for adding to the checksum #ifdef WIN32 // We didn't stat the file before, // but now we need the information. if(emu_stat(filename.c_str(), &file_st) != 0) { rNotifier.NotifyFileStatFailed(this, filename, strerror(errno)); // Report the error (logs and // eventual email to administrator) SetErrorWhenReadingFilesystemObject( rParams, filename.c_str()); // Ignore this entry for now. continue; } if(file_st.st_dev != link_st.st_dev) { rNotifier.NotifyMountPointSkipped(this, filename); continue; } #endif checksum_info.mModificationTime = FileModificationTime(file_st); checksum_info.mAttributeModificationTime = FileAttrModificationTime(file_st); checksum_info.mSize = file_st.st_size; currentStateChecksum.Add(&checksum_info, sizeof(checksum_info)); currentStateChecksum.Add(en->d_name, strlen(en->d_name)); // If the file has been modified madly into the future, download the // directory record anyway to ensure that it doesn't get uploaded // every single time the disc is scanned. if(checksum_info.mModificationTime > rParams.mUploadAfterThisTimeInTheFuture) { downloadDirectoryRecordBecauseOfFutureFiles = true; // Log that this has happened if(!rParams.mHaveLoggedWarningAboutFutureFileTimes) { rNotifier.NotifyFileModifiedInFuture( this, filename); rParams.mHaveLoggedWarningAboutFutureFileTimes = true; } } } if(::closedir(dirHandle) != 0) { THROW_EXCEPTION(CommonException, OSFileError) } dirHandle = 0; } catch(...) { if(dirHandle != 0) { ::closedir(dirHandle); } throw; } } // Finish off the checksum, and compare with the one currently stored bool checksumDifferent = true; currentStateChecksum.Finish(); if(mInitialSyncDone && currentStateChecksum.DigestMatches(mStateChecksum)) { // The checksum is the same, and there was one to compare with checksumDifferent = false; } // Pointer to potentially downloaded store directory info BackupStoreDirectory *pdirOnStore = 0; try { // Want to get the directory listing? if(ThisDirHasJustBeenCreated) { // Avoid sending another command to the server when we know it's empty pdirOnStore = new BackupStoreDirectory(mObjectID, ContainingDirectoryID); } else { // Consider asking the store for it if(!mInitialSyncDone || checksumDifferent || downloadDirectoryRecordBecauseOfFutureFiles) { pdirOnStore = FetchDirectoryListing(rParams); } } // Make sure the attributes are up to date -- if there's space on the server // and this directory has not just been created (because it's attributes will be correct in this case) // and the checksum is different, implying they *MIGHT* be different. if((!ThisDirHasJustBeenCreated) && checksumDifferent && (!rParams.mrContext.StorageLimitExceeded())) { UpdateAttributes(rParams, pdirOnStore, rLocalPath); } // Create the list of pointers to directory entries std::vector entriesLeftOver; if(pdirOnStore) { entriesLeftOver.resize(pdirOnStore->GetNumberOfEntries(), 0); BackupStoreDirectory::Iterator i(*pdirOnStore); // Copy in pointers to all the entries for(unsigned int l = 0; l < pdirOnStore->GetNumberOfEntries(); ++l) { entriesLeftOver[l] = i.Next(); } } // Do the directory reading bool updateCompleteSuccess = UpdateItems(rParams, rLocalPath, rRemotePath, pdirOnStore, entriesLeftOver, files, dirs); // LAST THING! (think exception safety) // Store the new checksum -- don't fetch things unnecessarily in the future // But... only if 1) the storage limit isn't exceeded -- make sure things are done again if // the directory is modified later // and 2) All the objects within the directory were stored successfully. if(!rParams.mrContext.StorageLimitExceeded() && updateCompleteSuccess) { currentStateChecksum.CopyDigestTo(mStateChecksum); } } catch(...) { // Bad things have happened -- clean up if(pdirOnStore != 0) { delete pdirOnStore; pdirOnStore = 0; } // Set things so that we get a full go at stuff later ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); throw; } // Clean up directory on store if(pdirOnStore != 0) { delete pdirOnStore; pdirOnStore = 0; } // Flag things as having happened. mInitialSyncDone = true; mSyncDone = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::FetchDirectoryListing(BackupClientDirectoryRecord::SyncParams &) // Purpose: Fetch the directory listing of this directory from the store. // Created: 2003/10/09 // // -------------------------------------------------------------------------- BackupStoreDirectory *BackupClientDirectoryRecord::FetchDirectoryListing(BackupClientDirectoryRecord::SyncParams &rParams) { BackupStoreDirectory *pdir = 0; try { // Get connection to store BackupProtocolClient &connection(rParams.mrContext.GetConnection()); // Query the directory std::auto_ptr dirreply(connection.QueryListDirectory( mObjectID, BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // both files and directories BackupProtocolClientListDirectory::Flags_Deleted | BackupProtocolClientListDirectory::Flags_OldVersion, // exclude old/deleted stuff true /* want attributes */)); // Retrieve the directory from the stream following pdir = new BackupStoreDirectory; ASSERT(pdir != 0); std::auto_ptr dirstream(connection.ReceiveStream()); pdir->ReadFromStream(*dirstream, connection.GetTimeout()); } catch(...) { delete pdir; pdir = 0; throw; } return pdir; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::UpdateAttributes(BackupClientDirectoryRecord::SyncParams &, const std::string &) // Purpose: Sets the attributes of the directory on the store, if necessary // Created: 2003/10/09 // // -------------------------------------------------------------------------- void BackupClientDirectoryRecord::UpdateAttributes(BackupClientDirectoryRecord::SyncParams &rParams, BackupStoreDirectory *pDirOnStore, const std::string &rLocalPath) { // Get attributes for the directory BackupClientFileAttributes attr; box_time_t attrModTime = 0; attr.ReadAttributes(rLocalPath.c_str(), true /* directories have zero mod times */, 0 /* no modification time */, &attrModTime); // Assume attributes need updating, unless proved otherwise bool updateAttr = true; // Got a listing to compare with? ASSERT(pDirOnStore == 0 || (pDirOnStore != 0 && pDirOnStore->HasAttributes())); if(pDirOnStore != 0 && pDirOnStore->HasAttributes()) { const StreamableMemBlock &storeAttrEnc(pDirOnStore->GetAttributes()); // Explict decryption BackupClientFileAttributes storeAttr(storeAttrEnc); // Compare the attributes if(attr.Compare(storeAttr, true, true /* ignore both modification times */)) { // No update necessary updateAttr = false; } } // Update them? if(updateAttr) { // Get connection to store BackupProtocolClient &connection(rParams.mrContext.GetConnection()); // Exception thrown if this doesn't work MemBlockStream attrStream(attr); connection.QueryChangeDirAttributes(mObjectID, attrModTime, attrStream); } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncParams &, const std::string &, BackupStoreDirectory *, std::vector &) // Purpose: Update the items stored on the server. The rFiles vector will be erased after it's used to save space. // Returns true if all items were updated successfully. (If not, the failures will have been logged). // Created: 2003/10/09 // // -------------------------------------------------------------------------- bool BackupClientDirectoryRecord::UpdateItems( BackupClientDirectoryRecord::SyncParams &rParams, const std::string &rLocalPath, const std::string &rRemotePath, BackupStoreDirectory *pDirOnStore, std::vector &rEntriesLeftOver, std::vector &rFiles, const std::vector &rDirs) { BackupClientContext& rContext(rParams.mrContext); ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); bool allUpdatedSuccessfully = true; // Decrypt all the directory entries. // It would be nice to be able to just compare the encrypted versions, however this doesn't work // in practise because there can be multiple encodings of the same filename using different // methods (although each method will result in the same string for the same filename.) This // happens when the server fixes a broken store, and gives plain text generated filenames. // So if we didn't do things like this, then you wouldn't be able to recover from bad things // happening with the server. DecryptedEntriesMap_t decryptedEntries; if(pDirOnStore != 0) { BackupStoreDirectory::Iterator i(*pDirOnStore); BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { decryptedEntries[BackupStoreFilenameClear(en->GetName()).GetClearFilename()] = en; } } // Do files for(std::vector::const_iterator f = rFiles.begin(); f != rFiles.end(); ++f) { // Send keep-alive message if needed rContext.DoKeepAlive(); // Filename of this file std::string filename(MakeFullPath(rLocalPath, *f)); // Get relevant info about file box_time_t modTime = 0; uint64_t attributesHash = 0; int64_t fileSize = 0; InodeRefType inodeNum = 0; bool hasMultipleHardLinks = true; // BLOCK { // Stat the file EMU_STRUCT_STAT st; if(EMU_LSTAT(filename.c_str(), &st) != 0) { rNotifier.NotifyFileStatFailed(this, filename, strerror(errno)); // Report the error (logs and // eventual email to administrator) SetErrorWhenReadingFilesystemObject(rParams, filename.c_str()); // Ignore this entry for now. continue; } // Extract required data modTime = FileModificationTime(st); fileSize = st.st_size; inodeNum = st.st_ino; hasMultipleHardLinks = (st.st_nlink > 1); attributesHash = BackupClientFileAttributes::GenerateAttributeHash(st, filename, *f); } // See if it's in the listing (if we have one) BackupStoreFilenameClear storeFilename(*f); BackupStoreDirectory::Entry *en = 0; int64_t latestObjectID = 0; if(pDirOnStore != 0) { DecryptedEntriesMap_t::iterator i(decryptedEntries.find(*f)); if(i != decryptedEntries.end()) { en = i->second; latestObjectID = en->GetObjectID(); } } // Check that the entry which might have been found is in fact a file if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == 0)) { // Directory exists in the place of this file -- sort it out RemoveDirectoryInPlaceOfFile(rParams, pDirOnStore, en, *f); en = 0; } // Check for renaming? if(pDirOnStore != 0 && en == 0) { // We now know... // 1) File has just been added // 2) It's not in the store // Do we know about the inode number? const BackupClientInodeToIDMap &idMap(rContext.GetCurrentIDMap()); int64_t renameObjectID = 0, renameInDirectory = 0; if(idMap.Lookup(inodeNum, renameObjectID, renameInDirectory)) { // Look up on the server to get the name, to build the local filename std::string localPotentialOldName; bool isDir = false; bool isCurrentVersion = false; box_time_t srvModTime = 0, srvAttributesHash = 0; BackupStoreFilenameClear oldLeafname; if(rContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion, &srvModTime, &srvAttributesHash, &oldLeafname)) { // Only interested if it's a file and the latest version if(!isDir && isCurrentVersion) { // Check that the object we found in the ID map doesn't exist on disc EMU_STRUCT_STAT st; if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) { // Doesn't exist locally, but does exist on the server. // Therefore we can safely rename it to this new file. // Get the connection to the server BackupProtocolClient &connection(rContext.GetConnection()); // Only do this step if there is room on the server. // This step will be repeated later when there is space available if(!rContext.StorageLimitExceeded()) { // Rename the existing files (ie include old versions) on the server connection.QueryMoveObject(renameObjectID, renameInDirectory, mObjectID /* move to this directory */, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName | BackupProtocolClientMoveObject::Flags_AllowMoveOverDeletedObject, storeFilename); // Stop the attempt to delete the file in the original location BackupClientDeleteList &rdelList(rContext.GetDeleteList()); rdelList.StopFileDeletion(renameInDirectory, oldLeafname); // Create new entry in the directory for it // -- will be near enough what's actually on the server for the rest to work. en = pDirOnStore->AddEntry(storeFilename, srvModTime, renameObjectID, 0 /* size in blocks unknown, but not needed */, BackupStoreDirectory::Entry::Flags_File, srvAttributesHash); // Store the object ID for the inode lookup map later latestObjectID = renameObjectID; } } } } } } // Is it in the mPendingEntries list? box_time_t pendingFirstSeenTime = 0; // ie not seen if(mpPendingEntries != 0) { std::map::const_iterator i(mpPendingEntries->find(*f)); if(i != mpPendingEntries->end()) { // found it -- set flag pendingFirstSeenTime = i->second; } } // If pDirOnStore == 0, then this must have been after an initial sync: ASSERT(pDirOnStore != 0 || mInitialSyncDone); // So, if pDirOnStore == 0, then we know that everything before syncPeriodStart // is either on the server, or in the toupload list. If the directory had changed, // we'd have got a directory listing. // // At this point, if (pDirOnStore == 0 && en == 0), we can assume it's on the server with a // mod time < syncPeriodStart, or didn't exist before that time. // // But if en != 0, then we need to compare modification times to avoid uploading it again. // Need to update? // // Condition for upload: // modification time within sync period // if it's been seen before but not uploaded, is the time from this first sight longer than the MaxUploadWait // and if we know about it from a directory listing, that it hasn't got the same upload time as on the store bool doUpload = false; // Only upload a file if the mod time locally is // different to that on the server. if (en == 0 || en->GetModificationTime() != modTime) { // Check the file modified within the acceptable time period we're checking // If the file isn't on the server, the acceptable time starts at zero. // Check pDirOnStore and en, because if we didn't download a directory listing, // pDirOnStore will be zero, but we know it's on the server. if (modTime < rParams.mSyncPeriodEnd) { if (pDirOnStore != 0 && en == 0) { doUpload = true; BOX_TRACE("Upload decision: " << filename << ": will upload " "(not on server)"); } else if (modTime >= rParams.mSyncPeriodStart) { doUpload = true; BOX_TRACE("Upload decision: " << filename << ": will upload " "(modified since last sync)"); } } // However, just in case things are continually // modified, we check the first seen time. // The two compares of syncPeriodEnd and // pendingFirstSeenTime are because the values // are unsigned. if (!doUpload && pendingFirstSeenTime != 0 && rParams.mSyncPeriodEnd > pendingFirstSeenTime && (rParams.mSyncPeriodEnd - pendingFirstSeenTime) > rParams.mMaxUploadWait) { doUpload = true; BOX_TRACE("Upload decision: " << filename << ": will upload " "(continually modified)"); } // Then make sure that if files are added with a // time less than the sync period start // (which can easily happen on file server), it // gets uploaded. The directory contents checksum // will pick up the fact it has been added, so the // store listing will be available when this happens. if (!doUpload && modTime <= rParams.mSyncPeriodStart && en != 0 && en->GetModificationTime() != modTime) { doUpload = true; BOX_TRACE("Upload decision: " << filename << ": will upload " "(mod time changed)"); } // And just to catch really badly off clocks in // the future for file server clients, // just upload the file if it's madly in the future. if (!doUpload && modTime > rParams.mUploadAfterThisTimeInTheFuture) { doUpload = true; BOX_TRACE("Upload decision: " << filename << ": will upload " "(mod time in the future)"); } } if (en != 0 && en->GetModificationTime() == modTime) { BOX_TRACE("Upload decision: " << filename << ": will not upload " "(not modified since last upload)"); } else if (!doUpload) { if (modTime > rParams.mSyncPeriodEnd) { box_time_t now = GetCurrentBoxTime(); int age = BoxTimeToSeconds(now - modTime); BOX_TRACE("Upload decision: " << filename << ": will not upload " "(modified too recently: " "only " << age << " seconds ago)"); } else { BOX_TRACE("Upload decision: " << filename << ": will not upload " "(mod time is " << modTime << " which is outside sync window, " << rParams.mSyncPeriodStart << " to " << rParams.mSyncPeriodEnd << ")"); } } bool fileSynced = true; if (doUpload) { // Upload needed, don't mark sync success until // we've actually done it fileSynced = false; // Make sure we're connected -- must connect here so we know whether // the storage limit has been exceeded, and hence whether or not // to actually upload the file. rContext.GetConnection(); // Only do this step if there is room on the server. // This step will be repeated later when there is space available if(!rContext.StorageLimitExceeded()) { // Upload the file to the server, recording the // object ID it returns bool noPreviousVersionOnServer = ((pDirOnStore != 0) && (en == 0)); // Surround this in a try/catch block, to // catch errors, but still continue bool uploadSuccess = false; try { latestObjectID = UploadFile(rParams, filename, storeFilename, fileSize, modTime, attributesHash, noPreviousVersionOnServer); if (latestObjectID == 0) { // storage limit exceeded rParams.mrContext.SetStorageLimitExceeded(); uploadSuccess = false; allUpdatedSuccessfully = false; } else { uploadSuccess = true; } } catch(ConnectionException &e) { // Connection errors should just be // passed on to the main handler, // retries would probably just cause // more problems. rNotifier.NotifyFileUploadException( this, filename, e); throw; } catch(BoxException &e) { if (e.GetType() == BackupStoreException::ExceptionType && e.GetSubType() == BackupStoreException::SignalReceived) { // abort requested, pass the // exception on up. throw; } // an error occured -- make return // code false, to show error in directory allUpdatedSuccessfully = false; // Log it. SetErrorWhenReadingFilesystemObject(rParams, filename.c_str()); rNotifier.NotifyFileUploadException( this, filename, e); } // Update structures if the file was uploaded // successfully. if(uploadSuccess) { fileSynced = true; // delete from pending entries if(pendingFirstSeenTime != 0 && mpPendingEntries != 0) { mpPendingEntries->erase(*f); } } } else { rNotifier.NotifyFileSkippedServerFull(this, filename); } } else if(en != 0 && en->GetAttributesHash() != attributesHash) { // Attributes have probably changed, upload them again. // If the attributes have changed enough, the directory // hash will have changed too, and so the dir will have // been downloaded, and the entry will be available. // Get connection BackupProtocolClient &connection(rContext.GetConnection()); // Only do this step if there is room on the server. // This step will be repeated later when there is // space available if(!rContext.StorageLimitExceeded()) { try { rNotifier.NotifyFileUploadingAttributes( this, filename); // Update store BackupClientFileAttributes attr; attr.ReadAttributes(filename.c_str(), false /* put mod times in the attributes, please */); MemBlockStream attrStream(attr); connection.QuerySetReplacementFileAttributes(mObjectID, attributesHash, storeFilename, attrStream); fileSynced = true; } catch (BoxException &e) { BOX_ERROR("Failed to read or store " "file attributes for '" << filename << "', will try " "again later"); } } } if(modTime >= rParams.mSyncPeriodEnd) { // Allocate? if(mpPendingEntries == 0) { mpPendingEntries = new std::map; } // Adding to mPendingEntries list if(pendingFirstSeenTime == 0) { // Haven't seen this before -- add to list! (*mpPendingEntries)[*f] = modTime; } } // Zero pointer in rEntriesLeftOver, if we have a pointer to zero if(en != 0) { for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) { if(rEntriesLeftOver[l] == en) { rEntriesLeftOver[l] = 0; break; } } } // Does this file need an entry in the ID map? if(fileSize >= rParams.mFileTrackingSizeThreshold) { // Get the map BackupClientInodeToIDMap &idMap(rContext.GetNewIDMap()); // Need to get an ID from somewhere... if(latestObjectID != 0) { // Use this one BOX_TRACE("Storing uploaded file ID " << inodeNum << " (" << filename << ") " "in ID map as object " << latestObjectID << " with parent " << mObjectID); idMap.AddToMap(inodeNum, latestObjectID, mObjectID /* containing directory */); } else { // Don't know it -- haven't sent anything to the store, and didn't get a listing. // Look it up in the current map, and if it's there, use that. const BackupClientInodeToIDMap ¤tIDMap(rContext.GetCurrentIDMap()); int64_t objid = 0, dirid = 0; if(currentIDMap.Lookup(inodeNum, objid, dirid)) { // Found if (dirid != mObjectID) { BOX_WARNING("Found conflicting parent ID for file ID " << inodeNum << " (" << filename << "): expected " << mObjectID << " but found " << dirid << " (same directory used in two different locations?)"); } ASSERT(dirid == mObjectID); // NOTE: If the above assert fails, an inode number has been reused by the OS, // or there is a problem somewhere. If this happened on a short test run, look // into it. However, in a long running process this may happen occasionally and // not indicate anything wrong. // Run the release version for real life use, where this check is not made. BOX_TRACE("Storing found file ID " << inodeNum << " (" << filename << ") in ID map as object " << objid << " with parent " << mObjectID); idMap.AddToMap(inodeNum, objid, mObjectID /* containing directory */); } } } if (fileSynced) { rNotifier.NotifyFileSynchronised(this, filename, fileSize); } } // Erase contents of files to save space when recursing rFiles.clear(); // Delete the pending entries, if the map is entry if(mpPendingEntries != 0 && mpPendingEntries->size() == 0) { BOX_TRACE("Deleting mpPendingEntries from dir ID " << BOX_FORMAT_OBJECTID(mObjectID)); delete mpPendingEntries; mpPendingEntries = 0; } // Do directories for(std::vector::const_iterator d = rDirs.begin(); d != rDirs.end(); ++d) { // Send keep-alive message if needed rContext.DoKeepAlive(); // Get the local filename std::string dirname(MakeFullPath(rLocalPath, *d)); // See if it's in the listing (if we have one) BackupStoreFilenameClear storeFilename(*d); BackupStoreDirectory::Entry *en = 0; if(pDirOnStore != 0) { DecryptedEntriesMap_t::iterator i(decryptedEntries.find(*d)); if(i != decryptedEntries.end()) { en = i->second; } } // Check that the entry which might have been found is in fact a directory if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == 0)) { // Entry exists, but is not a directory. Bad. // Get rid of it. BackupProtocolClient &connection(rContext.GetConnection()); connection.QueryDeleteFile(mObjectID /* in directory */, storeFilename); rNotifier.NotifyFileDeleted(en->GetObjectID(), storeFilename.GetClearFilename()); // Nothing found en = 0; } // Flag for having created directory, so can optimise the // recursive call not to read it again, because we know // it's empty. bool haveJustCreatedDirOnServer = false; // Next, see if it's in the list of sub directories BackupClientDirectoryRecord *psubDirRecord = 0; std::map::iterator e(mSubDirectories.find(*d)); if(e != mSubDirectories.end()) { // In the list, just use this pointer psubDirRecord = e->second; } else { // Note: if we have exceeded our storage limit, then // we should not upload any more data, nor create any // DirectoryRecord representing data that would have // been uploaded. This step will be repeated when // there is some space available. bool doCreateDirectoryRecord = true; // Need to create the record. But do we need to create the directory on the server? int64_t subDirObjectID = 0; if(en != 0) { // No. Exists on the server, and we know about it from the listing. subDirObjectID = en->GetObjectID(); } else if(rContext.StorageLimitExceeded()) // know we've got a connection if we get this far, // as dir will have been modified. { doCreateDirectoryRecord = false; } else { // Yes, creation required! // It is known that the it doesn't exist: // if pDirOnStore == 0, then the directory has had an initial sync, and hasn't been modified. // so it has definately been created already. // if en == 0 but pDirOnStore != 0, well... obviously it doesn't exist. // Get attributes box_time_t attrModTime = 0; InodeRefType inodeNum = 0; BackupClientFileAttributes attr; bool failedToReadAttributes = false; try { attr.ReadAttributes(dirname.c_str(), true /* directories have zero mod times */, 0 /* not interested in mod time */, &attrModTime, 0 /* not file size */, &inodeNum); } catch (BoxException &e) { BOX_WARNING("Failed to read attributes " "of directory, cannot check " "for rename, assuming new: '" << dirname << "'"); failedToReadAttributes = true; } // Check to see if the directory been renamed // First, do we have a record in the ID map? int64_t renameObjectID = 0, renameInDirectory = 0; bool renameDir = false; const BackupClientInodeToIDMap &idMap( rContext.GetCurrentIDMap()); if(!failedToReadAttributes && idMap.Lookup(inodeNum, renameObjectID, renameInDirectory)) { // Look up on the server to get the name, to build the local filename std::string localPotentialOldName; bool isDir = false; bool isCurrentVersion = false; if(rContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion)) { // Only interested if it's a directory if(isDir && isCurrentVersion) { // Check that the object doesn't exist already EMU_STRUCT_STAT st; if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) { // Doesn't exist locally, but does exist on the server. // Therefore we can safely rename it. renameDir = true; } } } } // Get connection BackupProtocolClient &connection(rContext.GetConnection()); // Don't do a check for storage limit exceeded here, because if we get to this // stage, a connection will have been opened, and the status known, so the check // in the else if(...) above will be correct. // Build attribute stream for sending MemBlockStream attrStream(attr); if(renameDir) { // Rename the existing directory on the server connection.QueryMoveObject(renameObjectID, renameInDirectory, mObjectID /* move to this directory */, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName | BackupProtocolClientMoveObject::Flags_AllowMoveOverDeletedObject, storeFilename); // Put the latest attributes on it connection.QueryChangeDirAttributes(renameObjectID, attrModTime, attrStream); // Stop it being deleted later BackupClientDeleteList &rdelList( rContext.GetDeleteList()); rdelList.StopDirectoryDeletion(renameObjectID); // This is the ID for the renamed directory subDirObjectID = renameObjectID; } else { // Create a new directory std::auto_ptr dirCreate(connection.QueryCreateDirectory( mObjectID, attrModTime, storeFilename, attrStream)); subDirObjectID = dirCreate->GetObjectID(); // Flag as having done this for optimisation later haveJustCreatedDirOnServer = true; } } if (doCreateDirectoryRecord) { // New an object for this psubDirRecord = new BackupClientDirectoryRecord(subDirObjectID, *d); // Store in list try { mSubDirectories[*d] = psubDirRecord; } catch(...) { delete psubDirRecord; psubDirRecord = 0; throw; } } } ASSERT(psubDirRecord != 0 || rContext.StorageLimitExceeded()); if(psubDirRecord) { // Sync this sub directory too psubDirRecord->SyncDirectory(rParams, mObjectID, dirname, rRemotePath + "/" + *d, haveJustCreatedDirOnServer); } // Zero pointer in rEntriesLeftOver, if we have a pointer to zero if(en != 0) { for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) { if(rEntriesLeftOver[l] == en) { rEntriesLeftOver[l] = 0; break; } } } } // Delete everything which is on the store, but not on disc for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) { if(rEntriesLeftOver[l] != 0) { BackupStoreDirectory::Entry *en = rEntriesLeftOver[l]; // These entries can't be deleted immediately, as it would prevent // renaming and moving of objects working properly. So we add them // to a list, which is actually deleted at the very end of the session. // If there's an error during the process, it doesn't matter if things // aren't actually deleted, as the whole state will be reset anyway. BackupClientDeleteList &rdel(rContext.GetDeleteList()); BackupStoreFilenameClear clear(en->GetName()); std::string localName = MakeFullPath(rLocalPath, clear.GetClearFilename()); // Delete this entry -- file or directory? if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) != 0) { // Set a pending deletion for the file rdel.AddFileDelete(mObjectID, en->GetName(), localName); } else if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0) { // Set as a pending deletion for the directory rdel.AddDirectoryDelete(en->GetObjectID(), localName); // If there's a directory record for it in // the sub directory map, delete it now BackupStoreFilenameClear dirname(en->GetName()); std::map::iterator e(mSubDirectories.find(dirname.GetClearFilename())); if(e != mSubDirectories.end()) { // Carefully delete the entry from the map BackupClientDirectoryRecord *rec = e->second; mSubDirectories.erase(e); delete rec; std::string name = MakeFullPath( rLocalPath, dirname.GetClearFilename()); BOX_TRACE("Deleted directory record " "for " << name); } } } } // Return success flag (will be false if some files failed) return allUpdatedSuccessfully; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile(SyncParams &, BackupStoreDirectory *, int64_t, const std::string &) // Purpose: Called to resolve difficulties when a directory is found on the // store where a file is to be uploaded. // Created: 9/7/04 // // -------------------------------------------------------------------------- void BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile( SyncParams &rParams, BackupStoreDirectory* pDirOnStore, BackupStoreDirectory::Entry* pEntry, const std::string &rFilename) { // First, delete the directory BackupProtocolClient &connection(rParams.mrContext.GetConnection()); connection.QueryDeleteDirectory(pEntry->GetObjectID()); BackupStoreFilenameClear clear(pEntry->GetName()); rParams.mrContext.GetProgressNotifier().NotifyDirectoryDeleted( pEntry->GetObjectID(), clear.GetClearFilename()); // Then, delete any directory record std::map::iterator e(mSubDirectories.find(rFilename)); if(e != mSubDirectories.end()) { // A record exists for this, remove it BackupClientDirectoryRecord *psubDirRecord = e->second; mSubDirectories.erase(e); // And delete the object delete psubDirRecord; } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::UploadFile( // BackupClientDirectoryRecord::SyncParams &, // const std::string &, // const BackupStoreFilename &, // int64_t, box_time_t, box_time_t, bool) // Purpose: Private. Upload a file to the server. May send // a patch instead of the whole thing // Created: 20/1/04 // // -------------------------------------------------------------------------- int64_t BackupClientDirectoryRecord::UploadFile( BackupClientDirectoryRecord::SyncParams &rParams, const std::string &rFilename, const BackupStoreFilename &rStoreFilename, int64_t FileSize, box_time_t ModificationTime, box_time_t AttributesHash, bool NoPreviousVersionOnServer) { BackupClientContext& rContext(rParams.mrContext); ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); // Get the connection BackupProtocolClient &connection(rContext.GetConnection()); // Info int64_t objID = 0; bool doNormalUpload = true; // Use a try block to catch store full errors try { // Might an old version be on the server, and is the file // size over the diffing threshold? if(!NoPreviousVersionOnServer && FileSize >= rParams.mDiffingUploadSizeThreshold) { // YES -- try to do diff, if possible // First, query the server to see if there's an old version available std::auto_ptr getBlockIndex(connection.QueryGetBlockIndexByName(mObjectID, rStoreFilename)); int64_t diffFromID = getBlockIndex->GetObjectID(); if(diffFromID != 0) { // Found an old version rNotifier.NotifyFileUploadingPatch(this, rFilename); // Get the index std::auto_ptr blockIndexStream(connection.ReceiveStream()); // // Diff the file // rContext.ManageDiffProcess(); bool isCompletelyDifferent = false; std::auto_ptr patchStream( BackupStoreFile::EncodeFileDiff( rFilename.c_str(), mObjectID, /* containing directory */ rStoreFilename, diffFromID, *blockIndexStream, connection.GetTimeout(), &rContext, // DiffTimer implementation 0 /* not interested in the modification time */, &isCompletelyDifferent)); rContext.UnManageDiffProcess(); // // Upload the patch to the store // std::auto_ptr stored(connection.QueryStoreFile(mObjectID, ModificationTime, AttributesHash, isCompletelyDifferent?(0):(diffFromID), rStoreFilename, *patchStream)); // Get object ID from the result objID = stored->GetObjectID(); // Don't attempt to upload it again! doNormalUpload = false; } } if(doNormalUpload) { // below threshold or nothing to diff from, so upload whole rNotifier.NotifyFileUploading(this, rFilename); // Prepare to upload, getting a stream which will encode the file as we go along std::auto_ptr upload( BackupStoreFile::EncodeFile(rFilename.c_str(), mObjectID, rStoreFilename, NULL, &rParams, &(rParams.mrRunStatusProvider))); // Send to store std::auto_ptr stored( connection.QueryStoreFile( mObjectID, ModificationTime, AttributesHash, 0 /* no diff from file ID */, rStoreFilename, *upload)); // Get object ID from the result objID = stored->GetObjectID(); } } catch(BoxException &e) { rContext.UnManageDiffProcess(); if(e.GetType() == ConnectionException::ExceptionType && e.GetSubType() == ConnectionException::Protocol_UnexpectedReply) { // Check and see what error the protocol has, // this is more useful to users than the exception. int type, subtype; if(connection.GetLastError(type, subtype)) { if(type == BackupProtocolClientError::ErrorType && subtype == BackupProtocolClientError::Err_StorageLimitExceeded) { // The hard limit was exceeded on the server, notify! rParams.mrSysadminNotifier.NotifySysadmin( SysadminNotifier::StoreFull); // return an error code instead of // throwing an exception that we // can't debug. return 0; } rNotifier.NotifyFileUploadServerError(this, rFilename, type, subtype); } } // Send the error on it's way throw; } rNotifier.NotifyFileUploaded(this, rFilename, FileSize); // Return the new object ID of this file return objID; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(SyncParams &, const char *) // Purpose: Sets the error state when there were problems reading an object // from the filesystem. // Created: 29/3/04 // // -------------------------------------------------------------------------- void BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(BackupClientDirectoryRecord::SyncParams &rParams, const char *Filename) { // Zero hash, so it gets synced properly next time round. ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); // Log the error - already done by caller /* rParams.GetProgressNotifier().NotifyFileReadFailed(this, Filename, strerror(errno)); */ // Mark that an error occured in the parameters object rParams.mReadErrorsOnFilesystemObjects = true; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::SyncParams::SyncParams(BackupClientContext &) // Purpose: Constructor // Created: 8/3/04 // // -------------------------------------------------------------------------- BackupClientDirectoryRecord::SyncParams::SyncParams( RunStatusProvider &rRunStatusProvider, SysadminNotifier &rSysadminNotifier, ProgressNotifier &rProgressNotifier, BackupClientContext &rContext) : mSyncPeriodStart(0), mSyncPeriodEnd(0), mMaxUploadWait(0), mMaxFileTimeInFuture(99999999999999999LL), mFileTrackingSizeThreshold(16*1024), mDiffingUploadSizeThreshold(16*1024), mrRunStatusProvider(rRunStatusProvider), mrSysadminNotifier(rSysadminNotifier), mrProgressNotifier(rProgressNotifier), mrContext(rContext), mReadErrorsOnFilesystemObjects(false), mUploadAfterThisTimeInTheFuture(99999999999999999LL), mHaveLoggedWarningAboutFutureFileTimes(false) { } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::SyncParams::~SyncParams() // Purpose: Destructor // Created: 8/3/04 // // -------------------------------------------------------------------------- BackupClientDirectoryRecord::SyncParams::~SyncParams() { } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::Deserialize(Archive & rArchive) // Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction. // // Created: 2005/04/11 // // -------------------------------------------------------------------------- void BackupClientDirectoryRecord::Deserialize(Archive & rArchive) { // Make deletion recursive DeleteSubDirectories(); // Delete maps if(mpPendingEntries != 0) { delete mpPendingEntries; mpPendingEntries = 0; } // // // rArchive.Read(mObjectID); rArchive.Read(mSubDirName); rArchive.Read(mInitialSyncDone); rArchive.Read(mSyncDone); // // // int64_t iCount = 0; rArchive.Read(iCount); if (iCount != sizeof(mStateChecksum)/sizeof(mStateChecksum[0])) { // we have some kind of internal system representation change: throw for now THROW_EXCEPTION(CommonException, Internal) } for (int v = 0; v < iCount; v++) { // Load each checksum entry rArchive.Read(mStateChecksum[v]); } // // // iCount = 0; rArchive.Read(iCount); if (iCount > 0) { // load each pending entry mpPendingEntries = new std::map; if (!mpPendingEntries) { throw std::bad_alloc(); } for (int v = 0; v < iCount; v++) { std::string strItem; box_time_t btItem; rArchive.Read(strItem); rArchive.Read(btItem); (*mpPendingEntries)[strItem] = btItem; } } // // // iCount = 0; rArchive.Read(iCount); if (iCount > 0) { for (int v = 0; v < iCount; v++) { std::string strItem; rArchive.Read(strItem); BackupClientDirectoryRecord* pSubDirRecord = new BackupClientDirectoryRecord(0, ""); // will be deserialized anyway, give it id 0 for now if (!pSubDirRecord) { throw std::bad_alloc(); } /***** RECURSE *****/ pSubDirRecord->Deserialize(rArchive); mSubDirectories[strItem] = pSubDirRecord; } } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDirectoryRecord::Serialize(Archive & rArchive) // Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction. // // Created: 2005/04/11 // // -------------------------------------------------------------------------- void BackupClientDirectoryRecord::Serialize(Archive & rArchive) const { // // // rArchive.Write(mObjectID); rArchive.Write(mSubDirName); rArchive.Write(mInitialSyncDone); rArchive.Write(mSyncDone); // // // int64_t iCount = 0; // when reading back the archive, we will // need to know how many items there are. iCount = sizeof(mStateChecksum) / sizeof(mStateChecksum[0]); rArchive.Write(iCount); for (int v = 0; v < iCount; v++) { rArchive.Write(mStateChecksum[v]); } // // // if (!mpPendingEntries) { iCount = 0; rArchive.Write(iCount); } else { iCount = mpPendingEntries->size(); rArchive.Write(iCount); for (std::map::const_iterator i = mpPendingEntries->begin(); i != mpPendingEntries->end(); i++) { rArchive.Write(i->first); rArchive.Write(i->second); } } // // // iCount = mSubDirectories.size(); rArchive.Write(iCount); for (std::map::const_iterator i = mSubDirectories.begin(); i != mSubDirectories.end(); i++) { const BackupClientDirectoryRecord* pSubItem = i->second; ASSERT(pSubItem); rArchive.Write(i->first); pSubItem->Serialize(rArchive); } } boxbackup/bin/bbackupd/BackupDaemonInterface.h0000664000175000017500000001233211345271627022171 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupDaemonInterface.h // Purpose: Interfaces for managing a BackupDaemon // Created: 2008/12/30 // // -------------------------------------------------------------------------- #ifndef BACKUPDAEMONINTERFACE__H #define BACKUPDAEMONINTERFACE__H #include // #include // #include "BackupClientFileAttributes.h" // #include "BackupStoreDirectory.h" #include "BoxTime.h" // #include "MD5Digest.h" // #include "ReadLoggingStream.h" // #include "RunStatusProvider.h" class Archive; class BackupClientContext; class BackupDaemon; // -------------------------------------------------------------------------- // // Class // Name: SysadminNotifier // Purpose: Provides a NotifySysadmin() method to send mail to the sysadmin // Created: 2005/11/15 // // -------------------------------------------------------------------------- class SysadminNotifier { public: virtual ~SysadminNotifier() { } typedef enum { StoreFull = 0, ReadError, BackupError, BackupStart, BackupFinish, BackupOK, MAX // When adding notifications, remember to add // strings to NotifySysadmin() } EventCode; virtual void NotifySysadmin(EventCode Event) = 0; }; // -------------------------------------------------------------------------- // // Class // Name: ProgressNotifier // Purpose: Provides methods for the backup library to inform the user // interface about its progress with the backup // Created: 2005/11/20 // // -------------------------------------------------------------------------- class BackupClientContext; class BackupClientDirectoryRecord; class ProgressNotifier { public: virtual ~ProgressNotifier() { } virtual void NotifyIDMapsSetup(BackupClientContext& rContext) = 0; virtual void NotifyScanDirectory( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyDirStatFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) = 0; virtual void NotifyFileStatFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) = 0; virtual void NotifyDirListFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) = 0; virtual void NotifyMountPointSkipped( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyFileExcluded( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyDirExcluded( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyUnsupportedFileType( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyFileReadFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) = 0; virtual void NotifyFileModifiedInFuture( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyFileSkippedServerFull( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyFileUploadException( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const BoxException& rException) = 0; virtual void NotifyFileUploadServerError( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, int type, int subtype) = 0; virtual void NotifyFileUploading( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyFileUploadingPatch( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyFileUploadingAttributes( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) = 0; virtual void NotifyFileUploaded( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, int64_t FileSize) = 0; virtual void NotifyFileSynchronised( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, int64_t FileSize) = 0; virtual void NotifyDirectoryDeleted( int64_t ObjectID, const std::string& rRemotePath) = 0; virtual void NotifyFileDeleted( int64_t ObjectID, const std::string& rRemotePath) = 0; virtual void NotifyReadProgress(int64_t readSize, int64_t offset, int64_t length, box_time_t elapsed, box_time_t finish) = 0; virtual void NotifyReadProgress(int64_t readSize, int64_t offset, int64_t length) = 0; virtual void NotifyReadProgress(int64_t readSize, int64_t offset) = 0; }; // -------------------------------------------------------------------------- // // Class // Name: LocationResolver // Purpose: Interface for classes that can resolve locations to paths, // like BackupDaemon // Created: 2003/10/08 // // -------------------------------------------------------------------------- class LocationResolver { public: virtual ~LocationResolver() { } virtual bool FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const = 0; }; #endif // BACKUPDAEMONINTERFACE__H boxbackup/bin/bbackupd/BackupClientContext.h0000664000175000017500000001613711436002351021722 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientContext.h // Purpose: Keep track of context // Created: 2003/10/08 // // -------------------------------------------------------------------------- #ifndef BACKUPCLIENTCONTEXT__H #define BACKUPCLIENTCONTEXT__H #include "BoxTime.h" #include "BackupClientDeleteList.h" #include "BackupClientDirectoryRecord.h" #include "BackupDaemonInterface.h" #include "BackupStoreFile.h" #include "ExcludeList.h" #include "Timer.h" class TLSContext; class BackupProtocolClient; class SocketStreamTLS; class BackupClientInodeToIDMap; class BackupDaemon; class BackupStoreFilenameClear; #include // -------------------------------------------------------------------------- // // Class // Name: BackupClientContext // Purpose: // Created: 2003/10/08 // // -------------------------------------------------------------------------- class BackupClientContext : public DiffTimer { public: BackupClientContext ( LocationResolver &rResolver, TLSContext &rTLSContext, const std::string &rHostname, int32_t Port, uint32_t AccountNumber, bool ExtendedLogging, bool ExtendedLogToFile, std::string ExtendedLogFile, ProgressNotifier &rProgressNotifier ); virtual ~BackupClientContext(); private: BackupClientContext(const BackupClientContext &); public: BackupProtocolClient &GetConnection(); void CloseAnyOpenConnection(); int GetTimeout() const; BackupClientDeleteList &GetDeleteList(); void PerformDeletions(); enum { ClientStoreMarker_NotKnown = 0 }; void SetClientStoreMarker(int64_t ClientStoreMarker) {mClientStoreMarker = ClientStoreMarker;} int64_t GetClientStoreMarker() const {return mClientStoreMarker;} bool StorageLimitExceeded() {return mStorageLimitExceeded;} void SetStorageLimitExceeded() {mStorageLimitExceeded = true;} // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::SetIDMaps(const BackupClientInodeToIDMap *, BackupClientInodeToIDMap *) // Purpose: Store pointers to the Current and New ID maps // Created: 11/11/03 // // -------------------------------------------------------------------------- void SetIDMaps(const BackupClientInodeToIDMap *pCurrent, BackupClientInodeToIDMap *pNew) { ASSERT(pCurrent != 0); ASSERT(pNew != 0); mpCurrentIDMap = pCurrent; mpNewIDMap = pNew; } const BackupClientInodeToIDMap &GetCurrentIDMap() const; BackupClientInodeToIDMap &GetNewIDMap() const; // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::SetExcludeLists(ExcludeList *, ExcludeList *) // Purpose: Sets the exclude lists for the operation. Can be 0. // Created: 28/1/04 // // -------------------------------------------------------------------------- void SetExcludeLists(ExcludeList *pExcludeFiles, ExcludeList *pExcludeDirs) { mpExcludeFiles = pExcludeFiles; mpExcludeDirs = pExcludeDirs; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::ExcludeFile(const std::string &) // Purpose: Returns true is this file should be excluded from the backup // Created: 28/1/04 // // -------------------------------------------------------------------------- inline bool ExcludeFile(const std::string &rFullFilename) { if(mpExcludeFiles != 0) { return mpExcludeFiles->IsExcluded(rFullFilename); } // If no list, don't exclude anything return false; } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::ExcludeDir(const std::string &) // Purpose: Returns true is this directory should be excluded from the backup // Created: 28/1/04 // // -------------------------------------------------------------------------- inline bool ExcludeDir(const std::string &rFullDirName) { if(mpExcludeDirs != 0) { return mpExcludeDirs->IsExcluded(rFullDirName); } // If no list, don't exclude anything return false; } // Utility functions -- may do a lot of work bool FindFilename(int64_t ObjectID, int64_t ContainingDirectory, std::string &rPathOut, bool &rIsDirectoryOut, bool &rIsCurrentVersionOut, box_time_t *pModTimeOnServer = 0, box_time_t *pAttributesHashOnServer = 0, BackupStoreFilenameClear *pLeafname = 0); // not const as may connect to server // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::SetMaximumDiffingTime() // Purpose: Sets the maximum time that will be spent diffing a file // Created: 04/19/2005 // // -------------------------------------------------------------------------- void SetMaximumDiffingTime(int iSeconds); // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::SetKeepAliveTime() // Purpose: Sets the time interval for repetitive keep-alive operation // Created: 04/19/2005 // // -------------------------------------------------------------------------- void SetKeepAliveTime(int iSeconds); // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::ManageDiffProcess() // Purpose: Initiates an SSL connection/session keep-alive process // Created: 04/19/2005 // // -------------------------------------------------------------------------- void ManageDiffProcess(); // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::UnManageDiffProcess() // Purpose: Suspends an SSL connection/session keep-alive process // Created: 04/19/2005 // // -------------------------------------------------------------------------- void UnManageDiffProcess(); // ------------------------------------------------------------------- // // Function // Name: BackupClientContext::DoKeepAlive() // Purpose: Check whether it's time to send a KeepAlive // message over the SSL link, and if so, send it. // Created: 04/19/2005 // // ------------------------------------------------------------------- virtual void DoKeepAlive(); virtual int GetMaximumDiffingTime(); virtual bool IsManaged() { return mbIsManaged; } ProgressNotifier& GetProgressNotifier() const { return mrProgressNotifier; } private: LocationResolver &mrResolver; TLSContext &mrTLSContext; std::string mHostname; int mPort; uint32_t mAccountNumber; SocketStreamTLS *mpSocket; BackupProtocolClient *mpConnection; bool mExtendedLogging; bool mExtendedLogToFile; std::string mExtendedLogFile; FILE* mpExtendedLogFileHandle; int64_t mClientStoreMarker; BackupClientDeleteList *mpDeleteList; const BackupClientInodeToIDMap *mpCurrentIDMap; BackupClientInodeToIDMap *mpNewIDMap; bool mStorageLimitExceeded; ExcludeList *mpExcludeFiles; ExcludeList *mpExcludeDirs; Timer mKeepAliveTimer; bool mbIsManaged; int mKeepAliveTime; int mMaximumDiffingTime; ProgressNotifier &mrProgressNotifier; }; #endif // BACKUPCLIENTCONTEXT__H boxbackup/bin/bbackupd/ClientException.txt0000664000175000017500000000076710601327014021477 0ustar siretartsiretart # 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. boxbackup/bin/bbackupd/BackupDaemon.cpp0000664000175000017500000022151611453350134020700 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupDaemon.cpp // Purpose: Backup daemon // Created: 2003/10/08 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SIGNAL_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SYS_MOUNT_H #include #endif #ifdef HAVE_MNTENT_H #include #endif #ifdef HAVE_SYS_MNTTAB_H #include #include #endif #ifdef HAVE_PROCESS_H #include #endif #include #include "Configuration.h" #include "IOStream.h" #include "MemBlockStream.h" #include "CommonException.h" #include "BoxPortsAndFiles.h" #include "SSLLib.h" #include "autogen_BackupProtocolClient.h" #include "autogen_ClientException.h" #include "autogen_ConversionException.h" #include "Archive.h" #include "BackupClientContext.h" #include "BackupClientCryptoKeys.h" #include "BackupClientDirectoryRecord.h" #include "BackupClientFileAttributes.h" #include "BackupClientInodeToIDMap.h" #include "BackupClientMakeExcludeList.h" #include "BackupDaemon.h" #include "BackupDaemonConfigVerify.h" #include "BackupStoreConstants.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" #include "BackupStoreFilenameClear.h" #include "BannerText.h" #include "Conversion.h" #include "ExcludeList.h" #include "FileStream.h" #include "IOStreamGetLine.h" #include "LocalProcessStream.h" #include "Logging.h" #include "Random.h" #include "Timer.h" #include "Utils.h" #ifdef WIN32 #include "Win32ServiceFunctions.h" #include "Win32BackupService.h" extern Win32BackupService* gpDaemonService; #endif #include "MemLeakFindOn.h" static const time_t MAX_SLEEP_TIME = 1024; // Make the actual sync period have a little bit of extra time, up to a 64th of the main sync period. // This prevents repetative cycles of load on the server #define SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY 6 // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::BackupDaemon() // Purpose: constructor // Created: 2003/10/08 // // -------------------------------------------------------------------------- BackupDaemon::BackupDaemon() : mState(BackupDaemon::State_Initialising), mDeleteRedundantLocationsAfter(0), mLastNotifiedEvent(SysadminNotifier::MAX), mDeleteUnusedRootDirEntriesAfter(0), mClientStoreMarker(BackupClientContext::ClientStoreMarker_NotKnown), mStorageLimitExceeded(false), mReadErrorsOnFilesystemObjects(false), mLastSyncTime(0), mNextSyncTime(0), mCurrentSyncStartTime(0), mUpdateStoreInterval(0), mDeleteStoreObjectInfoFile(false), mDoSyncForcedByPreviousSyncError(false), mLogAllFileAccess(false), mpProgressNotifier(this), mpLocationResolver(this), mpRunStatusProvider(this), mpSysadminNotifier(this) #ifdef WIN32 , mInstallService(false), mRemoveService(false), mRunAsService(false), mServiceName("bbackupd") #endif { // Only ever one instance of a daemon SSLLib::Initialise(); } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::~BackupDaemon() // Purpose: Destructor // Created: 2003/10/08 // // -------------------------------------------------------------------------- BackupDaemon::~BackupDaemon() { DeleteAllLocations(); DeleteAllIDMaps(); } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DaemonName() // Purpose: Get name of daemon // Created: 2003/10/08 // // -------------------------------------------------------------------------- const char *BackupDaemon::DaemonName() const { return "bbackupd"; } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DaemonBanner() // Purpose: Daemon banner // Created: 1/1/04 // // -------------------------------------------------------------------------- std::string BackupDaemon::DaemonBanner() const { return BANNER_TEXT("Backup Client"); } void BackupDaemon::Usage() { this->Daemon::Usage(); #ifdef WIN32 std::cout << " -s Run as a Windows Service, for internal use only\n" " -i Install Windows Service (you may want to specify a config file)\n" " -r Remove Windows Service\n" " -S Service name for -i and -r options\n"; #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::GetConfigVerify() // Purpose: Get configuration specification // Created: 2003/10/08 // // -------------------------------------------------------------------------- const ConfigurationVerify *BackupDaemon::GetConfigVerify() const { // Defined elsewhere return &BackupDaemonConfigVerify; } #ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::SetupInInitialProcess() // Purpose: Platforms with non-checkable credentials on // local sockets only. // Prints a warning if the command socket is used. // Created: 25/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::SetupInInitialProcess() { // Print a warning on this platform if the CommandSocket is used. if(GetConfiguration().KeyExists("CommandSocket")) { BOX_WARNING( "==============================================================================\n" "SECURITY WARNING: This platform cannot check the credentials of connections to\n" "the command socket. This is a potential DoS security problem.\n" "Remove the CommandSocket directive from the bbackupd.conf file if bbackupctl\n" "is not used.\n" "==============================================================================\n" ); } } #endif // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DeleteAllLocations() // Purpose: Deletes all records stored // Created: 2003/10/08 // // -------------------------------------------------------------------------- void BackupDaemon::DeleteAllLocations() { // Run through, and delete everything for(std::vector::iterator i = mLocations.begin(); i != mLocations.end(); ++i) { delete *i; } // Clear the contents of the map, so it is empty mLocations.clear(); // And delete everything from the associated mount vector mIDMapMounts.clear(); } #ifdef WIN32 std::string BackupDaemon::GetOptionString() { std::string oldOpts = this->Daemon::GetOptionString(); ASSERT(oldOpts.find("s") == std::string::npos); ASSERT(oldOpts.find("S") == std::string::npos); ASSERT(oldOpts.find("i") == std::string::npos); ASSERT(oldOpts.find("r") == std::string::npos); return oldOpts + "sS:ir"; } int BackupDaemon::ProcessOption(signed int option) { switch(option) { case 's': { mRunAsService = true; return 0; } case 'S': { mServiceName = optarg; Logging::SetProgramName(mServiceName); return 0; } case 'i': { mInstallService = true; return 0; } case 'r': { mRemoveService = true; return 0; } default: { return this->Daemon::ProcessOption(option); } } } int BackupDaemon::Main(const std::string &rConfigFileName) { if (mInstallService) { return InstallService(rConfigFileName.c_str(), mServiceName); } if (mRemoveService) { return RemoveService(mServiceName); } int returnCode; if (mRunAsService) { // We will be called reentrantly by the Service Control // Manager, and we had better not call OurService again! mRunAsService = false; BOX_INFO("Box Backup service starting"); returnCode = OurService(rConfigFileName.c_str()); BOX_INFO("Box Backup service shut down"); } else { returnCode = this->Daemon::Main(rConfigFileName); } return returnCode; } #endif // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::Run() // Purpose: Run function for daemon // Created: 18/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::Run() { // initialise global timer mechanism Timers::Init(); #ifndef WIN32 // Ignore SIGPIPE so that if a command connection is broken, // the daemon doesn't terminate. ::signal(SIGPIPE, SIG_IGN); #endif // Create a command socket? const Configuration &conf(GetConfiguration()); if(conf.KeyExists("CommandSocket")) { // Yes, create a local UNIX socket mapCommandSocketInfo.reset(new CommandSocketInfo); const char *socketName = conf.GetKeyValue("CommandSocket").c_str(); #ifdef WIN32 mapCommandSocketInfo->mListeningSocket.Listen( socketName); #else ::unlink(socketName); mapCommandSocketInfo->mListeningSocket.Listen( Socket::TypeUNIX, socketName); #endif } // Handle things nicely on exceptions try { Run2(); } catch(...) { if(mapCommandSocketInfo.get()) { try { mapCommandSocketInfo.reset(); } catch(std::exception &e) { BOX_WARNING("Internal error while " "closing command socket after " "another exception: " << e.what()); } catch(...) { BOX_WARNING("Error closing command socket " "after exception, ignored."); } } Timers::Cleanup(); throw; } // Clean up mapCommandSocketInfo.reset(); Timers::Cleanup(); } void BackupDaemon::InitCrypto() { // Read in the certificates creating a TLS context const Configuration &conf(GetConfiguration()); std::string certFile(conf.GetKeyValue("CertificateFile")); std::string keyFile(conf.GetKeyValue("PrivateKeyFile")); std::string caFile(conf.GetKeyValue("TrustedCAsFile")); mTlsContext.Initialise(false /* as client */, certFile.c_str(), keyFile.c_str(), caFile.c_str()); // Set up the keys for various things BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::Run2() // Purpose: Run function for daemon (second stage) // Created: 2003/10/08 // // -------------------------------------------------------------------------- void BackupDaemon::Run2() { InitCrypto(); const Configuration &conf(GetConfiguration()); // How often to connect to the store (approximate) mUpdateStoreInterval = SecondsToBoxTime( conf.GetKeyValueInt("UpdateStoreInterval")); // But are we connecting automatically? bool automaticBackup = conf.GetKeyValueBool("AutomaticBackup"); // When the next sync should take place -- which is ASAP mNextSyncTime = 0; // When the last sync started (only updated if the store was not full when the sync ended) mLastSyncTime = 0; // -------------------------------------------------------------------------------------------- mDeleteStoreObjectInfoFile = DeserializeStoreObjectInfo( mLastSyncTime, mNextSyncTime); // -------------------------------------------------------------------------------------------- // Set state SetState(State_Idle); mDoSyncForcedByPreviousSyncError = false; // Loop around doing backups do { // Flags used below bool storageLimitExceeded = false; bool doSync = false; bool mDoSyncForcedByCommand = false; // Is a delay necessary? box_time_t currentTime; do { // Check whether we should be stopping, // and don't run a sync if so. if(StopRun()) break; currentTime = GetCurrentBoxTime(); // Pause a while, but no more than // MAX_SLEEP_TIME seconds (use the conditional // because times are unsigned) box_time_t requiredDelay = (mNextSyncTime < currentTime) ? (0) : (mNextSyncTime - currentTime); // If there isn't automatic backup happening, // set a long delay. And limit delays at the // same time. if(!automaticBackup && !mDoSyncForcedByPreviousSyncError) { requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); } else if(requiredDelay > SecondsToBoxTime(MAX_SLEEP_TIME)) { requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); } // Only delay if necessary if(requiredDelay > 0) { // Sleep somehow. There are choices // on how this should be done, // depending on the state of the // control connection if(mapCommandSocketInfo.get() != 0) { // A command socket exists, // so sleep by waiting on it WaitOnCommandSocket(requiredDelay, doSync, mDoSyncForcedByCommand); } else { // No command socket or // connection, just do a // normal sleep time_t sleepSeconds = BoxTimeToSeconds(requiredDelay); ::sleep((sleepSeconds <= 0) ? 1 : sleepSeconds); } } if ((automaticBackup || mDoSyncForcedByPreviousSyncError) && currentTime >= mNextSyncTime) { doSync = true; } } while(!doSync && !StopRun()); // Time of sync start, and if it's time for another sync // (and we're doing automatic syncs), set the flag mCurrentSyncStartTime = GetCurrentBoxTime(); if((automaticBackup || mDoSyncForcedByPreviousSyncError) && mCurrentSyncStartTime >= mNextSyncTime) { doSync = true; } // Use a script to see if sync is allowed now? if(!mDoSyncForcedByCommand && doSync && !StopRun()) { int d = UseScriptToSeeIfSyncAllowed(); if(d > 0) { // Script has asked for a delay mNextSyncTime = GetCurrentBoxTime() + SecondsToBoxTime(d); doSync = false; } } // Ready to sync? (but only if we're not supposed // to be stopping) if(doSync && !StopRun()) { RunSyncNowWithExceptionHandling(); } // Set state SetState(storageLimitExceeded?State_StorageLimitExceeded:State_Idle); } while(!StopRun()); // Make sure we have a clean start next time round (if restart) DeleteAllLocations(); DeleteAllIDMaps(); } void BackupDaemon::RunSyncNowWithExceptionHandling() { OnBackupStart(); // Do sync bool errorOccurred = false; int errorCode = 0, errorSubCode = 0; const char* errorString = "unknown"; try { RunSyncNow(); } catch(BoxException &e) { errorOccurred = true; errorString = e.what(); errorCode = e.GetType(); errorSubCode = e.GetSubType(); } catch(std::exception &e) { BOX_ERROR("Internal error during backup run: " << e.what()); errorOccurred = true; errorString = e.what(); } catch(...) { // TODO: better handling of exceptions here... // need to be very careful errorOccurred = true; } // do not retry immediately without a good reason mDoSyncForcedByPreviousSyncError = false; if(errorOccurred) { // Is it a berkely db failure? bool isBerkelyDbFailure = false; if (errorCode == BackupStoreException::ExceptionType && errorSubCode == BackupStoreException::BerkelyDBFailure) { isBerkelyDbFailure = true; } if(isBerkelyDbFailure) { // Delete corrupt files DeleteCorruptBerkelyDbFiles(); } // Clear state data // Go back to beginning of time mLastSyncTime = 0; mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; // no store marker, so download everything DeleteAllLocations(); DeleteAllIDMaps(); // Handle restart? if(StopRun()) { BOX_NOTICE("Exception (" << errorCode << "/" << errorSubCode << ") due to signal"); OnBackupFinish(); return; } NotifySysadmin(SysadminNotifier::BackupError); // If the Berkely db files get corrupted, // delete them and try again immediately. if(isBerkelyDbFailure) { BOX_ERROR("Berkely db inode map files corrupted, " "deleting and restarting scan. Renamed files " "and directories will not be tracked until " "after this scan."); ::sleep(1); } else { // Not restart/terminate, pause and retry // Notify administrator SetState(State_Error); BOX_ERROR("Exception caught (" << errorString << " " << errorCode << "/" << errorSubCode << "), reset state and waiting to retry..."); ::sleep(10); mNextSyncTime = mCurrentSyncStartTime + SecondsToBoxTime(100) + Random::RandomInt(mUpdateStoreInterval >> SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); } } // Notify system administrator about the final state of the backup else if(mReadErrorsOnFilesystemObjects) { NotifySysadmin(SysadminNotifier::ReadError); } else if(mStorageLimitExceeded) { NotifySysadmin(SysadminNotifier::StoreFull); } else { NotifySysadmin(SysadminNotifier::BackupOK); } // If we were retrying after an error, and this backup succeeded, // then now would be a good time to stop :-) mDoSyncForcedByPreviousSyncError = errorOccurred; OnBackupFinish(); } void BackupDaemon::RunSyncNow() { // Delete the serialised store object file, // so that we don't try to reload it after a // partially completed backup if(mDeleteStoreObjectInfoFile && !DeleteStoreObjectInfo()) { BOX_ERROR("Failed to delete the StoreObjectInfoFile, " "backup cannot continue safely."); THROW_EXCEPTION(ClientException, FailedToDeleteStoreObjectInfoFile); } // In case the backup throws an exception, // we should not try to delete the store info // object file again. mDeleteStoreObjectInfoFile = false; const Configuration &conf(GetConfiguration()); std::auto_ptr fileLogger; if (conf.KeyExists("LogFile")) { Log::Level level = Log::INFO; if (conf.KeyExists("LogFileLevel")) { level = Logging::GetNamedLevel( conf.GetKeyValue("LogFileLevel")); } fileLogger.reset(new FileLogger(conf.GetKeyValue("LogFile"), level)); } std::string extendedLogFile; if (conf.KeyExists("ExtendedLogFile")) { extendedLogFile = conf.GetKeyValue("ExtendedLogFile"); } if (conf.KeyExists("LogAllFileAccess")) { mLogAllFileAccess = conf.GetKeyValueBool("LogAllFileAccess"); } // Then create a client context object (don't // just connect, as this may be unnecessary) BackupClientContext clientContext ( *mpLocationResolver, mTlsContext, conf.GetKeyValue("StoreHostname"), conf.GetKeyValueInt("StorePort"), conf.GetKeyValueUint32("AccountNumber"), conf.GetKeyValueBool("ExtendedLogging"), conf.KeyExists("ExtendedLogFile"), extendedLogFile, *mpProgressNotifier ); // The minimum age a file needs to be before it will be // considered for uploading box_time_t minimumFileAge = SecondsToBoxTime( conf.GetKeyValueInt("MinimumFileAge")); // The maximum time we'll wait to upload a file, regardless // of how often it's modified box_time_t maxUploadWait = SecondsToBoxTime( conf.GetKeyValueInt("MaxUploadWait")); // Adjust by subtracting the minimum file age, so is relative // to sync period end in comparisons if (maxUploadWait > minimumFileAge) { maxUploadWait -= minimumFileAge; } else { maxUploadWait = 0; } // Calculate the sync period of files to examine box_time_t syncPeriodStart = mLastSyncTime; box_time_t syncPeriodEnd = GetCurrentBoxTime() - minimumFileAge; if(syncPeriodStart >= syncPeriodEnd && syncPeriodStart - syncPeriodEnd < minimumFileAge) { // This can happen if we receive a force-sync command less // than minimumFileAge after the last sync. Deal with it by // moving back syncPeriodStart, which should not do any // damage. syncPeriodStart = syncPeriodEnd - SecondsToBoxTime(1); } if(syncPeriodStart >= syncPeriodEnd) { BOX_ERROR("Invalid (negative) sync period: " "perhaps your clock is going " "backwards (" << syncPeriodStart << " to " << syncPeriodEnd << ")"); THROW_EXCEPTION(ClientException, ClockWentBackwards); } // Check logic ASSERT(syncPeriodEnd > syncPeriodStart); // Paranoid check on sync times if(syncPeriodStart >= syncPeriodEnd) return; // Adjust syncPeriodEnd to emulate snapshot // behaviour properly box_time_t syncPeriodEndExtended = syncPeriodEnd; // Using zero min file age? if(minimumFileAge == 0) { // Add a year on to the end of the end time, // to make sure we sync files which are // modified after the scan run started. // Of course, they may be eligible to be // synced again the next time round, // but this should be OK, because the changes // only upload should upload no data. syncPeriodEndExtended += SecondsToBoxTime( (time_t)(356*24*3600)); } // Set up the sync parameters BackupClientDirectoryRecord::SyncParams params(*mpRunStatusProvider, *mpSysadminNotifier, *mpProgressNotifier, clientContext); params.mSyncPeriodStart = syncPeriodStart; params.mSyncPeriodEnd = syncPeriodEndExtended; // use potentially extended end time params.mMaxUploadWait = maxUploadWait; params.mFileTrackingSizeThreshold = conf.GetKeyValueInt("FileTrackingSizeThreshold"); params.mDiffingUploadSizeThreshold = conf.GetKeyValueInt("DiffingUploadSizeThreshold"); params.mMaxFileTimeInFuture = SecondsToBoxTime(conf.GetKeyValueInt("MaxFileTimeInFuture")); mDeleteRedundantLocationsAfter = conf.GetKeyValueInt("DeleteRedundantLocationsAfter"); mStorageLimitExceeded = false; mReadErrorsOnFilesystemObjects = false; // Setup various timings int maximumDiffingTime = 600; int keepAliveTime = 60; // max diffing time, keep-alive time if(conf.KeyExists("MaximumDiffingTime")) { maximumDiffingTime = conf.GetKeyValueInt("MaximumDiffingTime"); } if(conf.KeyExists("KeepAliveTime")) { keepAliveTime = conf.GetKeyValueInt("KeepAliveTime"); } clientContext.SetMaximumDiffingTime(maximumDiffingTime); clientContext.SetKeepAliveTime(keepAliveTime); // Set store marker clientContext.SetClientStoreMarker(mClientStoreMarker); // Set up the locations, if necessary -- // need to do it here so we have a // (potential) connection to use if(mLocations.empty()) { const Configuration &locations( conf.GetSubConfiguration( "BackupLocations")); // Make sure all the directory records // are set up SetupLocations(clientContext, locations); } mpProgressNotifier->NotifyIDMapsSetup(clientContext); // Get some ID maps going SetupIDMapsForSync(); // Delete any unused directories? DeleteUnusedRootDirEntries(clientContext); // Go through the records, syncing them for(std::vector::const_iterator i(mLocations.begin()); i != mLocations.end(); ++i) { // Set current and new ID map pointers // in the context clientContext.SetIDMaps(mCurrentIDMaps[(*i)->mIDMapIndex], mNewIDMaps[(*i)->mIDMapIndex]); // Set exclude lists (context doesn't // take ownership) clientContext.SetExcludeLists( (*i)->mpExcludeFiles, (*i)->mpExcludeDirs); // Sync the directory (*i)->mpDirectoryRecord->SyncDirectory( params, BackupProtocolClientListDirectory::RootDirectory, (*i)->mPath, std::string("/") + (*i)->mName); // Unset exclude lists (just in case) clientContext.SetExcludeLists(0, 0); } // Perform any deletions required -- these are // delayed until the end to allow renaming to // happen neatly. clientContext.PerformDeletions(); // Close any open connection clientContext.CloseAnyOpenConnection(); // Get the new store marker mClientStoreMarker = clientContext.GetClientStoreMarker(); mStorageLimitExceeded = clientContext.StorageLimitExceeded(); mReadErrorsOnFilesystemObjects = params.mReadErrorsOnFilesystemObjects; if(!mStorageLimitExceeded) { // The start time of the next run is the end time of this // run. This is only done if the storage limit wasn't // exceeded (as things won't have been done properly if // it was) mLastSyncTime = syncPeriodEnd; } // Commit the ID Maps CommitIDMapsAfterSync(); // Calculate when the next sync run should be mNextSyncTime = mCurrentSyncStartTime + mUpdateStoreInterval + Random::RandomInt(mUpdateStoreInterval >> SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); // -------------------------------------------------------------------------------------------- // We had a successful backup, save the store // info. If we save successfully, we must // delete the file next time we start a backup mDeleteStoreObjectInfoFile = SerializeStoreObjectInfo(mLastSyncTime, mNextSyncTime); // -------------------------------------------------------------------------------------------- } void BackupDaemon::OnBackupStart() { // Touch a file to record times in filesystem TouchFileInWorkingDir("last_sync_start"); // Reset statistics on uploads BackupStoreFile::ResetStats(); // Tell anything connected to the command socket SendSyncStartOrFinish(true /* start */); // Notify administrator NotifySysadmin(SysadminNotifier::BackupStart); // Set state and log start SetState(State_Connected); BOX_NOTICE("Beginning scan of local files"); } void BackupDaemon::OnBackupFinish() { // Log BOX_NOTICE("Finished scan of local files"); // Log the stats BOX_NOTICE("File statistics: total file size uploaded " << BackupStoreFile::msStats.mBytesInEncodedFiles << ", bytes already on server " << BackupStoreFile::msStats.mBytesAlreadyOnServer << ", encoded size " << BackupStoreFile::msStats.mTotalFileStreamSize); // Reset statistics again BackupStoreFile::ResetStats(); // Notify administrator NotifySysadmin(SysadminNotifier::BackupFinish); // Tell anything connected to the command socket SendSyncStartOrFinish(false /* finish */); // Touch a file to record times in filesystem TouchFileInWorkingDir("last_sync_finish"); } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::UseScriptToSeeIfSyncAllowed() // Purpose: Private. Use a script to see if the sync should be // allowed now (if configured). Returns -1 if it's // allowed, time in seconds to wait otherwise. // Created: 21/6/04 // // -------------------------------------------------------------------------- int BackupDaemon::UseScriptToSeeIfSyncAllowed() { const Configuration &conf(GetConfiguration()); // Got a script to run? if(!conf.KeyExists("SyncAllowScript")) { // No. Do sync. return -1; } // If there's no result, try again in five minutes int waitInSeconds = (60*5); std::string script(conf.GetKeyValue("SyncAllowScript") + " \"" + GetConfigFileName() + "\""); // Run it? pid_t pid = 0; try { std::auto_ptr pscript(LocalProcessStream(script, pid)); // Read in the result IOStreamGetLine getLine(*pscript); std::string line; if(getLine.GetLine(line, true, 30000)) // 30 seconds should be enough { // Got a string, interpret if(line == "now") { // Script says do it now. Obey. waitInSeconds = -1; } else { try { // How many seconds to wait? waitInSeconds = BoxConvert::Convert(line); } catch(ConversionException &e) { BOX_ERROR("Invalid output from " "SyncAllowScript: '" << line << "' (" << script << ")"); throw; } BOX_NOTICE("Delaying sync by " << waitInSeconds << " seconds due to SyncAllowScript " << "(" << script << ")"); } } } catch(std::exception &e) { BOX_ERROR("Internal error running SyncAllowScript: " << e.what() << " (" << script << ")"); } catch(...) { // Ignore any exceptions // Log that something bad happened BOX_ERROR("Unknown error running SyncAllowScript (" << script << ")"); } // Wait and then cleanup child process, if any if(pid != 0) { int status = 0; ::waitpid(pid, &status, 0); } return waitInSeconds; } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::WaitOnCommandSocket(box_time_t, bool &, bool &) // Purpose: Waits on a the command socket for a time of UP TO the required time // but may be much less, and handles a command if necessary. // Created: 18/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut) { ASSERT(mapCommandSocketInfo.get()); if(!mapCommandSocketInfo.get()) { // failure case isn't too bad ::sleep(1); return; } BOX_TRACE("Wait on command socket, delay = " << RequiredDelay); try { // Timeout value for connections and things int timeout = ((int)BoxTimeToMilliSeconds(RequiredDelay)) + 1; // Handle bad boundary cases if(timeout <= 0) timeout = 1; if(timeout == INFTIM) timeout = 100000; // Wait for socket connection, or handle a command? if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { // No connection, listen for a new one mapCommandSocketInfo->mpConnectedSocket.reset(mapCommandSocketInfo->mListeningSocket.Accept(timeout).release()); if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { // If a connection didn't arrive, there was a timeout, which means we've // waited long enough and it's time to go. return; } else { #ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET bool uidOK = true; BOX_WARNING("On this platform, no security check can be made on the credentials of peers connecting to the command socket. (bbackupctl)"); #else // Security check -- does the process connecting to this socket have // the same UID as this process? bool uidOK = false; // BLOCK { uid_t remoteEUID = 0xffff; gid_t remoteEGID = 0xffff; if(mapCommandSocketInfo->mpConnectedSocket->GetPeerCredentials(remoteEUID, remoteEGID)) { // Credentials are available -- check UID if(remoteEUID == ::getuid()) { // Acceptable uidOK = true; } } } #endif // PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET // Is this an acceptable connection? if(!uidOK) { // Dump the connection BOX_ERROR("Incoming command connection from peer had different user ID than this process, or security check could not be completed."); mapCommandSocketInfo->mpConnectedSocket.reset(); return; } else { // Log BOX_INFO("Connection from command socket"); // Send a header line summarising the configuration and current state const Configuration &conf(GetConfiguration()); char summary[256]; int summarySize = sprintf(summary, "bbackupd: %d %d %d %d\nstate %d\n", conf.GetKeyValueBool("AutomaticBackup"), conf.GetKeyValueInt("UpdateStoreInterval"), conf.GetKeyValueInt("MinimumFileAge"), conf.GetKeyValueInt("MaxUploadWait"), mState); mapCommandSocketInfo->mpConnectedSocket->Write(summary, summarySize); // Set the timeout to something very small, so we don't wait too long on waiting // for any incoming data timeout = 10; // milliseconds } } } // So there must be a connection now. ASSERT(mapCommandSocketInfo->mpConnectedSocket.get() != 0); // Is there a getline object ready? if(mapCommandSocketInfo->mpGetLine == 0) { // Create a new one mapCommandSocketInfo->mpGetLine = new IOStreamGetLine(*(mapCommandSocketInfo->mpConnectedSocket.get())); } // Ping the remote side, to provide errors which will mean the socket gets closed mapCommandSocketInfo->mpConnectedSocket->Write("ping\n", 5); // Wait for a command or something on the socket std::string command; while(mapCommandSocketInfo->mpGetLine != 0 && !mapCommandSocketInfo->mpGetLine->IsEOF() && mapCommandSocketInfo->mpGetLine->GetLine(command, false /* no preprocessing */, timeout)) { BOX_TRACE("Receiving command '" << command << "' over command socket"); bool sendOK = false; bool sendResponse = true; // Command to process! if(command == "quit" || command == "") { // Close the socket. CloseCommandConnection(); sendResponse = false; } else if(command == "sync") { // Sync now! DoSyncFlagOut = true; SyncIsForcedOut = false; sendOK = true; } else if(command == "force-sync") { // Sync now (forced -- overrides any SyncAllowScript) DoSyncFlagOut = true; SyncIsForcedOut = true; sendOK = true; } else if(command == "reload") { // Reload the configuration SetReloadConfigWanted(); sendOK = true; } else if(command == "terminate") { // Terminate the daemon cleanly SetTerminateWanted(); sendOK = true; } // Send a response back? if(sendResponse) { mapCommandSocketInfo->mpConnectedSocket->Write(sendOK?"ok\n":"error\n", sendOK?3:6); } // Set timeout to something very small, so this just checks for data which is waiting timeout = 1; } // Close on EOF? if(mapCommandSocketInfo->mpGetLine != 0 && mapCommandSocketInfo->mpGetLine->IsEOF()) { CloseCommandConnection(); } } catch(ConnectionException &ce) { BOX_NOTICE("Failed to write to command socket: " << ce.what()); // If an error occurs, and there is a connection active, // just close that connection and continue. Otherwise, // let the error propagate. if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { throw; // thread will die } else { // Close socket and ignore error CloseCommandConnection(); } } catch(std::exception &e) { BOX_ERROR("Failed to write to command socket: " << e.what()); // If an error occurs, and there is a connection active, // just close that connection and continue. Otherwise, // let the error propagate. if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { throw; // thread will die } else { // Close socket and ignore error CloseCommandConnection(); } } catch(...) { BOX_ERROR("Failed to write to command socket: unknown error"); // If an error occurs, and there is a connection active, // just close that connection and continue. Otherwise, // let the error propagate. if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { throw; // thread will die } else { // Close socket and ignore error CloseCommandConnection(); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::CloseCommandConnection() // Purpose: Close the command connection, ignoring any errors // Created: 18/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::CloseCommandConnection() { try { BOX_TRACE("Closing command connection"); if(mapCommandSocketInfo->mpGetLine) { delete mapCommandSocketInfo->mpGetLine; mapCommandSocketInfo->mpGetLine = 0; } mapCommandSocketInfo->mpConnectedSocket.reset(); } catch(std::exception &e) { BOX_ERROR("Internal error while closing command " "socket: " << e.what()); } catch(...) { // Ignore any errors } } // -------------------------------------------------------------------------- // // File // Name: BackupDaemon.cpp // Purpose: Send a start or finish sync message to the command socket, if it's connected. // // Created: 18/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::SendSyncStartOrFinish(bool SendStart) { // The bbackupctl program can't rely on a state change, because it // may never change if the server doesn't need to be contacted. if(mapCommandSocketInfo.get() && mapCommandSocketInfo->mpConnectedSocket.get() != 0) { std::string message = SendStart ? "start-sync" : "finish-sync"; try { message += "\n"; mapCommandSocketInfo->mpConnectedSocket->Write( message.c_str(), message.size()); } catch(std::exception &e) { BOX_ERROR("Internal error while sending to " "command socket client: " << e.what()); CloseCommandConnection(); } catch(...) { CloseCommandConnection(); } } } #if !defined(HAVE_STRUCT_STATFS_F_MNTONNAME) && !defined(HAVE_STRUCT_STATVFS_F_NMTONNAME) // string comparison ordering for when mount points are handled // by code, rather than the OS. typedef struct { bool operator()(const std::string &s1, const std::string &s2) { if(s1.size() == s2.size()) { // Equal size, sort according to natural sort order return s1 < s2; } else { // Make sure longer strings go first return s1.size() > s2.size(); } } } mntLenCompare; #endif // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::SetupLocations(BackupClientContext &, const Configuration &) // Purpose: Makes sure that the list of directories records is correctly set up // Created: 2003/10/08 // // -------------------------------------------------------------------------- void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Configuration &rLocationsConf) { if(!mLocations.empty()) { // Looks correctly set up return; } // Make sure that if a directory is reinstated, then it doesn't get deleted mDeleteUnusedRootDirEntriesAfter = 0; mUnusedRootDirEntries.clear(); // Just a check to make sure it's right. DeleteAllLocations(); // Going to need a copy of the root directory. Get a connection, // and fetch it. BackupProtocolClient &connection(rClientContext.GetConnection()); // Ask server for a list of everything in the root directory, // which is a directory itself std::auto_ptr dirreply( connection.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, // only directories BackupProtocolClientListDirectory::Flags_Dir, // exclude old/deleted stuff BackupProtocolClientListDirectory::Flags_Deleted | BackupProtocolClientListDirectory::Flags_OldVersion, false /* no attributes */)); // Retrieve the directory from the stream following BackupStoreDirectory dir; std::auto_ptr dirstream(connection.ReceiveStream()); dir.ReadFromStream(*dirstream, connection.GetTimeout()); // Map of mount names to ID map index std::map mounts; int numIDMaps = 0; #ifdef HAVE_MOUNTS #if !defined(HAVE_STRUCT_STATFS_F_MNTONNAME) && !defined(HAVE_STRUCT_STATVFS_F_MNTONNAME) // Linux and others can't tell you where a directory is mounted. So we // have to read the mount entries from /etc/mtab! Bizarre that the OS // itself can't tell you, but there you go. std::set mountPoints; // BLOCK FILE *mountPointsFile = 0; #ifdef HAVE_STRUCT_MNTENT_MNT_DIR // Open mounts file mountPointsFile = ::setmntent("/proc/mounts", "r"); if(mountPointsFile == 0) { mountPointsFile = ::setmntent("/etc/mtab", "r"); } if(mountPointsFile == 0) { THROW_EXCEPTION(CommonException, OSFileError); } try { // Read all the entries, and put them in the set struct mntent *entry = 0; while((entry = ::getmntent(mountPointsFile)) != 0) { BOX_TRACE("Found mount point at " << entry->mnt_dir); mountPoints.insert(std::string(entry->mnt_dir)); } // Close mounts file ::endmntent(mountPointsFile); } catch(...) { ::endmntent(mountPointsFile); throw; } #else // ! HAVE_STRUCT_MNTENT_MNT_DIR // Open mounts file mountPointsFile = ::fopen("/etc/mnttab", "r"); if(mountPointsFile == 0) { THROW_EXCEPTION(CommonException, OSFileError); } try { // Read all the entries, and put them in the set struct mnttab entry; while(getmntent(mountPointsFile, &entry) == 0) { BOX_TRACE("Found mount point at " << entry.mnt_mountp); mountPoints.insert(std::string(entry.mnt_mountp)); } // Close mounts file ::fclose(mountPointsFile); } catch(...) { ::fclose(mountPointsFile); throw; } #endif // HAVE_STRUCT_MNTENT_MNT_DIR // Check sorting and that things are as we expect ASSERT(mountPoints.size() > 0); #ifndef BOX_RELEASE_BUILD { std::set::reverse_iterator i(mountPoints.rbegin()); ASSERT(*i == "/"); } #endif // n BOX_RELEASE_BUILD #endif // n HAVE_STRUCT_STATFS_F_MNTONNAME || n HAVE_STRUCT_STATVFS_F_MNTONNAME #endif // HAVE_MOUNTS // Then... go through each of the entries in the configuration, // making sure there's a directory created for it. std::vector locNames = rLocationsConf.GetSubConfigurationNames(); for(std::vector::iterator pLocName = locNames.begin(); pLocName != locNames.end(); pLocName++) { const Configuration& rConfig( rLocationsConf.GetSubConfiguration(*pLocName)); BOX_TRACE("new location: " << *pLocName); // Create a record for it std::auto_ptr apLoc(new Location); try { // Setup names in the location record apLoc->mName = *pLocName; apLoc->mPath = rConfig.GetKeyValue("Path"); // Read the exclude lists from the Configuration apLoc->mpExcludeFiles = BackupClientMakeExcludeList_Files(rConfig); apLoc->mpExcludeDirs = BackupClientMakeExcludeList_Dirs(rConfig); // Does this exist on the server? // Remove from dir object early, so that if we fail // to stat the local directory, we still don't // consider to remote one for deletion. BackupStoreDirectory::Iterator iter(dir); BackupStoreFilenameClear dirname(apLoc->mName); // generate the filename BackupStoreDirectory::Entry *en = iter.FindMatchingClearName(dirname); int64_t oid = 0; if(en != 0) { oid = en->GetObjectID(); // Delete the entry from the directory, so we get a list of // unused root directories at the end of this. dir.DeleteEntry(oid); } // Do a fsstat on the pathname to find out which mount it's on { #if defined HAVE_STRUCT_STATFS_F_MNTONNAME || defined HAVE_STRUCT_STATVFS_F_MNTONNAME || defined WIN32 // BSD style statfs -- includes mount point, which is nice. #ifdef HAVE_STRUCT_STATVFS_F_MNTONNAME struct statvfs s; if(::statvfs(apLoc->mPath.c_str(), &s) != 0) #else // HAVE_STRUCT_STATVFS_F_MNTONNAME struct statfs s; if(::statfs(apLoc->mPath.c_str(), &s) != 0) #endif // HAVE_STRUCT_STATVFS_F_MNTONNAME { BOX_LOG_SYS_WARNING("Failed to stat location " "path '" << apLoc->mPath << "', skipping location '" << apLoc->mName << "'"); continue; } // Where the filesystem is mounted std::string mountName(s.f_mntonname); #else // !HAVE_STRUCT_STATFS_F_MNTONNAME && !WIN32 // Warn in logs if the directory isn't absolute if(apLoc->mPath[0] != '/') { BOX_WARNING("Location path '" << apLoc->mPath << "' is not absolute"); } // Go through the mount points found, and find a suitable one std::string mountName("/"); { std::set::const_iterator i(mountPoints.begin()); BOX_TRACE(mountPoints.size() << " potential mount points"); for(; i != mountPoints.end(); ++i) { // Compare first n characters with the filename // If it matches, the file belongs in that mount point // (sorting order ensures this) BOX_TRACE("checking against mount point " << *i); if(::strncmp(i->c_str(), apLoc->mPath.c_str(), i->size()) == 0) { // Match mountName = *i; break; } } BOX_TRACE("mount point chosen for " << apLoc->mPath << " is " << mountName); } #endif // Got it? std::map::iterator f(mounts.find(mountName)); if(f != mounts.end()) { // Yes -- store the index apLoc->mIDMapIndex = f->second; } else { // No -- new index apLoc->mIDMapIndex = numIDMaps; mounts[mountName] = numIDMaps; // Store the mount name mIDMapMounts.push_back(mountName); // Increment number of maps ++numIDMaps; } } // Does this exist on the server? if(en == 0) { // Doesn't exist, so it has to be created on the server. Let's go! // First, get the directory's attributes and modification time box_time_t attrModTime = 0; BackupClientFileAttributes attr; try { attr.ReadAttributes(apLoc->mPath.c_str(), true /* directories have zero mod times */, 0 /* not interested in mod time */, &attrModTime /* get the attribute modification time */); } catch (BoxException &e) { BOX_ERROR("Failed to get attributes " "for path '" << apLoc->mPath << "', skipping location '" << apLoc->mName << "'"); continue; } // Execute create directory command try { MemBlockStream attrStream(attr); std::auto_ptr dirCreate(connection.QueryCreateDirectory( BackupProtocolClientListDirectory::RootDirectory, attrModTime, dirname, attrStream)); // Object ID for later creation oid = dirCreate->GetObjectID(); } catch (BoxException &e) { BOX_ERROR("Failed to create remote " "directory '/" << apLoc->mName << "', skipping location '" << apLoc->mName << "'"); continue; } } // Create and store the directory object for the root of this location ASSERT(oid != 0); BackupClientDirectoryRecord *precord = new BackupClientDirectoryRecord(oid, *pLocName); apLoc->mpDirectoryRecord.reset(precord); // Push it back on the vector of locations mLocations.push_back(apLoc.release()); } catch (std::exception &e) { BOX_ERROR("Failed to configure location '" << apLoc->mName << "' path '" << apLoc->mPath << "': " << e.what() << ": please check for previous errors"); throw; } catch(...) { BOX_ERROR("Failed to configure location '" << apLoc->mName << "' path '" << apLoc->mPath << "': please check for " "previous errors"); throw; } } // Any entries in the root directory which need deleting? if(dir.GetNumberOfEntries() > 0 && mDeleteRedundantLocationsAfter == 0) { BOX_NOTICE(dir.GetNumberOfEntries() << " redundant locations " "in root directory found, but will not delete because " "DeleteRedundantLocationsAfter = 0"); } else if(dir.GetNumberOfEntries() > 0) { box_time_t now = GetCurrentBoxTime(); // This should reset the timer if the list of unused // locations changes, but it will not if the number of // unused locations does not change, but the locations // do change, e.g. one mysteriously appears and another // mysteriously appears. (FIXME) if (dir.GetNumberOfEntries() != mUnusedRootDirEntries.size() || mDeleteUnusedRootDirEntriesAfter == 0) { mDeleteUnusedRootDirEntriesAfter = now + SecondsToBoxTime(mDeleteRedundantLocationsAfter); } int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter - now); BOX_NOTICE(dir.GetNumberOfEntries() << " redundant locations " "in root directory found, will delete from store " "after " << secs << " seconds."); // Store directories in list of things to delete mUnusedRootDirEntries.clear(); BackupStoreDirectory::Iterator iter(dir); BackupStoreDirectory::Entry *en = 0; while((en = iter.Next()) != 0) { // Add name to list BackupStoreFilenameClear clear(en->GetName()); const std::string &name(clear.GetClearFilename()); mUnusedRootDirEntries.push_back( std::pair (en->GetObjectID(), name)); // Log this BOX_INFO("Unused location in root: " << name); } ASSERT(mUnusedRootDirEntries.size() > 0); } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::SetupIDMapsForSync() // Purpose: Sets up ID maps for the sync process -- make sure they're all there // Created: 11/11/03 // // -------------------------------------------------------------------------- void BackupDaemon::SetupIDMapsForSync() { // Need to do different things depending on whether it's an // in memory implementation, or whether it's all stored on disc. #ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION // Make sure we have some blank, empty ID maps DeleteIDMapVector(mNewIDMaps); FillIDMapVector(mNewIDMaps, true /* new maps */); // Then make sure that the current maps have objects, // even if they are empty (for the very first run) if(mCurrentIDMaps.empty()) { FillIDMapVector(mCurrentIDMaps, false /* current maps */); } #else // Make sure we have some blank, empty ID maps DeleteIDMapVector(mNewIDMaps); FillIDMapVector(mNewIDMaps, true /* new maps */); DeleteIDMapVector(mCurrentIDMaps); FillIDMapVector(mCurrentIDMaps, false /* new maps */); #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::FillIDMapVector(std::vector &) // Purpose: Fills the vector with the right number of empty ID maps // Created: 11/11/03 // // -------------------------------------------------------------------------- void BackupDaemon::FillIDMapVector(std::vector &rVector, bool NewMaps) { ASSERT(rVector.size() == 0); rVector.reserve(mIDMapMounts.size()); for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) { // Create the object BackupClientInodeToIDMap *pmap = new BackupClientInodeToIDMap(); try { // Get the base filename of this map std::string filename; MakeMapBaseName(l, filename); // If it's a new one, add a suffix if(NewMaps) { filename += ".n"; } // If it's not a new map, it may not exist in which case an empty map should be created if(!NewMaps && !FileExists(filename.c_str())) { pmap->OpenEmpty(); } else { // Open the map pmap->Open(filename.c_str(), !NewMaps /* read only */, NewMaps /* create new */); } // Store on vector rVector.push_back(pmap); } catch(...) { delete pmap; throw; } } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DeleteCorruptBerkelyDbFiles() // Purpose: Delete the Berkely db files from disc after they have been corrupted. // Created: 14/9/04 // // -------------------------------------------------------------------------- void BackupDaemon::DeleteCorruptBerkelyDbFiles() { for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) { // Get the base filename of this map std::string filename; MakeMapBaseName(l, filename); // Delete the file BOX_TRACE("Deleting " << filename); ::unlink(filename.c_str()); // Add a suffix for the new map filename += ".n"; // Delete that too BOX_TRACE("Deleting " << filename); ::unlink(filename.c_str()); } } // -------------------------------------------------------------------------- // // Function // Name: MakeMapBaseName(unsigned int, std::string &) // Purpose: Makes the base name for a inode map // Created: 20/11/03 // // -------------------------------------------------------------------------- void BackupDaemon::MakeMapBaseName(unsigned int MountNumber, std::string &rNameOut) const { // Get the directory for the maps const Configuration &config(GetConfiguration()); std::string dir(config.GetKeyValue("DataDirectory")); // Make a leafname std::string leaf(mIDMapMounts[MountNumber]); for(unsigned int z = 0; z < leaf.size(); ++z) { if(leaf[z] == DIRECTORY_SEPARATOR_ASCHAR) { leaf[z] = '_'; } } // Build the final filename rNameOut = dir + DIRECTORY_SEPARATOR "mnt" + leaf; } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::CommitIDMapsAfterSync() // Purpose: Commits the new ID maps, so the 'new' maps are now the 'current' maps. // Created: 11/11/03 // // -------------------------------------------------------------------------- void BackupDaemon::CommitIDMapsAfterSync() { // Need to do different things depending on whether it's an in memory implementation, // or whether it's all stored on disc. #ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION // Remove the current ID maps DeleteIDMapVector(mCurrentIDMaps); // Copy the (pointers to) "new" maps over to be the new "current" maps mCurrentIDMaps = mNewIDMaps; // Clear the new ID maps vector (not delete them!) mNewIDMaps.clear(); #else // Get rid of the maps in memory (leaving them on disc of course) DeleteIDMapVector(mCurrentIDMaps); DeleteIDMapVector(mNewIDMaps); // Then move the old maps into the new places for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) { std::string target; MakeMapBaseName(l, target); std::string newmap(target + ".n"); // Try to rename #ifdef WIN32 // win32 rename doesn't overwrite existing files ::remove(target.c_str()); #endif if(::rename(newmap.c_str(), target.c_str()) != 0) { BOX_LOG_SYS_ERROR("Failed to rename ID map: " << newmap << " to " << target); THROW_EXCEPTION(CommonException, OSFileError) } } #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DeleteIDMapVector(std::vector &) // Purpose: Deletes the contents of a vector of ID maps // Created: 11/11/03 // // -------------------------------------------------------------------------- void BackupDaemon::DeleteIDMapVector(std::vector &rVector) { while(!rVector.empty()) { // Pop off list BackupClientInodeToIDMap *toDel = rVector.back(); rVector.pop_back(); // Close and delete toDel->Close(); delete toDel; } ASSERT(rVector.size() == 0); } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::FindLocationPathName(const std::string &, std::string &) const // Purpose: Tries to find the path of the root of a backup location. Returns true (and path in rPathOut) // if it can be found, false otherwise. // Created: 12/11/03 // // -------------------------------------------------------------------------- bool BackupDaemon::FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const { // Search for the location for(std::vector::const_iterator i(mLocations.begin()); i != mLocations.end(); ++i) { if((*i)->mName == rLocationName) { rPathOut = (*i)->mPath; return true; } } // Didn't find it return false; } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::SetState(int) // Purpose: Record current action of daemon, and update process title to reflect this // Created: 11/12/03 // // -------------------------------------------------------------------------- void BackupDaemon::SetState(int State) { // Two little checks if(State == mState) return; if(State < 0) return; // Update mState = State; // Set process title const static char *stateText[] = {"idle", "connected", "error -- waiting for retry", "over limit on server -- not backing up"}; SetProcessTitle(stateText[State]); // If there's a command socket connected, then inform it -- disconnecting from the // command socket if there's an error char newState[64]; sprintf(newState, "state %d", State); std::string message = newState; message += "\n"; if(!mapCommandSocketInfo.get()) { return; } if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { return; } // Something connected to the command socket, tell it about the new state try { mapCommandSocketInfo->mpConnectedSocket->Write(message.c_str(), message.length()); } catch(ConnectionException &ce) { BOX_NOTICE("Failed to write state to command socket: " << ce.what()); CloseCommandConnection(); } catch(std::exception &e) { BOX_ERROR("Failed to write state to command socket: " << e.what()); CloseCommandConnection(); } catch(...) { BOX_ERROR("Failed to write state to command socket: " "unknown error"); CloseCommandConnection(); } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::TouchFileInWorkingDir(const char *) // Purpose: Make sure a zero length file of the name exists in the working directory. // Use for marking times of events in the filesystem. // Created: 21/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::TouchFileInWorkingDir(const char *Filename) { // Filename const Configuration &config(GetConfiguration()); std::string fn(config.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR); fn += Filename; // Open and close it to update the timestamp FileStream touch(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::NotifySysadmin(int) // Purpose: Run the script to tell the sysadmin about events // which need attention. // Created: 25/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::NotifySysadmin(SysadminNotifier::EventCode Event) { static const char *sEventNames[] = { "store-full", "read-error", "backup-error", "backup-start", "backup-finish", "backup-ok", 0 }; // BOX_TRACE("sizeof(sEventNames) == " << sizeof(sEventNames)); // BOX_TRACE("sizeof(*sEventNames) == " << sizeof(*sEventNames)); // BOX_TRACE("NotifyEvent__MAX == " << NotifyEvent__MAX); ASSERT((sizeof(sEventNames)/sizeof(*sEventNames)) == SysadminNotifier::MAX + 1); if(Event < 0 || Event >= SysadminNotifier::MAX) { BOX_ERROR("BackupDaemon::NotifySysadmin() called for " "invalid event code " << Event); THROW_EXCEPTION(BackupStoreException, BadNotifySysadminEventCode); } BOX_TRACE("BackupDaemon::NotifySysadmin() called, event = " << sEventNames[Event]); if(!GetConfiguration().KeyExists("NotifyAlways") || !GetConfiguration().GetKeyValueBool("NotifyAlways")) { // Don't send lots of repeated messages // Note: backup-start and backup-finish will always be // logged, because mLastNotifiedEvent is never set to // these values and therefore they are never "duplicates". if(mLastNotifiedEvent == Event) { if(Event == SysadminNotifier::BackupOK) { BOX_INFO("Suppressing duplicate notification " "about " << sEventNames[Event]); } else { BOX_WARNING("Suppressing duplicate notification " "about " << sEventNames[Event]); } return; } } // Is there a notification script? const Configuration &conf(GetConfiguration()); if(!conf.KeyExists("NotifyScript")) { // Log, and then return if(Event != SysadminNotifier::BackupStart && Event != SysadminNotifier::BackupFinish) { BOX_INFO("Not notifying administrator about event " << sEventNames[Event] << ", set NotifyScript " "to do this in future"); } return; } // Script to run std::string script(conf.GetKeyValue("NotifyScript") + " " + sEventNames[Event] + " \"" + GetConfigFileName() + "\""); // Log what we're about to do BOX_INFO("About to notify administrator about event " << sEventNames[Event] << ", running script '" << script << "'"); // Then do it int returnCode = ::system(script.c_str()); if(returnCode != 0) { BOX_WARNING("Notify script returned error code: " << returnCode << " (" << script << ")"); } else if(Event != SysadminNotifier::BackupStart && Event != SysadminNotifier::BackupFinish) { mLastNotifiedEvent = Event; } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &) // Purpose: Deletes any unused entries in the root directory, if they're scheduled to be deleted. // Created: 13/5/04 // // -------------------------------------------------------------------------- void BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &rContext) { if(mUnusedRootDirEntries.empty()) { BOX_INFO("Not deleting unused entries - none in list"); return; } if(mDeleteUnusedRootDirEntriesAfter == 0) { BOX_INFO("Not deleting unused entries - " "zero delete time (bad)"); return; } // Check time box_time_t now = GetCurrentBoxTime(); if(now < mDeleteUnusedRootDirEntriesAfter) { int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter - now); BOX_INFO("Not deleting unused entries - too early (" << secs << " seconds remaining)"); return; } // Entries to delete, and it's the right time to do so... BOX_NOTICE("Deleting unused locations from store root..."); BackupProtocolClient &connection(rContext.GetConnection()); for(std::vector >::iterator i(mUnusedRootDirEntries.begin()); i != mUnusedRootDirEntries.end(); ++i) { connection.QueryDeleteDirectory(i->first); rContext.GetProgressNotifier().NotifyFileDeleted( i->first, i->second); } // Reset state mDeleteUnusedRootDirEntriesAfter = 0; mUnusedRootDirEntries.clear(); } // -------------------------------------------------------------------------- typedef struct { int32_t mMagicValue; // also the version number int32_t mNumEntries; int64_t mObjectID; // this object ID int64_t mContainerID; // ID of container uint64_t mAttributesModTime; int32_t mOptionsPresent; // bit mask of optional sections / features present } loc_StreamFormat; // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::Location::Location() // Purpose: Constructor // Created: 11/11/03 // // -------------------------------------------------------------------------- BackupDaemon::Location::Location() : mIDMapIndex(0), mpExcludeFiles(0), mpExcludeDirs(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::Location::~Location() // Purpose: Destructor // Created: 11/11/03 // // -------------------------------------------------------------------------- BackupDaemon::Location::~Location() { // Clean up exclude locations if(mpExcludeDirs != 0) { delete mpExcludeDirs; mpExcludeDirs = 0; } if(mpExcludeFiles != 0) { delete mpExcludeFiles; mpExcludeFiles = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::Location::Deserialize(Archive & rArchive) // Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction. // // Created: 2005/04/11 // // -------------------------------------------------------------------------- void BackupDaemon::Location::Deserialize(Archive &rArchive) { // // // mpDirectoryRecord.reset(NULL); if(mpExcludeFiles) { delete mpExcludeFiles; mpExcludeFiles = NULL; } if(mpExcludeDirs) { delete mpExcludeDirs; mpExcludeDirs = NULL; } // // // rArchive.Read(mName); rArchive.Read(mPath); rArchive.Read(mIDMapIndex); // // // int64_t aMagicMarker = 0; rArchive.Read(aMagicMarker); if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP) { // NOOP } else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE) { BackupClientDirectoryRecord *pSubRecord = new BackupClientDirectoryRecord(0, ""); if(!pSubRecord) { throw std::bad_alloc(); } mpDirectoryRecord.reset(pSubRecord); mpDirectoryRecord->Deserialize(rArchive); } else { // there is something going on here THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile); } // // // rArchive.Read(aMagicMarker); if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP) { // NOOP } else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE) { mpExcludeFiles = new ExcludeList; if(!mpExcludeFiles) { throw std::bad_alloc(); } mpExcludeFiles->Deserialize(rArchive); } else { // there is something going on here THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile); } // // // rArchive.Read(aMagicMarker); if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP) { // NOOP } else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE) { mpExcludeDirs = new ExcludeList; if(!mpExcludeDirs) { throw std::bad_alloc(); } mpExcludeDirs->Deserialize(rArchive); } else { // there is something going on here THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile); } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::Location::Serialize(Archive & rArchive) // Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction. // // Created: 2005/04/11 // // -------------------------------------------------------------------------- void BackupDaemon::Location::Serialize(Archive & rArchive) const { // // // rArchive.Write(mName); rArchive.Write(mPath); rArchive.Write(mIDMapIndex); // // // if(mpDirectoryRecord.get() == NULL) { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP; rArchive.Write(aMagicMarker); } else { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows rArchive.Write(aMagicMarker); mpDirectoryRecord->Serialize(rArchive); } // // // if(!mpExcludeFiles) { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP; rArchive.Write(aMagicMarker); } else { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows rArchive.Write(aMagicMarker); mpExcludeFiles->Serialize(rArchive); } // // // if(!mpExcludeDirs) { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP; rArchive.Write(aMagicMarker); } else { int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows rArchive.Write(aMagicMarker); mpExcludeDirs->Serialize(rArchive); } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::CommandSocketInfo::CommandSocketInfo() // Purpose: Constructor // Created: 18/2/04 // // -------------------------------------------------------------------------- BackupDaemon::CommandSocketInfo::CommandSocketInfo() : mpGetLine(0) { } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::CommandSocketInfo::~CommandSocketInfo() // Purpose: Destructor // Created: 18/2/04 // // -------------------------------------------------------------------------- BackupDaemon::CommandSocketInfo::~CommandSocketInfo() { if(mpGetLine) { delete mpGetLine; mpGetLine = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::SerializeStoreObjectInfo( // box_time_t theLastSyncTime, // box_time_t theNextSyncTime) // Purpose: Serializes remote directory and file information // into a stream of bytes, using an Archive // abstraction. // Created: 2005/04/11 // // -------------------------------------------------------------------------- static const int STOREOBJECTINFO_MAGIC_ID_VALUE = 0x7777525F; static const std::string STOREOBJECTINFO_MAGIC_ID_STRING = "BBACKUPD-STATE"; static const int STOREOBJECTINFO_VERSION = 2; bool BackupDaemon::SerializeStoreObjectInfo(box_time_t theLastSyncTime, box_time_t theNextSyncTime) const { if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) { return false; } std::string StoreObjectInfoFile = GetConfiguration().GetKeyValue("StoreObjectInfoFile"); if(StoreObjectInfoFile.size() <= 0) { return false; } bool created = false; try { FileStream aFile(StoreObjectInfoFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC); created = true; Archive anArchive(aFile, 0); anArchive.Write(STOREOBJECTINFO_MAGIC_ID_VALUE); anArchive.Write(STOREOBJECTINFO_MAGIC_ID_STRING); anArchive.Write(STOREOBJECTINFO_VERSION); anArchive.Write(GetLoadedConfigModifiedTime()); anArchive.Write(mClientStoreMarker); anArchive.Write(theLastSyncTime); anArchive.Write(theNextSyncTime); // // // int64_t iCount = mLocations.size(); anArchive.Write(iCount); for(int v = 0; v < iCount; v++) { ASSERT(mLocations[v]); mLocations[v]->Serialize(anArchive); } // // // iCount = mIDMapMounts.size(); anArchive.Write(iCount); for(int v = 0; v < iCount; v++) anArchive.Write(mIDMapMounts[v]); // // // iCount = mUnusedRootDirEntries.size(); anArchive.Write(iCount); for(int v = 0; v < iCount; v++) { anArchive.Write(mUnusedRootDirEntries[v].first); anArchive.Write(mUnusedRootDirEntries[v].second); } if (iCount > 0) { anArchive.Write(mDeleteUnusedRootDirEntriesAfter); } // // // aFile.Close(); BOX_INFO("Saved store object info file version " << STOREOBJECTINFO_VERSION << " (" << StoreObjectInfoFile << ")"); } catch(std::exception &e) { BOX_ERROR("Failed to write StoreObjectInfoFile: " << StoreObjectInfoFile << ": " << e.what()); } catch(...) { BOX_ERROR("Failed to write StoreObjectInfoFile: " << StoreObjectInfoFile << ": unknown error"); } return created; } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DeserializeStoreObjectInfo( // box_time_t & theLastSyncTime, // box_time_t & theNextSyncTime) // Purpose: Deserializes remote directory and file information // from a stream of bytes, using an Archive // abstraction. // Created: 2005/04/11 // // -------------------------------------------------------------------------- bool BackupDaemon::DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, box_time_t & theNextSyncTime) { // // // DeleteAllLocations(); // // // if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) { return false; } std::string StoreObjectInfoFile = GetConfiguration().GetKeyValue("StoreObjectInfoFile"); if(StoreObjectInfoFile.size() <= 0) { return false; } try { FileStream aFile(StoreObjectInfoFile.c_str(), O_RDONLY); Archive anArchive(aFile, 0); // // see if the content looks like a valid serialised archive // int iMagicValue = 0; anArchive.Read(iMagicValue); if(iMagicValue != STOREOBJECTINFO_MAGIC_ID_VALUE) { BOX_WARNING("Store object info file " "is not a valid or compatible serialised " "archive. Will re-cache from store. " "(" << StoreObjectInfoFile << ")"); return false; } // // get a bit optimistic and read in a string identifier // std::string strMagicValue; anArchive.Read(strMagicValue); if(strMagicValue != STOREOBJECTINFO_MAGIC_ID_STRING) { BOX_WARNING("Store object info file " "is not a valid or compatible serialised " "archive. Will re-cache from store. " "(" << StoreObjectInfoFile << ")"); return false; } // // check if we are loading some future format // version by mistake // int iVersion = 0; anArchive.Read(iVersion); if(iVersion != STOREOBJECTINFO_VERSION) { BOX_WARNING("Store object info file " "version " << iVersion << " unsupported. " "Will re-cache from store. " "(" << StoreObjectInfoFile << ")"); return false; } // // check if this state file is even valid // for the loaded bbackupd.conf file // box_time_t lastKnownConfigModTime; anArchive.Read(lastKnownConfigModTime); if(lastKnownConfigModTime != GetLoadedConfigModifiedTime()) { BOX_WARNING("Store object info file " "out of date. Will re-cache from store. " "(" << StoreObjectInfoFile << ")"); return false; } // // this is it, go at it // anArchive.Read(mClientStoreMarker); anArchive.Read(theLastSyncTime); anArchive.Read(theNextSyncTime); // // // int64_t iCount = 0; anArchive.Read(iCount); for(int v = 0; v < iCount; v++) { Location* pLocation = new Location; if(!pLocation) { throw std::bad_alloc(); } pLocation->Deserialize(anArchive); mLocations.push_back(pLocation); } // // // iCount = 0; anArchive.Read(iCount); for(int v = 0; v < iCount; v++) { std::string strItem; anArchive.Read(strItem); mIDMapMounts.push_back(strItem); } // // // iCount = 0; anArchive.Read(iCount); for(int v = 0; v < iCount; v++) { int64_t anId; anArchive.Read(anId); std::string aName; anArchive.Read(aName); mUnusedRootDirEntries.push_back(std::pair(anId, aName)); } if (iCount > 0) anArchive.Read(mDeleteUnusedRootDirEntriesAfter); // // // aFile.Close(); BOX_INFO("Loaded store object info file version " << iVersion << " (" << StoreObjectInfoFile << ")"); return true; } catch(std::exception &e) { BOX_ERROR("Internal error reading store object info file: " << StoreObjectInfoFile << ": " << e.what()); } catch(...) { BOX_ERROR("Internal error reading store object info file: " << StoreObjectInfoFile << ": unknown error"); } DeleteAllLocations(); mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; theLastSyncTime = 0; theNextSyncTime = 0; BOX_WARNING("Store object info file is missing, not accessible, " "or inconsistent. Will re-cache from store. " "(" << StoreObjectInfoFile << ")"); return false; } // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::DeleteStoreObjectInfo() // Purpose: Deletes the serialised state file, to prevent us // from using it again if a backup is interrupted. // // Created: 2006/02/12 // // -------------------------------------------------------------------------- bool BackupDaemon::DeleteStoreObjectInfo() const { if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) { return false; } std::string storeObjectInfoFile(GetConfiguration().GetKeyValue("StoreObjectInfoFile")); // Check to see if the file exists if(!FileExists(storeObjectInfoFile.c_str())) { // File doesn't exist -- so can't be deleted. But something // isn't quite right, so log a message BOX_WARNING("StoreObjectInfoFile did not exist when it " "was supposed to: " << storeObjectInfoFile); // Return true to stop things going around in a loop return true; } // Actually delete it if(::unlink(storeObjectInfoFile.c_str()) != 0) { BOX_LOG_SYS_ERROR("Failed to delete the old " "StoreObjectInfoFile: " << storeObjectInfoFile); return false; } return true; } boxbackup/bin/bbackupd/BackupClientInodeToIDMap.cpp0000664000175000017500000001737111071517743023061 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientInodeToIDMap.cpp // Purpose: Map of inode numbers to file IDs on the store // Created: 11/11/03 // // -------------------------------------------------------------------------- #include "Box.h" #ifdef HAVE_DB // Include db headers and other OS files if they're needed for the disc implementation #include #include #include #include #include #endif #define BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION #include "BackupClientInodeToIDMap.h" #include "BackupStoreException.h" #include "MemLeakFindOn.h" // What type of Berkeley DB shall we use? #define TABLE_DATABASE_TYPE DB_HASH typedef struct { int64_t mObjectID; int64_t mInDirectory; } IDBRecord; // -------------------------------------------------------------------------- // // Function // Name: BackupClientInodeToIDMap::BackupClientInodeToIDMap() // Purpose: Constructor // Created: 11/11/03 // // -------------------------------------------------------------------------- BackupClientInodeToIDMap::BackupClientInodeToIDMap() #ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION : mReadOnly(true), mEmpty(false), dbp(0) #endif { } // -------------------------------------------------------------------------- // // Function // Name: BackupClientInodeToIDMap::~BackupClientInodeToIDMap() // Purpose: Destructor // Created: 11/11/03 // // -------------------------------------------------------------------------- BackupClientInodeToIDMap::~BackupClientInodeToIDMap() { #ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION if(dbp != 0) { #if BDB_VERSION_MAJOR >= 3 dbp->close(0); #else dbp->close(dbp); #endif } #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupClientInodeToIDMap::Open(const char *, bool, bool) // Purpose: Open the database map, creating a file on disc to store everything // Created: 20/11/03 // // -------------------------------------------------------------------------- void BackupClientInodeToIDMap::Open(const char *Filename, bool ReadOnly, bool CreateNew) { #ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION // Correct arguments? ASSERT(!(CreateNew && ReadOnly)); // Correct usage? ASSERT(dbp == 0); ASSERT(!mEmpty); // Open the database file #if BDB_VERSION_MAJOR >= 3 dbp = new Db(0,0); dbp->set_pagesize(1024); /* Page size: 1K. */ dbp->set_cachesize(0, 32 * 1024, 0); dbp->open(NULL, Filename, NULL, DB_HASH, DB_CREATE, 0664); #else dbp = dbopen(Filename, (CreateNew?O_CREAT:0) | (ReadOnly?O_RDONLY:O_RDWR), S_IRUSR | S_IWUSR | S_IRGRP, TABLE_DATABASE_TYPE, NULL); #endif if(dbp == NULL) { THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure); } // Read only flag mReadOnly = ReadOnly; #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupClientInodeToIDMap::OpenEmpty() // Purpose: 'Open' this map. Not associated with a disc file. Useful for when a map // is required, but is against an empty file on disc which shouldn't be created. // Implies read only. // Created: 20/11/03 // // -------------------------------------------------------------------------- void BackupClientInodeToIDMap::OpenEmpty() { #ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION ASSERT(dbp == 0); mEmpty = true; mReadOnly = true; #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupClientInodeToIDMap::Close() // Purpose: Close the database file // Created: 20/11/03 // // -------------------------------------------------------------------------- void BackupClientInodeToIDMap::Close() { #ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION if(dbp != 0) { #if BDB_VERSION_MAJOR >= 3 if(dbp->close(0) != 0) #else if(dbp->close(dbp) != 0) #endif { THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure); } dbp = 0; } #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupClientInodeToIDMap::AddToMap(InodeRefType, int64_t, int64_t) // Purpose: Adds an entry to the map. Overwrites any existing entry. // Created: 11/11/03 // // -------------------------------------------------------------------------- void BackupClientInodeToIDMap::AddToMap(InodeRefType InodeRef, int64_t ObjectID, int64_t InDirectory) { #ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION mMap[InodeRef] = std::pair(ObjectID, InDirectory); #else if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, InodeMapIsReadOnly); } if(dbp == 0) { THROW_EXCEPTION(BackupStoreException, InodeMapNotOpen); } // Setup structures IDBRecord rec; rec.mObjectID = ObjectID; rec.mInDirectory = InDirectory; #if BDB_VERSION_MAJOR >= 3 Dbt key(&InodeRef, sizeof(InodeRef)); Dbt data(&rec, sizeof(rec)); if (dbp->put(0, &key, &data, 0) != 0) { THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure); } #else DBT key; key.data = &InodeRef; key.size = sizeof(InodeRef); DBT data; data.data = &rec; data.size = sizeof(rec); // Add to map (or replace existing entry) if(dbp->put(dbp, &key, &data, 0) != 0) { THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure); } #endif #endif } // -------------------------------------------------------------------------- // // Function // Name: BackupClientInodeToIDMap::Lookup(InodeRefType, // int64_t &, int64_t &) const // Purpose: Looks up an inode in the map, returning true if it // exists, and the object ids of it and the directory // it's in the reference arguments. // Created: 11/11/03 // // -------------------------------------------------------------------------- bool BackupClientInodeToIDMap::Lookup(InodeRefType InodeRef, int64_t &rObjectIDOut, int64_t &rInDirectoryOut) const { #ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION std::map >::const_iterator i(mMap.find(InodeRef)); // Found? if(i == mMap.end()) { return false; } // Yes. Return the details rObjectIDOut = i->second.first; rInDirectoryOut = i->second.second; return true; #else if(mEmpty) { // Map is empty return false; } if(dbp == 0) { THROW_EXCEPTION(BackupStoreException, InodeMapNotOpen); } #if BDB_VERSION_MAJOR >= 3 Dbt key(&InodeRef, sizeof(InodeRef)); Dbt data(0, 0); switch(dbp->get(NULL, &key, &data, 0)) #else DBT key; key.data = &InodeRef; key.size = sizeof(InodeRef); DBT data; data.data = 0; data.size = 0; switch(dbp->get(dbp, &key, &data, 0)) #endif { case 1: // key not in file return false; case -1: // error default: // not specified in docs THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure); return false; case 0: // success, found it break; } // Check for sensible return #if BDB_VERSION_MAJOR >= 3 if(key.get_data() == 0 || data.get_size() != sizeof(IDBRecord)) { // Assert in debug version ASSERT(key.get_data() == 0 || data.get_size() != sizeof(IDBRecord)); // Invalid entries mean it wasn't found return false; } // Data alignment isn't guaranteed to be on a suitable boundary IDBRecord rec; ::memcpy(&rec, data.get_data(), sizeof(rec)); #else if(key.data == 0 || data.size != sizeof(IDBRecord)) { // Assert in debug version ASSERT(key.data == 0 || data.size != sizeof(IDBRecord)); // Invalid entries mean it wasn't found return false; } // Data alignment isn't guaranteed to be on a suitable boundary IDBRecord rec; ::memcpy(&rec, data.data, sizeof(rec)); #endif // Return data rObjectIDOut = rec.mObjectID; rInDirectoryOut = rec.mInDirectory; // Don't have to worry about freeing the returned data // Found return true; #endif } boxbackup/bin/bbackupd/Win32BackupService.cpp0000664000175000017500000000161710705402711021714 0ustar siretartsiretart// 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 boxbackup/bin/bbackupd/bbackupd.cpp0000664000175000017500000000214511017267347020126 0ustar siretartsiretart// -------------------------------------------------------------------------- // // 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; } boxbackup/bin/bbackupd/BackupClientDeleteList.h0000664000175000017500000000364711017274425022346 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientDeleteList.h // Purpose: List of pending deletes for backup // Created: 10/11/03 // // -------------------------------------------------------------------------- #ifndef BACKUPCLIENTDELETELIST__H #define BACKUPCLIENTDELETELIST__H #include "BackupStoreFilename.h" class BackupClientContext; #include #include #include // -------------------------------------------------------------------------- // // Class // Name: BackupClientDeleteList // Purpose: List of pending deletes for backup // Created: 10/11/03 // // -------------------------------------------------------------------------- class BackupClientDeleteList { private: class FileToDelete { public: int64_t mDirectoryID; BackupStoreFilename mFilename; std::string mLocalPath; FileToDelete(int64_t DirectoryID, const BackupStoreFilename& rFilename, const std::string& rLocalPath); }; class DirToDelete { public: int64_t mObjectID; std::string mLocalPath; DirToDelete(int64_t ObjectID, const std::string& rLocalPath); }; public: BackupClientDeleteList(); ~BackupClientDeleteList(); void AddDirectoryDelete(int64_t ObjectID, const std::string& rLocalPath); void AddFileDelete(int64_t DirectoryID, const BackupStoreFilename &rFilename, const std::string& rLocalPath); void StopDirectoryDeletion(int64_t ObjectID); void StopFileDeletion(int64_t DirectoryID, const BackupStoreFilename &rFilename); void PerformDeletions(BackupClientContext &rContext); private: std::vector mDirectoryList; std::set mDirectoryNoDeleteList; // note: things only get in this list if they're not present in mDirectoryList when they are 'added' std::vector mFileList; std::vector > mFileNoDeleteList; }; #endif // BACKUPCLIENTDELETELIST__H boxbackup/bin/bbackupd/Makefile.extra0000664000175000017500000000032211345266370020423 0ustar siretartsiretart MAKEEXCEPTION = ../../lib/common/makeexception.pl # AUTOGEN SEEDING autogen_ClientException.h autogen_ClientException.cpp: $(MAKEEXCEPTION) ClientException.txt $(_PERL) $(MAKEEXCEPTION) ClientException.txt boxbackup/bin/bbackupd/BackupClientDeleteList.cpp0000664000175000017500000001412611017274425022673 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupClientDeleteList.cpp // Purpose: List of pending deletes for backup // Created: 10/11/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupClientDeleteList.h" #include "BackupClientContext.h" #include "autogen_BackupProtocolClient.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupClientDeleteList::BackupClientDeleteList() // Purpose: Constructor // Created: 10/11/03 // // -------------------------------------------------------------------------- BackupClientDeleteList::BackupClientDeleteList() { } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDeleteList::~BackupClientDeleteList() // Purpose: Destructor // Created: 10/11/03 // // -------------------------------------------------------------------------- BackupClientDeleteList::~BackupClientDeleteList() { } BackupClientDeleteList::FileToDelete::FileToDelete(int64_t DirectoryID, const BackupStoreFilename& rFilename, const std::string& rLocalPath) : mDirectoryID(DirectoryID), mFilename(rFilename), mLocalPath(rLocalPath) { } BackupClientDeleteList::DirToDelete::DirToDelete(int64_t ObjectID, const std::string& rLocalPath) : mObjectID(ObjectID), mLocalPath(rLocalPath) { } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDeleteList::AddDirectoryDelete(int64_t, // const BackupStoreFilename&) // Purpose: Add a directory to the list of directories to be deleted. // Created: 10/11/03 // // -------------------------------------------------------------------------- void BackupClientDeleteList::AddDirectoryDelete(int64_t ObjectID, const std::string& rLocalPath) { // Only add the delete to the list if it's not in the "no delete" set if(mDirectoryNoDeleteList.find(ObjectID) == mDirectoryNoDeleteList.end()) { // Not in the list, so should delete it mDirectoryList.push_back(DirToDelete(ObjectID, rLocalPath)); } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDeleteList::AddFileDelete(int64_t, // const BackupStoreFilename &) // Purpose: // Created: 10/11/03 // // -------------------------------------------------------------------------- void BackupClientDeleteList::AddFileDelete(int64_t DirectoryID, const BackupStoreFilename &rFilename, const std::string& rLocalPath) { // Try to find it in the no delete list std::vector >::iterator delEntry(mFileNoDeleteList.begin()); while(delEntry != mFileNoDeleteList.end()) { if((delEntry)->first == DirectoryID && (delEntry)->second == rFilename) { // Found! break; } ++delEntry; } // Only add it to the delete list if it wasn't in the no delete list if(delEntry == mFileNoDeleteList.end()) { mFileList.push_back(FileToDelete(DirectoryID, rFilename, rLocalPath)); } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) // Purpose: Perform all the pending deletes // Created: 10/11/03 // // -------------------------------------------------------------------------- void BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) { // Anything to do? if(mDirectoryList.empty() && mFileList.empty()) { // Nothing! return; } // Get a connection BackupProtocolClient &connection(rContext.GetConnection()); // Do the deletes for(std::vector::iterator i(mDirectoryList.begin()); i != mDirectoryList.end(); ++i) { connection.QueryDeleteDirectory(i->mObjectID); rContext.GetProgressNotifier().NotifyDirectoryDeleted( i->mObjectID, i->mLocalPath); } // Clear the directory list mDirectoryList.clear(); // Delete the files for(std::vector::iterator i(mFileList.begin()); i != mFileList.end(); ++i) { connection.QueryDeleteFile(i->mDirectoryID, i->mFilename); rContext.GetProgressNotifier().NotifyFileDeleted( i->mDirectoryID, i->mLocalPath); } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDeleteList::StopDirectoryDeletion(int64_t) // Purpose: Stop a directory being deleted // Created: 19/11/03 // // -------------------------------------------------------------------------- void BackupClientDeleteList::StopDirectoryDeletion(int64_t ObjectID) { // First of all, is it in the delete vector? std::vector::iterator delEntry(mDirectoryList.begin()); for(; delEntry != mDirectoryList.end(); delEntry++) { if(delEntry->mObjectID == ObjectID) { // Found! break; } } if(delEntry != mDirectoryList.end()) { // erase this entry mDirectoryList.erase(delEntry); } else { // Haven't been asked to delete it yet, put it in the // no delete list mDirectoryNoDeleteList.insert(ObjectID); } } // -------------------------------------------------------------------------- // // Function // Name: BackupClientDeleteList::StopFileDeletion(int64_t, const BackupStoreFilename &) // Purpose: Stop a file from being deleted // Created: 19/11/03 // // -------------------------------------------------------------------------- void BackupClientDeleteList::StopFileDeletion(int64_t DirectoryID, const BackupStoreFilename &rFilename) { // Find this in the delete list std::vector::iterator delEntry(mFileList.begin()); while(delEntry != mFileList.end()) { if(delEntry->mDirectoryID == DirectoryID && delEntry->mFilename == rFilename) { // Found! break; } ++delEntry; } if(delEntry != mFileList.end()) { // erase this entry mFileList.erase(delEntry); } else { // Haven't been asked to delete it yet, put it in the no delete list mFileNoDeleteList.push_back(std::pair(DirectoryID, rFilename)); } } boxbackup/bin/bbackupd/BackupDaemon.h0000664000175000017500000003511611345271627020355 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupDaemon.h // Purpose: Backup daemon // Created: 2003/10/08 // // -------------------------------------------------------------------------- #ifndef BACKUPDAEMON__H #define BACKUPDAEMON__H #include #include #include #include "BackupClientContext.h" #include "BackupClientDirectoryRecord.h" #include "BoxTime.h" #include "Daemon.h" #include "Logging.h" #include "Socket.h" #include "SocketListen.h" #include "SocketStream.h" #include "TLSContext.h" #include "autogen_BackupProtocolClient.h" #ifdef WIN32 #include "WinNamedPipeListener.h" #include "WinNamedPipeStream.h" #endif class BackupClientDirectoryRecord; class BackupClientContext; class Configuration; class BackupClientInodeToIDMap; class ExcludeList; class IOStreamGetLine; class Archive; // -------------------------------------------------------------------------- // // Class // Name: BackupDaemon // Purpose: Backup daemon // Created: 2003/10/08 // // -------------------------------------------------------------------------- class BackupDaemon : public Daemon, ProgressNotifier, LocationResolver, RunStatusProvider, SysadminNotifier { public: BackupDaemon(); ~BackupDaemon(); private: // methods below do partial (specialized) serialization of // client state only bool SerializeStoreObjectInfo(box_time_t theLastSyncTime, box_time_t theNextSyncTime) const; bool DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, box_time_t & theNextSyncTime); bool DeleteStoreObjectInfo() const; BackupDaemon(const BackupDaemon &); public: #ifdef WIN32 // add command-line options to handle Windows services std::string GetOptionString(); int ProcessOption(signed int option); int Main(const std::string &rConfigFileName); // This shouldn't be here, but apparently gcc on // Windows has no idea about inherited methods... virtual int Main(const char *DefaultConfigFile, int argc, const char *argv[]) { return Daemon::Main(DefaultConfigFile, argc, argv); } #endif void Run(); virtual const char *DaemonName() const; virtual std::string DaemonBanner() const; virtual void Usage(); const ConfigurationVerify *GetConfigVerify() const; bool FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const; enum { // Add stuff to this, make sure the textual equivalents in SetState() are changed too. State_Initialising = -1, State_Idle = 0, State_Connected = 1, State_Error = 2, State_StorageLimitExceeded = 3 }; int GetState() {return mState;} // Allow other classes to call this too void NotifySysadmin(SysadminNotifier::EventCode Event); private: void Run2(); public: void InitCrypto(); void RunSyncNowWithExceptionHandling(); void RunSyncNow(); void OnBackupStart(); void OnBackupFinish(); // TouchFileInWorkingDir is only here for use by Boxi. // This does NOT constitute an API! void TouchFileInWorkingDir(const char *Filename); private: void DeleteAllLocations(); void SetupLocations(BackupClientContext &rClientContext, const Configuration &rLocationsConf); void DeleteIDMapVector(std::vector &rVector); void DeleteAllIDMaps() { DeleteIDMapVector(mCurrentIDMaps); DeleteIDMapVector(mNewIDMaps); } void FillIDMapVector(std::vector &rVector, bool NewMaps); void SetupIDMapsForSync(); void CommitIDMapsAfterSync(); void DeleteCorruptBerkelyDbFiles(); void MakeMapBaseName(unsigned int MountNumber, std::string &rNameOut) const; void SetState(int State); void WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut); void CloseCommandConnection(); void SendSyncStartOrFinish(bool SendStart); void DeleteUnusedRootDirEntries(BackupClientContext &rContext); #ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET // For warning user about potential security hole virtual void SetupInInitialProcess(); #endif int UseScriptToSeeIfSyncAllowed(); public: class Location { public: Location(); ~Location(); void Deserialize(Archive & rArchive); void Serialize(Archive & rArchive) const; private: Location(const Location &); // copy not allowed Location &operator=(const Location &); public: std::string mName; std::string mPath; std::auto_ptr mpDirectoryRecord; int mIDMapIndex; ExcludeList *mpExcludeFiles; ExcludeList *mpExcludeDirs; }; typedef const std::vector Locations; Locations GetLocations() { return mLocations; } private: int mState; // what the daemon is currently doing std::vector mLocations; std::vector mIDMapMounts; std::vector mCurrentIDMaps; std::vector mNewIDMaps; int mDeleteRedundantLocationsAfter; // For the command socket class CommandSocketInfo { public: CommandSocketInfo(); ~CommandSocketInfo(); private: CommandSocketInfo(const CommandSocketInfo &); // no copying CommandSocketInfo &operator=(const CommandSocketInfo &); public: #ifdef WIN32 WinNamedPipeListener<1 /* listen backlog */> mListeningSocket; std::auto_ptr mpConnectedSocket; #else SocketListen mListeningSocket; std::auto_ptr mpConnectedSocket; #endif IOStreamGetLine *mpGetLine; }; // Using a socket? std::auto_ptr mapCommandSocketInfo; // Stop notifications being repeated. SysadminNotifier::EventCode mLastNotifiedEvent; // Unused entries in the root directory wait a while before being deleted box_time_t mDeleteUnusedRootDirEntriesAfter; // time to delete them std::vector > mUnusedRootDirEntries; int64_t mClientStoreMarker; bool mStorageLimitExceeded; bool mReadErrorsOnFilesystemObjects; box_time_t mLastSyncTime, mNextSyncTime; box_time_t mCurrentSyncStartTime, mUpdateStoreInterval; TLSContext mTlsContext; bool mDeleteStoreObjectInfoFile; bool mDoSyncForcedByPreviousSyncError; public: bool StopRun() { return this->Daemon::StopRun(); } bool StorageLimitExceeded() { return mStorageLimitExceeded; } private: bool mLogAllFileAccess; public: ProgressNotifier* GetProgressNotifier() { return mpProgressNotifier; } LocationResolver* GetLocationResolver() { return mpLocationResolver; } RunStatusProvider* GetRunStatusProvider() { return mpRunStatusProvider; } SysadminNotifier* GetSysadminNotifier() { return mpSysadminNotifier; } void SetProgressNotifier (ProgressNotifier* p) { mpProgressNotifier = p; } void SetLocationResolver (LocationResolver* p) { mpLocationResolver = p; } void SetRunStatusProvider(RunStatusProvider* p) { mpRunStatusProvider = p; } void SetSysadminNotifier (SysadminNotifier* p) { mpSysadminNotifier = p; } private: ProgressNotifier* mpProgressNotifier; LocationResolver* mpLocationResolver; RunStatusProvider* mpRunStatusProvider; SysadminNotifier* mpSysadminNotifier; /* ProgressNotifier implementation */ public: virtual void NotifyIDMapsSetup(BackupClientContext& rContext) { } virtual void NotifyScanDirectory( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { if (mLogAllFileAccess) { BOX_INFO("Scanning directory: " << rLocalPath); } } virtual void NotifyDirStatFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) { BOX_WARNING("Failed to access directory: " << rLocalPath << ": " << rErrorMsg); } virtual void NotifyFileStatFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) { BOX_WARNING("Failed to access file: " << rLocalPath << ": " << rErrorMsg); } virtual void NotifyDirListFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) { BOX_WARNING("Failed to list directory: " << rLocalPath << ": " << rErrorMsg); } virtual void NotifyMountPointSkipped( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { #ifdef WIN32 BOX_WARNING("Ignored directory: " << rLocalPath << ": is an NTFS junction/reparse point; create " "a new location if you want to back it up"); #else BOX_WARNING("Ignored directory: " << rLocalPath << ": is a mount point; create a new location " "if you want to back it up"); #endif } virtual void NotifyFileExcluded( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { if (mLogAllFileAccess) { BOX_INFO("Skipping excluded file: " << rLocalPath); } } virtual void NotifyDirExcluded( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { if (mLogAllFileAccess) { BOX_INFO("Skipping excluded directory: " << rLocalPath); } } virtual void NotifyUnsupportedFileType( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { BOX_WARNING("Ignoring file of unknown type: " << rLocalPath); } virtual void NotifyFileReadFailed( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const std::string& rErrorMsg) { BOX_WARNING("Error reading file: " << rLocalPath << ": " << rErrorMsg); } virtual void NotifyFileModifiedInFuture( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { BOX_WARNING("Some files have modification times excessively " "in the future. Check clock synchronisation. " "Example file (only one shown): " << rLocalPath); } virtual void NotifyFileSkippedServerFull( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { BOX_WARNING("Skipped file: server is full: " << rLocalPath); } virtual void NotifyFileUploadException( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, const BoxException& rException) { if (rException.GetType() == CommonException::ExceptionType && rException.GetSubType() == CommonException::AccessDenied) { BOX_ERROR("Failed to upload file: " << rLocalPath << ": Access denied"); } else { BOX_ERROR("Failed to upload file: " << rLocalPath << ": caught exception: " << rException.what() << " (" << rException.GetType() << "/" << rException.GetSubType() << ")"); } } virtual void NotifyFileUploadServerError( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, int type, int subtype) { std::ostringstream msgs; if (type != BackupProtocolClientError::ErrorType) { msgs << "unknown error type " << type; } else { switch(subtype) { case BackupProtocolClientError::Err_WrongVersion: msgs << "WrongVersion"; break; case BackupProtocolClientError::Err_NotInRightProtocolPhase: msgs << "NotInRightProtocolPhase"; break; case BackupProtocolClientError::Err_BadLogin: msgs << "BadLogin"; break; case BackupProtocolClientError::Err_CannotLockStoreForWriting: msgs << "CannotLockStoreForWriting"; break; case BackupProtocolClientError::Err_SessionReadOnly: msgs << "SessionReadOnly"; break; case BackupProtocolClientError::Err_FileDoesNotVerify: msgs << "FileDoesNotVerify"; break; case BackupProtocolClientError::Err_DoesNotExist: msgs << "DoesNotExist"; break; case BackupProtocolClientError::Err_DirectoryAlreadyExists: msgs << "DirectoryAlreadyExists"; break; case BackupProtocolClientError::Err_CannotDeleteRoot: msgs << "CannotDeleteRoot"; break; case BackupProtocolClientError::Err_TargetNameExists: msgs << "TargetNameExists"; break; case BackupProtocolClientError::Err_StorageLimitExceeded: msgs << "StorageLimitExceeded"; break; case BackupProtocolClientError::Err_DiffFromFileDoesNotExist: msgs << "DiffFromFileDoesNotExist"; break; case BackupProtocolClientError::Err_DoesNotExistInDirectory: msgs << "DoesNotExistInDirectory"; break; case BackupProtocolClientError::Err_PatchConsistencyError: msgs << "PatchConsistencyError"; break; default: msgs << "unknown error subtype " << subtype; } } BOX_ERROR("Failed to upload file: " << rLocalPath << ": server error: " << msgs.str()); } virtual void NotifyFileUploading( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { if (mLogAllFileAccess) { BOX_NOTICE("Uploading complete file: " << rLocalPath); } } virtual void NotifyFileUploadingPatch( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { if (mLogAllFileAccess) { BOX_NOTICE("Uploading patch to file: " << rLocalPath); } } virtual void NotifyFileUploadingAttributes( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) { if (mLogAllFileAccess) { BOX_NOTICE("Uploading new file attributes: " << rLocalPath); } } virtual void NotifyFileUploaded( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, int64_t FileSize) { if (mLogAllFileAccess) { BOX_NOTICE("Uploaded file: " << rLocalPath); } } virtual void NotifyFileSynchronised( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath, int64_t FileSize) { if (mLogAllFileAccess) { BOX_INFO("Synchronised file: " << rLocalPath); } } virtual void NotifyDirectoryDeleted( int64_t ObjectID, const std::string& rRemotePath) { if (mLogAllFileAccess) { BOX_NOTICE("Deleted directory: " << rRemotePath << " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << ")"); } } virtual void NotifyFileDeleted( int64_t ObjectID, const std::string& rRemotePath) { if (mLogAllFileAccess) { BOX_NOTICE("Deleted file: " << rRemotePath << " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << ")"); } } virtual void NotifyReadProgress(int64_t readSize, int64_t offset, int64_t length, box_time_t elapsed, box_time_t finish) { BOX_TRACE("Read " << readSize << " bytes at " << offset << ", " << (length - offset) << " remain, eta " << BoxTimeToSeconds(finish - elapsed) << "s"); } virtual void NotifyReadProgress(int64_t readSize, int64_t offset, int64_t length) { BOX_TRACE("Read " << readSize << " bytes at " << offset << ", " << (length - offset) << " remain"); } virtual void NotifyReadProgress(int64_t readSize, int64_t offset) { BOX_TRACE("Read " << readSize << " bytes at " << offset << ", unknown bytes remaining"); } #ifdef WIN32 private: bool mInstallService, mRemoveService, mRunAsService; std::string mServiceName; #endif }; #endif // BACKUPDAEMON__H boxbackup/bin/bbackupd/Win32ServiceFunctions.h0000664000175000017500000000123710705403051022120 0ustar siretartsiretart//*************************************************************** // 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 boxbackup/bin/s3simulator/0000775000175000017500000000000011652362374016360 5ustar siretartsiretartboxbackup/bin/s3simulator/s3simulator.cpp0000664000175000017500000000122311170703462021337 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: s3simulator.cpp // Purpose: main file for S3 simulator daemon // Created: 2003/10/11 // // -------------------------------------------------------------------------- #include "Box.h" #include "S3Simulator.h" #include "MainHelper.h" #include "MemLeakFindOn.h" int main(int argc, const char *argv[]) { int ExitCode = 0; MAINHELPER_START Logging::SetProgramName("s3simulator"); Logging::ToConsole(true); Logging::ToSyslog (true); S3Simulator daemon; ExitCode = daemon.Main("s3simulator.conf", argc, argv); MAINHELPER_END return ExitCode; } boxbackup/bin/bbstoreaccounts/0000775000175000017500000000000011652362374017273 5ustar siretartsiretartboxbackup/bin/bbstoreaccounts/bbstoreaccounts.cpp0000664000175000017500000004023011254533353023171 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: bbstoreaccounts // Purpose: backup store administration tool // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include #include #include #include #include "BoxPortsAndFiles.h" #include "BackupStoreConfigVerify.h" #include "RaidFileController.h" #include "BackupStoreAccounts.h" #include "BackupStoreAccountDatabase.h" #include "MainHelper.h" #include "BackupStoreInfo.h" #include "StoreStructure.h" #include "NamedLock.h" #include "UnixUser.h" #include "BackupStoreCheck.h" #include "Utils.h" #include "MemLeakFindOn.h" #include // max size of soft limit as percent of hard limit #define MAX_SOFT_LIMIT_SIZE 97 bool sMachineReadableOutput = false; void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit) { if(SoftLimit > HardLimit) { BOX_FATAL("Soft limit must be less than the hard limit."); exit(1); } if(SoftLimit > ((HardLimit * MAX_SOFT_LIMIT_SIZE) / 100)) { BOX_WARNING("We recommend setting the soft limit below " << MAX_SOFT_LIMIT_SIZE << "% of the hard limit, or " << HumanReadableSize((HardLimit * MAX_SOFT_LIMIT_SIZE) / 100) << " in this case."); } } int BlockSizeOfDiscSet(int DiscSet) { // Get controller, check disc set number RaidFileController &controller(RaidFileController::GetController()); if(DiscSet < 0 || DiscSet >= controller.GetNumDiscSets()) { BOX_FATAL("Disc set " << DiscSet << " does not exist."); exit(1); } // Return block size return controller.GetDiscSet(DiscSet).GetBlockSize(); } std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int DiscSet) { return FormatUsageBar(Blocks, Blocks * BlockSizeOfDiscSet(DiscSet), MaxBlocks * BlockSizeOfDiscSet(DiscSet), sMachineReadableOutput); } int64_t SizeStringToBlocks(const char *string, int DiscSet) { // Find block size int blockSize = BlockSizeOfDiscSet(DiscSet); // Get number char *endptr = (char*)string; int64_t number = strtol(string, &endptr, 0); if(endptr == string || number == LONG_MIN || number == LONG_MAX) { BOX_FATAL("'" << string << "' is not a valid number."); exit(1); } // Check units switch(*endptr) { case 'M': case 'm': // Units: Mb return (number * 1024*1024) / blockSize; break; case 'G': case 'g': // Units: Gb return (number * 1024*1024*1024) / blockSize; break; case 'B': case 'b': // Units: Blocks // Easy! Just return the number specified. return number; break; default: BOX_FATAL(string << " has an invalid units specifier " "(use B for blocks, M for MB, G for GB, eg 2GB)"); exit(1); break; } } bool GetWriteLockOnAccount(NamedLock &rLock, const std::string rRootDir, int DiscSetNum) { std::string writeLockFilename; StoreStructure::MakeWriteLockFilename(rRootDir, DiscSetNum, writeLockFilename); bool gotLock = false; int triesLeft = 8; do { gotLock = rLock.TryAndGetLock(writeLockFilename.c_str(), 0600 /* restrictive file permissions */); if(!gotLock) { --triesLeft; ::sleep(1); } } while(!gotLock && triesLeft > 0); if(!gotLock) { // Couldn't lock the account -- just stop now BOX_ERROR("Failed to lock the account, did not change limits. " "Try again later."); } return gotLock; } int SetLimit(Configuration &rConfig, const std::string &rUsername, int32_t ID, const char *SoftLimitStr, const char *HardLimitStr) { // Become the user specified in the config file? std::auto_ptr user; if(!rUsername.empty()) { // Username specified, change... user.reset(new UnixUser(rUsername.c_str())); user->ChangeProcessUser(true /* temporary */); // Change will be undone at the end of this function } // Load in the account database std::auto_ptr db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str())); // Already exists? if(!db->EntryExists(ID)) { BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << " does not exist."); return 1; } // Load it in BackupStoreAccounts acc(*db); std::string rootDir; int discSet; acc.GetAccountRoot(ID, rootDir, discSet); // Attempt to lock NamedLock writeLock; if(!GetWriteLockOnAccount(writeLock, rootDir, discSet)) { // Failed to get lock return 1; } // Load the info std::auto_ptr info(BackupStoreInfo::Load(ID, rootDir, discSet, false /* Read/Write */)); // Change the limits int64_t softlimit = SizeStringToBlocks(SoftLimitStr, discSet); int64_t hardlimit = SizeStringToBlocks(HardLimitStr, discSet); CheckSoftHardLimits(softlimit, hardlimit); info->ChangeLimits(softlimit, hardlimit); // Save info->Save(); BOX_NOTICE("Limits on account " << BOX_FORMAT_ACCOUNT(ID) << " changed to " << softlimit << " soft, " << hardlimit << " hard."); return 0; } int AccountInfo(Configuration &rConfig, int32_t ID) { // Load in the account database std::auto_ptr db( BackupStoreAccountDatabase::Read( rConfig.GetKeyValue("AccountDatabase").c_str())); // Exists? if(!db->EntryExists(ID)) { BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << " does not exist."); return 1; } // Load it in BackupStoreAccounts acc(*db); std::string rootDir; int discSet; acc.GetAccountRoot(ID, rootDir, discSet); std::auto_ptr info(BackupStoreInfo::Load(ID, rootDir, discSet, true /* ReadOnly */)); // Then print out lots of info std::cout << FormatUsageLineStart("Account ID", sMachineReadableOutput) << BOX_FORMAT_ACCOUNT(ID) << std::endl; std::cout << FormatUsageLineStart("Last object ID", sMachineReadableOutput) << BOX_FORMAT_OBJECTID(info->GetLastObjectIDUsed()) << std::endl; std::cout << FormatUsageLineStart("Used", sMachineReadableOutput) << BlockSizeToString(info->GetBlocksUsed(), info->GetBlocksHardLimit(), discSet) << std::endl; std::cout << FormatUsageLineStart("Old files", sMachineReadableOutput) << BlockSizeToString(info->GetBlocksInOldFiles(), info->GetBlocksHardLimit(), discSet) << std::endl; std::cout << FormatUsageLineStart("Deleted files", sMachineReadableOutput) << BlockSizeToString(info->GetBlocksInDeletedFiles(), info->GetBlocksHardLimit(), discSet) << std::endl; std::cout << FormatUsageLineStart("Directories", sMachineReadableOutput) << BlockSizeToString(info->GetBlocksInDirectories(), info->GetBlocksHardLimit(), discSet) << std::endl; std::cout << FormatUsageLineStart("Soft limit", sMachineReadableOutput) << BlockSizeToString(info->GetBlocksSoftLimit(), info->GetBlocksHardLimit(), discSet) << std::endl; std::cout << FormatUsageLineStart("Hard limit", sMachineReadableOutput) << BlockSizeToString(info->GetBlocksHardLimit(), info->GetBlocksHardLimit(), discSet) << std::endl; std::cout << FormatUsageLineStart("Client store marker", sMachineReadableOutput) << info->GetLastObjectIDUsed() << std::endl; return 0; } int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool AskForConfirmation) { // Check user really wants to do this if(AskForConfirmation) { BOX_WARNING("Really delete account " << BOX_FORMAT_ACCOUNT(ID) << "? (type 'yes' to confirm)"); char response[256]; if(::fgets(response, sizeof(response), stdin) == 0 || ::strcmp(response, "yes\n") != 0) { BOX_NOTICE("Deletion cancelled."); return 0; } } // Load in the account database std::auto_ptr db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str())); // Exists? if(!db->EntryExists(ID)) { BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << " does not exist."); return 1; } // Get info from the database BackupStoreAccounts acc(*db); std::string rootDir; int discSetNum; acc.GetAccountRoot(ID, rootDir, discSetNum); // Obtain a write lock, as the daemon user NamedLock writeLock; { // Bbecome the user specified in the config file std::auto_ptr user; if(!rUsername.empty()) { // Username specified, change... user.reset(new UnixUser(rUsername.c_str())); user->ChangeProcessUser(true /* temporary */); // Change will be undone at the end of this function } // Get a write lock if(!GetWriteLockOnAccount(writeLock, rootDir, discSetNum)) { // Failed to get lock return 1; } // Back to original user, but write is maintained } // Delete from account database db->DeleteEntry(ID); // Write back to disc db->Write(); // Remove the store files... // First, become the user specified in the config file std::auto_ptr user; if(!rUsername.empty()) { // Username specified, change... user.reset(new UnixUser(rUsername.c_str())); user->ChangeProcessUser(true /* temporary */); // Change will be undone at the end of this function } // Secondly, work out which directories need wiping std::vector toDelete; RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet discSet(rcontroller.GetDiscSet(discSetNum)); for(RaidFileDiscSet::const_iterator i(discSet.begin()); i != discSet.end(); ++i) { if(std::find(toDelete.begin(), toDelete.end(), *i) == toDelete.end()) { toDelete.push_back((*i) + DIRECTORY_SEPARATOR + rootDir); } } int retcode = 0; // Thirdly, delete the directories... for(std::vector::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d) { BOX_NOTICE("Deleting store directory " << (*d) << "..."); // Just use the rm command to delete the files std::string cmd("rm -rf "); cmd += *d; // Run command if(::system(cmd.c_str()) != 0) { BOX_ERROR("Failed to delete files in " << (*d) << ", delete them manually."); retcode = 1; } } // Success! return retcode; } int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool FixErrors, bool Quiet) { // Load in the account database std::auto_ptr db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str())); // Exists? if(!db->EntryExists(ID)) { BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << " does not exist."); return 1; } // Get info from the database BackupStoreAccounts acc(*db); std::string rootDir; int discSetNum; acc.GetAccountRoot(ID, rootDir, discSetNum); // Become the right user std::auto_ptr user; if(!rUsername.empty()) { // Username specified, change... user.reset(new UnixUser(rUsername.c_str())); user->ChangeProcessUser(true /* temporary */); // Change will be undone at the end of this function } // Check it BackupStoreCheck check(rootDir, discSetNum, ID, FixErrors, Quiet); check.Check(); return check.ErrorsFound()?1:0; } int CreateAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, int32_t DiscNumber, int32_t SoftLimit, int32_t HardLimit) { // Load in the account database std::auto_ptr db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str())); // Already exists? if(db->EntryExists(ID)) { BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << " already exists."); return 1; } // Create it. BackupStoreAccounts acc(*db); acc.Create(ID, DiscNumber, SoftLimit, HardLimit, rUsername); BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " created."); return 0; } void PrintUsageAndExit() { printf( "Usage: bbstoreaccounts [-c config_file] action account_id [args]\n" "Account ID is integer specified in hex\n" "\n" "Commands (and arguments):\n" " create \n" " Creates the specified account number (in hex with no 0x) on the\n" " specified raidfile disc set number (see raidfile.conf for valid\n" " set numbers) with the specified soft and hard limits (in blocks\n" " if suffixed with B, MB with M, GB with G)\n" " info [-m] \n" " Prints information about the specified account including number\n" " of blocks used. The -m option enable machine-readable output.\n" " setlimit \n" " Changes the limits of the account as specified. Numbers are\n" " interpreted as for the 'create' command (suffixed with B, M or G)\n" " delete [yes]\n" " Deletes the specified account. Prompts for confirmation unless\n" " the optional 'yes' parameter is provided.\n" " check [fix] [quiet]\n" " Checks the specified account for errors. If the 'fix' option is\n" " provided, any errors discovered that can be fixed automatically\n" " will be fixed. If the 'quiet' option is provided, less output is\n" " produced.\n" ); exit(2); } int main(int argc, const char *argv[]) { MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbstoreaccounts.memleaks", "bbstoreaccounts") MAINHELPER_START Logging::SetProgramName("bbstoreaccounts"); // Filename for configuration file? std::string configFilename; #ifdef WIN32 configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; #else configFilename = BOX_FILE_BBSTORED_DEFAULT_CONFIG; #endif // See if there's another entry on the command line int c; while((c = getopt(argc, (char * const *)argv, "c:m")) != -1) { switch(c) { case 'c': // store argument configFilename = optarg; break; case 'm': // enable machine readable output sMachineReadableOutput = true; break; case '?': default: PrintUsageAndExit(); } } // Adjust arguments argc -= optind; argv += optind; // Read in the configuration file std::string errs; std::auto_ptr config( Configuration::LoadAndVerify (configFilename, &BackupConfigFileVerify, errs)); if(config.get() == 0 || !errs.empty()) { BOX_ERROR("Invalid configuration file " << configFilename << ":" << errs); } // Get the user under which the daemon runs std::string username; { const Configuration &rserverConfig(config->GetSubConfiguration("Server")); if(rserverConfig.KeyExists("User")) { username = rserverConfig.GetKeyValue("User"); } } // Initialise the raid file controller RaidFileController &rcontroller(RaidFileController::GetController()); rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str()); // Then... check we have two arguments if(argc < 2) { PrintUsageAndExit(); } // Get the id int32_t id; if(::sscanf(argv[1], "%x", &id) != 1) { PrintUsageAndExit(); } // Now do the command. if(::strcmp(argv[0], "create") == 0) { // which disc? int32_t discnum; int32_t softlimit; int32_t hardlimit; if(argc < 5 || ::sscanf(argv[2], "%d", &discnum) != 1) { BOX_ERROR("create requires raid file disc number, " "soft and hard limits."); return 1; } // Decode limits softlimit = SizeStringToBlocks(argv[3], discnum); hardlimit = SizeStringToBlocks(argv[4], discnum); CheckSoftHardLimits(softlimit, hardlimit); // Create the account... return CreateAccount(*config, username, id, discnum, softlimit, hardlimit); } else if(::strcmp(argv[0], "info") == 0) { // Print information on this account return AccountInfo(*config, id); } else if(::strcmp(argv[0], "setlimit") == 0) { // Change the limits on this account if(argc < 4) { BOX_ERROR("setlimit requires soft and hard limits."); return 1; } return SetLimit(*config, username, id, argv[2], argv[3]); } else if(::strcmp(argv[0], "delete") == 0) { // Delete an account bool askForConfirmation = true; if(argc >= 3 && (::strcmp(argv[2], "yes") == 0)) { askForConfirmation = false; } return DeleteAccount(*config, username, id, askForConfirmation); } else if(::strcmp(argv[0], "check") == 0) { bool fixErrors = false; bool quiet = false; // Look at other options for(int o = 2; o < argc; ++o) { if(::strcmp(argv[o], "fix") == 0) { fixErrors = true; } else if(::strcmp(argv[o], "quiet") == 0) { quiet = true; } else { BOX_ERROR("Unknown option " << argv[o] << "."); return 2; } } // Check the account return CheckAccount(*config, username, id, fixErrors, quiet); } else { BOX_ERROR("Unknown command '" << argv[0] << "'."); return 1; } return 0; MAINHELPER_END } boxbackup/bin/bbstored/0000775000175000017500000000000011652362374015677 5ustar siretartsiretartboxbackup/bin/bbstored/BackupCommands.cpp0000664000175000017500000007732711512213011021264 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupCommands.cpp // Purpose: Implement commands for the Backup store protocol // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "autogen_BackupProtocolServer.h" #include "autogen_RaidFileException.h" #include "BackupConstants.h" #include "BackupStoreContext.h" #include "BackupStoreConstants.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" #include "BackupStoreInfo.h" #include "BufferedStream.h" #include "CollectInBufferStream.h" #include "FileStream.h" #include "InvisibleTempFileStream.h" #include "RaidFileController.h" #include "StreamableMemBlock.h" #include "MemLeakFindOn.h" #define CHECK_PHASE(phase) \ if(rContext.GetPhase() != BackupStoreContext::phase) \ { \ return std::auto_ptr(new BackupProtocolServerError( \ BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_NotInRightProtocolPhase)); \ } #define CHECK_WRITEABLE_SESSION \ if(rContext.SessionIsReadOnly()) \ { \ return std::auto_ptr(new BackupProtocolServerError( \ BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_SessionReadOnly)); \ } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerVersion::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Return the current version, or an error if the requested version isn't allowed // Created: 2003/08/20 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerVersion::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Version) // Correct version? if(mVersion != BACKUP_STORE_SERVER_VERSION) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_WrongVersion)); } // Mark the next phase rContext.SetPhase(BackupStoreContext::Phase_Login); // Return our version return std::auto_ptr(new BackupProtocolServerVersion(BACKUP_STORE_SERVER_VERSION)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerLogin::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Return the current version, or an error if the requested version isn't allowed // Created: 2003/08/20 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerLogin::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Login) // Check given client ID against the ID in the certificate certificate // and that the client actually has an account on this machine if(mClientID != rContext.GetClientID()) { BOX_WARNING("Failed login from client ID " << BOX_FORMAT_ACCOUNT(mClientID) << ": wrong certificate for this account"); return std::auto_ptr( new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_BadLogin)); } if(!rContext.GetClientHasAccount()) { BOX_WARNING("Failed login from client ID " << BOX_FORMAT_ACCOUNT(mClientID) << ": no such account on this server"); return std::auto_ptr( new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_BadLogin)); } // If we need to write, check that nothing else has got a write lock if((mFlags & Flags_ReadOnly) != Flags_ReadOnly) { // See if the context will get the lock if(!rContext.AttemptToGetWriteLock()) { BOX_WARNING("Failed to get write lock for Client ID " << BOX_FORMAT_ACCOUNT(mClientID)); return std::auto_ptr( new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_CannotLockStoreForWriting)); } // Debug: check we got the lock ASSERT(!rContext.SessionIsReadOnly()); } // Load the store info rContext.LoadStoreInfo(); // Get the last client store marker int64_t clientStoreMarker = rContext.GetClientStoreMarker(); // Mark the next phase rContext.SetPhase(BackupStoreContext::Phase_Commands); // Log login BOX_NOTICE("Login from Client ID " << BOX_FORMAT_ACCOUNT(mClientID) << " " << (((mFlags & Flags_ReadOnly) != Flags_ReadOnly) ?"Read/Write":"Read-only")); // Get the usage info for reporting to the client int64_t blocksUsed = 0, blocksSoftLimit = 0, blocksHardLimit = 0; rContext.GetStoreDiscUsageInfo(blocksUsed, blocksSoftLimit, blocksHardLimit); // Return success return std::auto_ptr(new BackupProtocolServerLoginConfirmed(clientStoreMarker, blocksUsed, blocksSoftLimit, blocksHardLimit)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerFinished::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Marks end of conversation (Protocol framework handles this) // Created: 2003/08/20 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerFinished::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { BOX_NOTICE("Session finished for Client ID " << BOX_FORMAT_ACCOUNT(rContext.GetClientID())); // Let the context know about it rContext.ReceivedFinishCommand(); // can be called in any phase return std::auto_ptr(new BackupProtocolServerFinished); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerListDirectory::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to list a directory // Created: 2003/09/02 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerListDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // Store the listing to a stream std::auto_ptr stream(new CollectInBufferStream); try { // Ask the context for a directory const BackupStoreDirectory &rdir( rContext.GetDirectory(mObjectID)); rdir.WriteToStream(*stream, mFlagsMustBeSet, mFlagsNotToBeSet, mSendAttributes, false /* never send dependency info to the client */); } catch (RaidFileException &e) { if (e.GetSubType() == RaidFileException::RaidFileDoesntExist) { return std::auto_ptr( new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExist)); } throw; } stream->SetForReading(); // Get the protocol to send the stream rProtocol.SendStreamAfterCommand(stream.release()); return std::auto_ptr( new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerStoreFile::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to store a file on the server // Created: 2003/09/02 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerStoreFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION std::auto_ptr hookResult = rContext.StartCommandHook(*this); if(hookResult.get()) { return hookResult; } // Check that the diff from file actually exists, if it's specified if(mDiffFromFileID != 0) { if(!rContext.ObjectExists(mDiffFromFileID, BackupStoreContext::ObjectExists_File)) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DiffFromFileDoesNotExist)); } } // A stream follows, which contains the file std::auto_ptr dirstream(rProtocol.ReceiveStream()); // Ask the context to store it int64_t id = 0; try { id = rContext.AddFile(*dirstream, mDirectoryObjectID, mModificationTime, mAttributesHash, mDiffFromFileID, mFilename, true /* mark files with same name as old versions */); } catch(BackupStoreException &e) { if(e.GetSubType() == BackupStoreException::AddedFileDoesNotVerify) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_FileDoesNotVerify)); } else if(e.GetSubType() == BackupStoreException::AddedFileExceedsStorageLimit) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_StorageLimitExceeded)); } else { throw; } } // Tell the caller what the file was return std::auto_ptr(new BackupProtocolServerSuccess(id)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerGetObject::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to get an arbitary object from the server // Created: 2003/09/03 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerGetObject::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // Check the object exists if(!rContext.ObjectExists(mObjectID)) { return std::auto_ptr(new BackupProtocolServerSuccess(NoObject)); } // Open the object std::auto_ptr object(rContext.OpenObject(mObjectID)); // Stream it to the peer rProtocol.SendStreamAfterCommand(object.release()); // Tell the caller what the file was return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerGetFile::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to get an file object from the server -- may have to do a bit of // work to get the object. // Created: 2003/09/03 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerGetFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // Check the objects exist if(!rContext.ObjectExists(mObjectID) || !rContext.ObjectExists(mInDirectory)) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExist)); } // Get the directory it's in const BackupStoreDirectory &rdir(rContext.GetDirectory(mInDirectory)); // Find the object within the directory BackupStoreDirectory::Entry *pfileEntry = rdir.FindEntryByID(mObjectID); if(pfileEntry == 0) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExistInDirectory)); } // The result std::auto_ptr stream; // Does this depend on anything? if(pfileEntry->GetDependsNewer() != 0) { // File exists, but is a patch from a new version. Generate the older version. std::vector patchChain; int64_t id = mObjectID; BackupStoreDirectory::Entry *en = 0; do { patchChain.push_back(id); en = rdir.FindEntryByID(id); if(en == 0) { BOX_ERROR("Object " << BOX_FORMAT_OBJECTID(mObjectID) << " in dir " << BOX_FORMAT_OBJECTID(mInDirectory) << " for account " << BOX_FORMAT_ACCOUNT(rContext.GetClientID()) << " references object " << BOX_FORMAT_OBJECTID(id) << " which does not exist in dir"); return std::auto_ptr( new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_PatchConsistencyError)); } id = en->GetDependsNewer(); } while(en != 0 && id != 0); // OK! The last entry in the chain is the full file, the others are patches back from it. // Open the last one, which is the current from file std::auto_ptr from(rContext.OpenObject(patchChain[patchChain.size() - 1])); // Then, for each patch in the chain, do a combine for(int p = ((int)patchChain.size()) - 2; p >= 0; --p) { // ID of patch int64_t patchID = patchChain[p]; // Open it a couple of times std::auto_ptr diff(rContext.OpenObject(patchID)); std::auto_ptr diff2(rContext.OpenObject(patchID)); // Choose a temporary filename for the result of the combination std::ostringstream fs; fs << rContext.GetStoreRoot() << ".recombinetemp." << p; std::string tempFn = RaidFileController::DiscSetPathToFileSystemPath( rContext.GetStoreDiscSet(), fs.str(), p + 16); // Open the temporary file std::auto_ptr combined; try { { // Write nastily to allow this to work with gcc 2.x std::auto_ptr t( new InvisibleTempFileStream( tempFn.c_str(), O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_TRUNC)); combined = t; } } catch(...) { // Make sure it goes ::unlink(tempFn.c_str()); throw; } // Do the combining BackupStoreFile::CombineFile(*diff, *diff2, *from, *combined); // Move to the beginning of the combined file combined->Seek(0, IOStream::SeekType_Absolute); // Then shuffle round for the next go if (from.get()) from->Close(); from = combined; } // Now, from contains a nice file to send to the client. Reorder it { // Write nastily to allow this to work with gcc 2.x std::auto_ptr t(BackupStoreFile::ReorderFileToStreamOrder(from.get(), true /* take ownership */)); stream = t; } // Release from file to avoid double deletion from.release(); } else { // Simple case: file already exists on disc ready to go // Open the object std::auto_ptr object(rContext.OpenObject(mObjectID)); BufferedStream buf(*object); // Verify it if(!BackupStoreFile::VerifyEncodedFileFormat(buf)) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_FileDoesNotVerify)); } // Reset stream -- seek to beginning object->Seek(0, IOStream::SeekType_Absolute); // Reorder the stream/file into stream order { // Write nastily to allow this to work with gcc 2.x std::auto_ptr t(BackupStoreFile::ReorderFileToStreamOrder(object.get(), true /* take ownership */)); stream = t; } // Object will be deleted when the stream is deleted, // so can release the object auto_ptr here to avoid // premature deletion object.release(); } // Stream the reordered stream to the peer rProtocol.SendStreamAfterCommand(stream.get()); // Don't delete the stream here stream.release(); // Tell the caller what the file was return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerCreateDirectory::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Create directory command // Created: 2003/09/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerCreateDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Get the stream containing the attributes std::auto_ptr attrstream(rProtocol.ReceiveStream()); // Collect the attributes -- do this now so no matter what the outcome, // the data has been absorbed. StreamableMemBlock attr; attr.Set(*attrstream, rProtocol.GetTimeout()); // Check to see if the hard limit has been exceeded if(rContext.HardLimitExceeded()) { // Won't allow creation if the limit has been exceeded return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_StorageLimitExceeded)); } bool alreadyExists = false; int64_t id = rContext.AddDirectory(mContainingDirectoryID, mDirectoryName, attr, mAttributesModTime, alreadyExists); if(alreadyExists) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DirectoryAlreadyExists)); } // Tell the caller what the file was return std::auto_ptr(new BackupProtocolServerSuccess(id)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerChangeDirAttributes::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Change attributes on directory // Created: 2003/09/06 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerChangeDirAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Get the stream containing the attributes std::auto_ptr attrstream(rProtocol.ReceiveStream()); // Collect the attributes -- do this now so no matter what the outcome, // the data has been absorbed. StreamableMemBlock attr; attr.Set(*attrstream, rProtocol.GetTimeout()); // Get the context to do it's magic rContext.ChangeDirAttributes(mObjectID, attr, mAttributesModTime); // Tell the caller what the file was return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerSetReplacementFileAttributes::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Change attributes on directory // Created: 2003/09/06 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerSetReplacementFileAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Get the stream containing the attributes std::auto_ptr attrstream(rProtocol.ReceiveStream()); // Collect the attributes -- do this now so no matter what the outcome, // the data has been absorbed. StreamableMemBlock attr; attr.Set(*attrstream, rProtocol.GetTimeout()); // Get the context to do it's magic int64_t objectID = 0; if(!rContext.ChangeFileAttributes(mFilename, mInDirectory, attr, mAttributesHash, objectID)) { // Didn't exist return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExist)); } // Tell the caller what the file was return std::auto_ptr(new BackupProtocolServerSuccess(objectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Delete a file // Created: 2003/10/21 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Context handles this int64_t objectID = 0; rContext.DeleteFile(mFilename, mInDirectory, objectID); // return the object ID or zero for not found return std::auto_ptr(new BackupProtocolServerSuccess(objectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerUndeleteFile::DoCommand( // BackupProtocolServer &, BackupStoreContext &) // Purpose: Undelete a file // Created: 2008-09-12 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerUndeleteFile::DoCommand( BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Context handles this bool result = rContext.UndeleteFile(mObjectID, mInDirectory); // return the object ID or zero for not found return std::auto_ptr( new BackupProtocolServerSuccess(result ? mObjectID : 0)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Delete a directory // Created: 2003/10/21 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Check it's not asking for the root directory to be deleted if(mObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_CannotDeleteRoot)); } // Context handles this rContext.DeleteDirectory(mObjectID); // return the object ID return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Undelete a directory // Created: 23/11/03 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Check it's not asking for the root directory to be deleted if(mObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_CannotDeleteRoot)); } // Context handles this rContext.DeleteDirectory(mObjectID, true /* undelete */); // return the object ID return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Command to set the client's store marker // Created: 2003/10/29 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Set the marker rContext.SetClientStoreMarker(mClientStoreMarker); // return store marker set return std::auto_ptr(new BackupProtocolServerSuccess(mClientStoreMarker)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Command to move an object from one directory to another // Created: 2003/11/12 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION // Let context do this, but modify error reporting on exceptions... try { rContext.MoveObject(mObjectID, mMoveFromDirectory, mMoveToDirectory, mNewFilename, (mFlags & Flags_MoveAllWithSameName) == Flags_MoveAllWithSameName, (mFlags & Flags_AllowMoveOverDeletedObject) == Flags_AllowMoveOverDeletedObject); } catch(BackupStoreException &e) { if(e.GetSubType() == BackupStoreException::CouldNotFindEntryInDirectory) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExist)); } else if(e.GetSubType() == BackupStoreException::NameAlreadyExistsInDirectory) { return std::auto_ptr(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_TargetNameExists)); } else { throw; } } // Return the object ID return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Command to find the name of an object // Created: 12/11/03 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // Create a stream for the list of filenames std::auto_ptr stream(new CollectInBufferStream); // Object and directory IDs int64_t objectID = mObjectID; int64_t dirID = mContainingDirectoryID; // Data to return in the reply int32_t numNameElements = 0; int16_t objectFlags = 0; int64_t modTime = 0; uint64_t attrModHash = 0; bool haveModTimes = false; do { // Check the directory really exists if(!rContext.ObjectExists(dirID, BackupStoreContext::ObjectExists_Directory)) { return std::auto_ptr(new BackupProtocolServerObjectName(BackupProtocolServerObjectName::NumNameElements_ObjectDoesntExist, 0, 0, 0)); } // Load up the directory const BackupStoreDirectory &rdir(rContext.GetDirectory(dirID)); // Find the element in this directory and store it's name if(objectID != ObjectID_DirectoryOnly) { const BackupStoreDirectory::Entry *en = rdir.FindEntryByID(objectID); // If this can't be found, then there is a problem... tell the caller it can't be found if(en == 0) { // Abort! return std::auto_ptr(new BackupProtocolServerObjectName(BackupProtocolServerObjectName::NumNameElements_ObjectDoesntExist, 0, 0, 0)); } // Store flags? if(objectFlags == 0) { objectFlags = en->GetFlags(); } // Store modification times? if(!haveModTimes) { modTime = en->GetModificationTime(); attrModHash = en->GetAttributesHash(); haveModTimes = true; } // Store the name in the stream en->GetName().WriteToStream(*stream); // Count of name elements ++numNameElements; } // Setup for next time round objectID = dirID; dirID = rdir.GetContainerID(); } while(objectID != 0 && objectID != BACKUPSTORE_ROOT_DIRECTORY_ID); // Stream to send? if(numNameElements > 0) { // Get the stream ready to go stream->SetForReading(); // Tell the protocol to send the stream rProtocol.SendStreamAfterCommand(stream.release()); } // Make reply return std::auto_ptr(new BackupProtocolServerObjectName(numNameElements, modTime, attrModHash, objectFlags)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Get the block index from a file, by ID // Created: 19/1/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // Open the file std::auto_ptr stream(rContext.OpenObject(mObjectID)); // Move the file pointer to the block index BackupStoreFile::MoveStreamPositionToBlockIndex(*stream); // Return the stream to the client rProtocol.SendStreamAfterCommand(stream.release()); // Return the object ID return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Get the block index from a file, by name within a directory // Created: 19/1/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // Get the directory const BackupStoreDirectory &dir(rContext.GetDirectory(mInDirectory)); // Find the latest object ID within it which has the same name int64_t objectID = 0; BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0) { if(en->GetName() == mFilename) { // Store the ID, if it's a newer ID than the last one if(en->GetObjectID() > objectID) { objectID = en->GetObjectID(); } } } // Found anything? if(objectID == 0) { // No... return a zero object ID return std::auto_ptr(new BackupProtocolServerSuccess(0)); } // Open the file std::auto_ptr stream(rContext.OpenObject(objectID)); // Move the file pointer to the block index BackupStoreFile::MoveStreamPositionToBlockIndex(*stream); // Return the stream to the client rProtocol.SendStreamAfterCommand(stream.release()); // Return the object ID return std::auto_ptr(new BackupProtocolServerSuccess(objectID)); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Return the amount of disc space used // Created: 19/4/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // Get store info from context const BackupStoreInfo &rinfo(rContext.GetBackupStoreInfo()); // Find block size RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet &rdiscSet(rcontroller.GetDiscSet(rinfo.GetDiscSetNumber())); // Return info return std::auto_ptr(new BackupProtocolServerAccountUsage( rinfo.GetBlocksUsed(), rinfo.GetBlocksInOldFiles(), rinfo.GetBlocksInDeletedFiles(), rinfo.GetBlocksInDirectories(), rinfo.GetBlocksSoftLimit(), rinfo.GetBlocksHardLimit(), rdiscSet.GetBlockSize() )); } // -------------------------------------------------------------------------- // // Function // Name: BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Return the amount of disc space used // Created: 19/4/04 // // -------------------------------------------------------------------------- std::auto_ptr BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) // // NOOP // return std::auto_ptr(new BackupProtocolServerIsAlive()); } boxbackup/bin/bbstored/HousekeepStoreAccount.cpp0000664000175000017500000010317011345265242022662 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HousekeepStoreAccount.cpp // Purpose: // Created: 11/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "HousekeepStoreAccount.h" #include "BackupStoreDaemon.h" #include "StoreStructure.h" #include "BackupStoreConstants.h" #include "RaidFileRead.h" #include "RaidFileWrite.h" #include "BackupStoreDirectory.h" #include "BackupStoreInfo.h" #include "NamedLock.h" #include "autogen_BackupStoreException.h" #include "BackupStoreFile.h" #include "BufferedStream.h" #include "MemLeakFindOn.h" // check every 32 directories scanned/files deleted #define POLL_INTERPROCESS_MSG_CHECK_FREQUENCY 32 // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::HousekeepStoreAccount(int, const std::string &, int, BackupStoreDaemon &) // Purpose: Constructor // Created: 11/12/03 // // -------------------------------------------------------------------------- HousekeepStoreAccount::HousekeepStoreAccount(int AccountID, const std::string &rStoreRoot, int StoreDiscSet, HousekeepingCallback* pHousekeepingCallback) : mAccountID(AccountID), mStoreRoot(rStoreRoot), mStoreDiscSet(StoreDiscSet), mpHousekeepingCallback(pHousekeepingCallback), mDeletionSizeTarget(0), mPotentialDeletionsTotalSize(0), mMaxSizeInPotentialDeletions(0), mBlocksUsed(0), mBlocksInOldFiles(0), mBlocksInDeletedFiles(0), mBlocksInDirectories(0), mBlocksUsedDelta(0), mBlocksInOldFilesDelta(0), mBlocksInDeletedFilesDelta(0), mBlocksInDirectoriesDelta(0), mFilesDeleted(0), mEmptyDirectoriesDeleted(0), mSuppressRefCountChangeWarnings(false), mCountUntilNextInterprocessMsgCheck(POLL_INTERPROCESS_MSG_CHECK_FREQUENCY) { } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::~HousekeepStoreAccount() // Purpose: Destructor // Created: 11/12/03 // // -------------------------------------------------------------------------- HousekeepStoreAccount::~HousekeepStoreAccount() { } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::DoHousekeeping() // Purpose: Perform the housekeeping // Created: 11/12/03 // // -------------------------------------------------------------------------- void HousekeepStoreAccount::DoHousekeeping(bool KeepTryingForever) { // Attempt to lock the account std::string writeLockFilename; StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet, writeLockFilename); NamedLock writeLock; if(!writeLock.TryAndGetLock(writeLockFilename.c_str(), 0600 /* restrictive file permissions */)) { if(KeepTryingForever) { BOX_WARNING("Failed to lock account for housekeeping, " "still trying..."); while(!writeLock.TryAndGetLock(writeLockFilename, 0600 /* restrictive file permissions */)) { sleep(1); } } else { // Couldn't lock the account -- just stop now return; } } // Load the store info to find necessary info for the housekeeping std::auto_ptr info(BackupStoreInfo::Load(mAccountID, mStoreRoot, mStoreDiscSet, false /* Read/Write */)); // Calculate how much should be deleted mDeletionSizeTarget = info->GetBlocksUsed() - info->GetBlocksSoftLimit(); if(mDeletionSizeTarget < 0) { mDeletionSizeTarget = 0; } // initialise the refcount database mNewRefCounts.clear(); // try to pre-allocate as much memory as we need mNewRefCounts.reserve(info->GetLastObjectIDUsed()); // initialise the refcount of the root entry mNewRefCounts.resize(BACKUPSTORE_ROOT_DIRECTORY_ID + 1, 0); mNewRefCounts[BACKUPSTORE_ROOT_DIRECTORY_ID] = 1; // Scan the directory for potential things to delete // This will also remove eligible items marked with RemoveASAP bool continueHousekeeping = ScanDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID); // If scan directory stopped for some reason, probably parent // instructed to terminate, stop now. if(!continueHousekeeping) { // If any files were marked "delete now", then update // the size of the store. if(mBlocksUsedDelta != 0 || mBlocksInOldFilesDelta != 0 || mBlocksInDeletedFilesDelta != 0) { info->ChangeBlocksUsed(mBlocksUsedDelta); info->ChangeBlocksInOldFiles(mBlocksInOldFilesDelta); info->ChangeBlocksInDeletedFiles(mBlocksInDeletedFilesDelta); // Save the store info back info->Save(); } return; } // Log any difference in opinion between the values recorded in // the store info, and the values just calculated for space usage. // BLOCK { int64_t used = info->GetBlocksUsed(); int64_t usedOld = info->GetBlocksInOldFiles(); int64_t usedDeleted = info->GetBlocksInDeletedFiles(); int64_t usedDirectories = info->GetBlocksInDirectories(); // If the counts were wrong, taking into account RemoveASAP // items deleted, log a message if((used + mBlocksUsedDelta) != mBlocksUsed || (usedOld + mBlocksInOldFilesDelta) != mBlocksInOldFiles || (usedDeleted + mBlocksInDeletedFilesDelta) != mBlocksInDeletedFiles || usedDirectories != mBlocksInDirectories) { // Log this BOX_ERROR("Housekeeping on account " << BOX_FORMAT_ACCOUNT(mAccountID) << " found " "and fixed wrong block counts: " "used (" << (used + mBlocksUsedDelta) << "," << mBlocksUsed << "), old (" << (usedOld + mBlocksInOldFilesDelta) << "," << mBlocksInOldFiles << "), deleted (" << (usedDeleted + mBlocksInDeletedFilesDelta) << "," << mBlocksInDeletedFiles << "), dirs (" << usedDirectories << "," << mBlocksInDirectories << ")"); } // If the current values don't match, store them if(used != mBlocksUsed || usedOld != mBlocksInOldFiles || usedDeleted != mBlocksInDeletedFiles || usedDirectories != (mBlocksInDirectories + mBlocksInDirectoriesDelta)) { // Set corrected values in store info info->CorrectAllUsedValues(mBlocksUsed, mBlocksInOldFiles, mBlocksInDeletedFiles, mBlocksInDirectories + mBlocksInDirectoriesDelta); info->Save(); } } // Reset the delta counts for files, as they will include // RemoveASAP flagged files deleted during the initial scan. // keep for reporting int64_t removeASAPBlocksUsedDelta = mBlocksUsedDelta; mBlocksUsedDelta = 0; mBlocksInOldFilesDelta = 0; mBlocksInDeletedFilesDelta = 0; // Go and delete items from the accounts bool deleteInterrupted = DeleteFiles(); // If that wasn't interrupted, remove any empty directories which // are also marked as deleted in their containing directory if(!deleteInterrupted) { deleteInterrupted = DeleteEmptyDirectories(); } // Log deletion if anything was deleted if(mFilesDeleted > 0 || mEmptyDirectoriesDeleted > 0) { BOX_INFO("Housekeeping on account " << BOX_FORMAT_ACCOUNT(mAccountID) << " " "removed " << (0 - (mBlocksUsedDelta + removeASAPBlocksUsedDelta)) << " blocks (" << mFilesDeleted << " files, " << mEmptyDirectoriesDeleted << " dirs)" << (deleteInterrupted?" and was interrupted":"")); } // We can only update the refcount database if we successfully // finished our scan of all directories, otherwise we don't actually // know which of the new counts are valid and which aren't // (we might not have seen second references to some objects, etc.) BackupStoreAccountDatabase::Entry account(mAccountID, mStoreDiscSet); std::auto_ptr apReferences; // try to load the reference count database try { apReferences = BackupStoreRefCountDatabase::Load(account, false); } catch(BoxException &e) { BOX_WARNING("Reference count database is missing or corrupted " "during housekeeping, creating a new one."); mSuppressRefCountChangeWarnings = true; BackupStoreRefCountDatabase::CreateForRegeneration(account); apReferences = BackupStoreRefCountDatabase::Load(account, false); } int64_t LastUsedObjectIdOnDisk = apReferences->GetLastObjectIDUsed(); for (int64_t ObjectID = BACKUPSTORE_ROOT_DIRECTORY_ID; ObjectID < mNewRefCounts.size(); ObjectID++) { if (ObjectID > LastUsedObjectIdOnDisk) { if (!mSuppressRefCountChangeWarnings) { BOX_WARNING("Reference count of object " << BOX_FORMAT_OBJECTID(ObjectID) << " not found in database, added" " with " << mNewRefCounts[ObjectID] << " references"); } apReferences->SetRefCount(ObjectID, mNewRefCounts[ObjectID]); LastUsedObjectIdOnDisk = ObjectID; continue; } BackupStoreRefCountDatabase::refcount_t OldRefCount = apReferences->GetRefCount(ObjectID); if (OldRefCount != mNewRefCounts[ObjectID]) { BOX_WARNING("Reference count of object " << BOX_FORMAT_OBJECTID(ObjectID) << " changed from " << OldRefCount << " to " << mNewRefCounts[ObjectID]); apReferences->SetRefCount(ObjectID, mNewRefCounts[ObjectID]); } } // zero excess references in the database for (int64_t ObjectID = mNewRefCounts.size(); ObjectID <= LastUsedObjectIdOnDisk; ObjectID++) { BackupStoreRefCountDatabase::refcount_t OldRefCount = apReferences->GetRefCount(ObjectID); BackupStoreRefCountDatabase::refcount_t NewRefCount = 0; if (OldRefCount != NewRefCount) { BOX_WARNING("Reference count of object " << BOX_FORMAT_OBJECTID(ObjectID) << " changed from " << OldRefCount << " to " << NewRefCount << " (not found)"); apReferences->SetRefCount(ObjectID, NewRefCount); } } // force file to be saved and closed before releasing the lock below apReferences.reset(); // Make sure the delta's won't cause problems if the counts are // really wrong, and it wasn't fixed because the store was // updated during the scan. if(mBlocksUsedDelta < (0 - info->GetBlocksUsed())) { mBlocksUsedDelta = (0 - info->GetBlocksUsed()); } if(mBlocksInOldFilesDelta < (0 - info->GetBlocksInOldFiles())) { mBlocksInOldFilesDelta = (0 - info->GetBlocksInOldFiles()); } if(mBlocksInDeletedFilesDelta < (0 - info->GetBlocksInDeletedFiles())) { mBlocksInDeletedFilesDelta = (0 - info->GetBlocksInDeletedFiles()); } if(mBlocksInDirectoriesDelta < (0 - info->GetBlocksInDirectories())) { mBlocksInDirectoriesDelta = (0 - info->GetBlocksInDirectories()); } // Update the usage counts in the store info->ChangeBlocksUsed(mBlocksUsedDelta); info->ChangeBlocksInOldFiles(mBlocksInOldFilesDelta); info->ChangeBlocksInDeletedFiles(mBlocksInDeletedFilesDelta); info->ChangeBlocksInDirectories(mBlocksInDirectoriesDelta); // Save the store info back info->Save(); // Explicity release the lock (would happen automatically on // going out of scope, included for code clarity) writeLock.ReleaseLock(); } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::MakeObjectFilename(int64_t, std::string &) // Purpose: Generate and place the filename for a given object ID // Created: 11/12/03 // // -------------------------------------------------------------------------- void HousekeepStoreAccount::MakeObjectFilename(int64_t ObjectID, std::string &rFilenameOut) { // Delegate to utility function StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, mStoreDiscSet, rFilenameOut, false /* don't bother ensuring the directory exists */); } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::ScanDirectory(int64_t) // Purpose: Private. Scan a directory for potentially deleteable // items, and add them to the list. Returns true if the // scan should continue. // Created: 11/12/03 // // -------------------------------------------------------------------------- bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) { #ifndef WIN32 if((--mCountUntilNextInterprocessMsgCheck) <= 0) { mCountUntilNextInterprocessMsgCheck = POLL_INTERPROCESS_MSG_CHECK_FREQUENCY; // Check for having to stop // Include account ID here as the specified account is locked if(mpHousekeepingCallback && mpHousekeepingCallback->CheckForInterProcessMsg(mAccountID)) { // Need to abort now return false; } } #endif // Get the filename std::string objectFilename; MakeObjectFilename(ObjectID, objectFilename); // Open it. std::auto_ptr dirStream(RaidFileRead::Open(mStoreDiscSet, objectFilename)); // Add the size of the directory on disc to the size being calculated int64_t originalDirSizeInBlocks = dirStream->GetDiscUsageInBlocks(); mBlocksInDirectories += originalDirSizeInBlocks; mBlocksUsed += originalDirSizeInBlocks; // Read the directory in BackupStoreDirectory dir; BufferedStream buf(*dirStream); dir.ReadFromStream(buf, IOStream::TimeOutInfinite); dirStream->Close(); // Is it empty? if(dir.GetNumberOfEntries() == 0) { // Add it to the list of directories to potentially delete mEmptyDirectories.push_back(dir.GetObjectID()); } // Calculate reference counts first, before we start requesting // files to be deleted. // BLOCK { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next()) != 0) { // This directory references this object if (mNewRefCounts.size() <= en->GetObjectID()) { mNewRefCounts.resize(en->GetObjectID() + 1, 0); } mNewRefCounts[en->GetObjectID()]++; } } // BLOCK { // Remove any files which are marked for removal as soon // as they become old or deleted. bool deletedSomething = false; do { // Iterate through the directory deletedSomething = false; BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0) { int16_t enFlags = en->GetFlags(); if((enFlags & BackupStoreDirectory::Entry::Flags_RemoveASAP) != 0 && (enFlags & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) != 0) { // Delete this immediately. DeleteFile(ObjectID, en->GetObjectID(), dir, objectFilename, originalDirSizeInBlocks); // flag as having done something deletedSomething = true; // Must start the loop from the beginning again, as iterator is now // probably invalid. break; } } } while(deletedSomething); } // BLOCK { // Add files to the list of potential deletions // map to count the distance from the mark typedef std::pair version_t; std::map markVersionAges; // map of pair (filename, mark number) -> version age // NOTE: use a reverse iterator to allow the distance from mark stuff to work BackupStoreDirectory::ReverseIterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0) { // Update recalculated usage sizes int16_t enFlags = en->GetFlags(); int64_t enSizeInBlocks = en->GetSizeInBlocks(); mBlocksUsed += enSizeInBlocks; if(enFlags & BackupStoreDirectory::Entry::Flags_OldVersion) mBlocksInOldFiles += enSizeInBlocks; if(enFlags & BackupStoreDirectory::Entry::Flags_Deleted) mBlocksInDeletedFiles += enSizeInBlocks; // Work out ages of this version from the last mark int32_t enVersionAge = 0; std::map::iterator enVersionAgeI( markVersionAges.find( version_t(en->GetName().GetEncodedFilename(), en->GetMarkNumber()))); if(enVersionAgeI != markVersionAges.end()) { enVersionAge = enVersionAgeI->second + 1; enVersionAgeI->second = enVersionAge; } else { markVersionAges[version_t(en->GetName().GetEncodedFilename(), en->GetMarkNumber())] = enVersionAge; } // enVersionAge is now the age of this version. // Potentially add it to the list if it's deleted, if it's an old version or deleted if((enFlags & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) != 0) { // Is deleted / old version. DelEn d; d.mObjectID = en->GetObjectID(); d.mInDirectory = ObjectID; d.mSizeInBlocks = en->GetSizeInBlocks(); d.mMarkNumber = en->GetMarkNumber(); d.mVersionAgeWithinMark = enVersionAge; d.mIsFlagDeleted = (enFlags & BackupStoreDirectory::Entry::Flags_Deleted) ? true : false; // Add it to the list mPotentialDeletions.insert(d); // Update various counts mPotentialDeletionsTotalSize += d.mSizeInBlocks; if(d.mSizeInBlocks > mMaxSizeInPotentialDeletions) mMaxSizeInPotentialDeletions = d.mSizeInBlocks; // Too much in the list of potential deletions? // (check against the deletion target + the max size in deletions, so that we never delete things // and take the total size below the deletion size target) if(mPotentialDeletionsTotalSize > (mDeletionSizeTarget + mMaxSizeInPotentialDeletions)) { int64_t sizeToRemove = mPotentialDeletionsTotalSize - (mDeletionSizeTarget + mMaxSizeInPotentialDeletions); bool recalcMaxSize = false; while(sizeToRemove > 0) { // Make iterator for the last element, while checking that there's something there in the first place. std::set::iterator i(mPotentialDeletions.end()); if(i != mPotentialDeletions.begin()) { // Nothing left in set break; } // Make this into an iterator pointing to the last element in the set --i; // Delete this one? if(sizeToRemove > i->mSizeInBlocks) { sizeToRemove -= i->mSizeInBlocks; if(i->mSizeInBlocks >= mMaxSizeInPotentialDeletions) { // Will need to recalculate the maximum size now, because we've just deleted that element recalcMaxSize = true; } mPotentialDeletions.erase(i); } else { // Over the size to remove, so stop now break; } } if(recalcMaxSize) { // Because an object which was the maximum size recorded was deleted from the set // it's necessary to recalculate this maximum. mMaxSizeInPotentialDeletions = 0; std::set::const_iterator i(mPotentialDeletions.begin()); for(; i != mPotentialDeletions.end(); ++i) { if(i->mSizeInBlocks > mMaxSizeInPotentialDeletions) { mMaxSizeInPotentialDeletions = i->mSizeInBlocks; } } } } } } } { // Recurse into subdirectories BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir)) != 0) { // Next level ASSERT((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == BackupStoreDirectory::Entry::Flags_Dir); if(!ScanDirectory(en->GetObjectID())) { // Halting operation return false; } } } return true; } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::DelEnCompare::operator()(const HousekeepStoreAccount::DelEn &, const HousekeepStoreAccount::DelEnd &) // Purpose: Comparison function for set // Created: 11/12/03 // // -------------------------------------------------------------------------- bool HousekeepStoreAccount::DelEnCompare::operator()(const HousekeepStoreAccount::DelEn &x, const HousekeepStoreAccount::DelEn &y) { // STL spec says this: // A Strict Weak Ordering is a Binary Predicate that compares two objects, returning true if the first precedes the second. // The sort order here is intended to preserve the entries of most value, that is, the newest objects // which are on a mark boundary. // Reverse order age, so oldest goes first if(x.mVersionAgeWithinMark > y.mVersionAgeWithinMark) { return true; } else if(x.mVersionAgeWithinMark < y.mVersionAgeWithinMark) { return false; } // but mark number in ascending order, so that the oldest marks are deleted first if(x.mMarkNumber < y.mMarkNumber) { return true; } else if(x.mMarkNumber > y.mMarkNumber) { return false; } // Just compare object ID now to put the oldest objects first return x.mObjectID < y.mObjectID; } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::DeleteFiles() // Purpose: Delete the files targetted for deletion, returning true if the operation was interrupted // Created: 15/12/03 // // -------------------------------------------------------------------------- bool HousekeepStoreAccount::DeleteFiles() { // Only delete files if the deletion target is greater than zero // (otherwise we delete one file each time round, which gradually deletes the old versions) if(mDeletionSizeTarget <= 0) { // Not interrupted return false; } // Iterate through the set of potential deletions, until enough has been deleted. // (there is likely to be more in the set than should be actually deleted). for(std::set::iterator i(mPotentialDeletions.begin()); i != mPotentialDeletions.end(); ++i) { #ifndef WIN32 if((--mCountUntilNextInterprocessMsgCheck) <= 0) { mCountUntilNextInterprocessMsgCheck = POLL_INTERPROCESS_MSG_CHECK_FREQUENCY; // Check for having to stop if(mpHousekeepingCallback && mpHousekeepingCallback->CheckForInterProcessMsg(mAccountID)) // include account ID here as the specified account is now locked { // Need to abort now return true; } } #endif // Load up the directory it's in // Get the filename std::string dirFilename; BackupStoreDirectory dir; int64_t dirSizeInBlocksOrig = 0; { MakeObjectFilename(i->mInDirectory, dirFilename); std::auto_ptr dirStream(RaidFileRead::Open(mStoreDiscSet, dirFilename)); dirSizeInBlocksOrig = dirStream->GetDiscUsageInBlocks(); dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); } // Delete the file DeleteFile(i->mInDirectory, i->mObjectID, dir, dirFilename, dirSizeInBlocksOrig); BOX_INFO("Housekeeping removed " << (i->mIsFlagDeleted ? "deleted" : "old") << " file " << BOX_FORMAT_OBJECTID(i->mObjectID) << " from dir " << BOX_FORMAT_OBJECTID(i->mInDirectory)); // Stop if the deletion target has been matched or exceeded // (checking here rather than at the beginning will tend to reduce the // space to slightly less than the soft limit, which will allow the backup // client to start uploading files again) if((0 - mBlocksUsedDelta) >= mDeletionSizeTarget) { break; } } return false; } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::DeleteFile(int64_t, int64_t, BackupStoreDirectory &, const std::string &, int64_t) // Purpose: Delete a file. Takes the directory already loaded in and the filename, // for efficiency in both the usage senarios. // Created: 15/7/04 // // -------------------------------------------------------------------------- void HousekeepStoreAccount::DeleteFile(int64_t InDirectory, int64_t ObjectID, BackupStoreDirectory &rDirectory, const std::string &rDirectoryFilename, int64_t OriginalDirSizeInBlocks) { // Find the entry inside the directory bool wasDeleted = false; bool wasOldVersion = false; int64_t deletedFileSizeInBlocks = 0; // A pointer to an object which requires commiting if the directory save goes OK std::auto_ptr padjustedEntry; // BLOCK { BackupStoreDirectory::Entry *pentry = rDirectory.FindEntryByID(ObjectID); if(pentry == 0) { BOX_ERROR("Housekeeping on account " << BOX_FORMAT_ACCOUNT(mAccountID) << " " "found error: object " << BOX_FORMAT_OBJECTID(ObjectID) << " " "not found in dir " << BOX_FORMAT_OBJECTID(InDirectory) << ", " "indicates logic error/corruption? Run " "bbstoreaccounts check fix"); return; } // Record the flags it's got set wasDeleted = ((pentry->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0); wasOldVersion = ((pentry->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) != 0); // Check this should be deleted if(!wasDeleted && !wasOldVersion) { // Things changed size we were last around return; } // Record size deletedFileSizeInBlocks = pentry->GetSizeInBlocks(); // If the entry is involved in a chain of patches, it needs to be handled // a bit more carefully. if(pentry->GetDependsNewer() != 0 && pentry->GetDependsOlder() == 0) { // This entry is a patch from a newer entry. Just need to update the info on that entry. BackupStoreDirectory::Entry *pnewer = rDirectory.FindEntryByID(pentry->GetDependsNewer()); if(pnewer == 0 || pnewer->GetDependsOlder() != ObjectID) { THROW_EXCEPTION(BackupStoreException, PatchChainInfoBadInDirectory); } // Change the info in the newer entry so that this no longer points to this entry pnewer->SetDependsOlder(0); } else if(pentry->GetDependsOlder() != 0) { BackupStoreDirectory::Entry *polder = rDirectory.FindEntryByID(pentry->GetDependsOlder()); if(pentry->GetDependsNewer() == 0) { // There exists an older version which depends on this one. Need to combine the two over that one. // Adjust the other entry in the directory if(polder == 0 || polder->GetDependsNewer() != ObjectID) { THROW_EXCEPTION(BackupStoreException, PatchChainInfoBadInDirectory); } // Change the info in the older entry so that this no longer points to this entry polder->SetDependsNewer(0); } else { // This entry is in the middle of a chain, and two patches need combining. // First, adjust the directory entries BackupStoreDirectory::Entry *pnewer = rDirectory.FindEntryByID(pentry->GetDependsNewer()); if(pnewer == 0 || pnewer->GetDependsOlder() != ObjectID || polder == 0 || polder->GetDependsNewer() != ObjectID) { THROW_EXCEPTION(BackupStoreException, PatchChainInfoBadInDirectory); } // Remove the middle entry from the linked list by simply using the values from this entry pnewer->SetDependsOlder(pentry->GetDependsOlder()); polder->SetDependsNewer(pentry->GetDependsNewer()); } // COMMON CODE to both cases // Generate the filename of the older version std::string objFilenameOlder; MakeObjectFilename(pentry->GetDependsOlder(), objFilenameOlder); // Open it twice (it's the diff) std::auto_ptr pdiff(RaidFileRead::Open(mStoreDiscSet, objFilenameOlder)); std::auto_ptr pdiff2(RaidFileRead::Open(mStoreDiscSet, objFilenameOlder)); // Open this file std::string objFilename; MakeObjectFilename(ObjectID, objFilename); std::auto_ptr pobjectBeingDeleted(RaidFileRead::Open(mStoreDiscSet, objFilename)); // And open a write file to overwrite the other directory entry padjustedEntry.reset(new RaidFileWrite(mStoreDiscSet, objFilenameOlder)); padjustedEntry->Open(true /* allow overwriting */); if(pentry->GetDependsNewer() == 0) { // There exists an older version which depends on this one. Need to combine the two over that one. BackupStoreFile::CombineFile(*pdiff, *pdiff2, *pobjectBeingDeleted, *padjustedEntry); } else { // This entry is in the middle of a chain, and two patches need combining. BackupStoreFile::CombineDiffs(*pobjectBeingDeleted, *pdiff, *pdiff2, *padjustedEntry); } // The file will be committed later when the directory is safely commited. // Work out the adjusted size int64_t newSize = padjustedEntry->GetDiscUsageInBlocks(); int64_t sizeDelta = newSize - polder->GetSizeInBlocks(); mBlocksUsedDelta += sizeDelta; if((polder->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0) { mBlocksInDeletedFilesDelta += sizeDelta; } if((polder->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) != 0) { mBlocksInOldFilesDelta += sizeDelta; } polder->SetSizeInBlocks(newSize); } // pentry no longer valid } // Delete it from the directory rDirectory.DeleteEntry(ObjectID); // Save directory back to disc // BLOCK int64_t dirRevisedSize = 0; { RaidFileWrite writeDir(mStoreDiscSet, rDirectoryFilename); writeDir.Open(true /* allow overwriting */); rDirectory.WriteToStream(writeDir); // get the disc usage (must do this before commiting it) dirRevisedSize = writeDir.GetDiscUsageInBlocks(); // Commit directory writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); // adjust usage counts for this directory if(dirRevisedSize > 0) { int64_t adjust = dirRevisedSize - OriginalDirSizeInBlocks; mBlocksUsedDelta += adjust; mBlocksInDirectoriesDelta += adjust; } } // Commit any new adjusted entry if(padjustedEntry.get() != 0) { padjustedEntry->Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); padjustedEntry.reset(); // delete it now } // Drop reference count by one. If it reaches zero, delete the file. if(--mNewRefCounts[ObjectID] == 0) { BOX_TRACE("Removing unreferenced object " << BOX_FORMAT_OBJECTID(ObjectID)); std::string objFilename; MakeObjectFilename(ObjectID, objFilename); RaidFileWrite del(mStoreDiscSet, objFilename); del.Delete(); } else { BOX_TRACE("Preserving object " << BOX_FORMAT_OBJECTID(ObjectID) << " with " << mNewRefCounts[ObjectID] << " references"); } // Adjust counts for the file ++mFilesDeleted; mBlocksUsedDelta -= deletedFileSizeInBlocks; if(wasDeleted) mBlocksInDeletedFilesDelta -= deletedFileSizeInBlocks; if(wasOldVersion) mBlocksInOldFilesDelta -= deletedFileSizeInBlocks; // Delete the directory? // Do this if... dir has zero entries, and is marked as deleted in it's containing directory if(rDirectory.GetNumberOfEntries() == 0) { // Candidate for deletion mEmptyDirectories.push_back(InDirectory); } } // -------------------------------------------------------------------------- // // Function // Name: HousekeepStoreAccount::DeleteEmptyDirectories() // Purpose: Remove any empty directories which are also marked as deleted in their containing directory, // returning true if the opertaion was interrupted // Created: 15/12/03 // // -------------------------------------------------------------------------- bool HousekeepStoreAccount::DeleteEmptyDirectories() { while(mEmptyDirectories.size() > 0) { std::vector toExamine; // Go through list for(std::vector::const_iterator i(mEmptyDirectories.begin()); i != mEmptyDirectories.end(); ++i) { #ifndef WIN32 if((--mCountUntilNextInterprocessMsgCheck) <= 0) { mCountUntilNextInterprocessMsgCheck = POLL_INTERPROCESS_MSG_CHECK_FREQUENCY; // Check for having to stop if(mpHousekeepingCallback && mpHousekeepingCallback->CheckForInterProcessMsg(mAccountID)) // include account ID here as the specified account is now locked { // Need to abort now return true; } } #endif // Do not delete the root directory if(*i == BACKUPSTORE_ROOT_DIRECTORY_ID) { continue; } DeleteEmptyDirectory(*i, toExamine); } // Remove contents of empty directories mEmptyDirectories.clear(); // Swap in new, so it's examined next time round mEmptyDirectories.swap(toExamine); } // Not interrupted return false; } void HousekeepStoreAccount::DeleteEmptyDirectory(int64_t dirId, std::vector& rToExamine) { // Load up the directory to potentially delete std::string dirFilename; BackupStoreDirectory dir; int64_t dirSizeInBlocks = 0; // BLOCK { MakeObjectFilename(dirId, dirFilename); // Check it actually exists (just in case it gets // added twice to the list) if(!RaidFileRead::FileExists(mStoreDiscSet, dirFilename)) { // doesn't exist, next! return; } // load std::auto_ptr dirStream( RaidFileRead::Open(mStoreDiscSet, dirFilename)); dirSizeInBlocks = dirStream->GetDiscUsageInBlocks(); dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); } // Make sure this directory is actually empty if(dir.GetNumberOfEntries() != 0) { // Not actually empty, try next one return; } // Candidate for deletion... open containing directory std::string containingDirFilename; BackupStoreDirectory containingDir; int64_t containingDirSizeInBlocksOrig = 0; { MakeObjectFilename(dir.GetContainerID(), containingDirFilename); std::auto_ptr containingDirStream( RaidFileRead::Open(mStoreDiscSet, containingDirFilename)); containingDirSizeInBlocksOrig = containingDirStream->GetDiscUsageInBlocks(); containingDir.ReadFromStream(*containingDirStream, IOStream::TimeOutInfinite); } // Find the entry BackupStoreDirectory::Entry *pdirentry = containingDir.FindEntryByID(dir.GetObjectID()); if((pdirentry != 0) && ((pdirentry->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0)) { // Should be deleted containingDir.DeleteEntry(dir.GetObjectID()); // Is the containing dir now a candidate for deletion? if(containingDir.GetNumberOfEntries() == 0) { rToExamine.push_back(containingDir.GetObjectID()); } // Write revised parent directory RaidFileWrite writeDir(mStoreDiscSet, containingDirFilename); writeDir.Open(true /* allow overwriting */); containingDir.WriteToStream(writeDir); // get the disc usage (must do this before commiting it) int64_t dirSize = writeDir.GetDiscUsageInBlocks(); // Commit directory writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); // adjust usage counts for this directory if(dirSize > 0) { int64_t adjust = dirSize - containingDirSizeInBlocksOrig; mBlocksUsedDelta += adjust; mBlocksInDirectoriesDelta += adjust; } // Delete the directory itself { RaidFileWrite del(mStoreDiscSet, dirFilename); del.Delete(); } BOX_INFO("Housekeeping removed empty deleted dir " << BOX_FORMAT_OBJECTID(dirId)); // And adjust usage counts for the directory that's // just been deleted mBlocksUsedDelta -= dirSizeInBlocks; mBlocksInDirectoriesDelta -= dirSizeInBlocks; // Update count ++mEmptyDirectoriesDeleted; } } boxbackup/bin/bbstored/BackupStoreContext.h0000664000175000017500000001353211221656111021626 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreContext.h // Purpose: Context for backup store server // Created: 2003/08/20 // // -------------------------------------------------------------------------- #ifndef BACKUPCONTEXT__H #define BACKUPCONTEXT__H #include #include #include #include "BackupStoreRefCountDatabase.h" #include "NamedLock.h" #include "ProtocolObject.h" #include "Utils.h" class BackupStoreDirectory; class BackupStoreFilename; class BackupStoreDaemon; class BackupStoreInfo; class IOStream; class BackupProtocolObject; class StreamableMemBlock; class HousekeepingInterface { public: virtual ~HousekeepingInterface() { } virtual void SendMessageToHousekeepingProcess(const void *Msg, int MsgLen) = 0; }; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreContext // Purpose: Context for backup store server // Created: 2003/08/20 // // -------------------------------------------------------------------------- class BackupStoreContext { public: BackupStoreContext(int32_t ClientID, HousekeepingInterface &rDaemon); ~BackupStoreContext(); private: BackupStoreContext(const BackupStoreContext &rToCopy); public: void ReceivedFinishCommand(); void CleanUp(); int32_t GetClientID() {return mClientID;} enum { Phase_START = 0, Phase_Version = 0, Phase_Login = 1, Phase_Commands = 2 }; int GetPhase() const {return mProtocolPhase;} void SetPhase(int NewPhase) {mProtocolPhase = NewPhase;} // Read only locking bool SessionIsReadOnly() {return mReadOnly;} bool AttemptToGetWriteLock(); void SetClientHasAccount(const std::string &rStoreRoot, int StoreDiscSet) {mClientHasAccount = true; mStoreRoot = rStoreRoot; mStoreDiscSet = StoreDiscSet;} bool GetClientHasAccount() const {return mClientHasAccount;} const std::string &GetStoreRoot() const {return mStoreRoot;} int GetStoreDiscSet() const {return mStoreDiscSet;} // Store info void LoadStoreInfo(); void SaveStoreInfo(bool AllowDelay = true); const BackupStoreInfo &GetBackupStoreInfo() const; // Client marker int64_t GetClientStoreMarker(); void SetClientStoreMarker(int64_t ClientStoreMarker); // Usage information void GetStoreDiscUsageInfo(int64_t &rBlocksUsed, int64_t &rBlocksSoftLimit, int64_t &rBlocksHardLimit); bool HardLimitExceeded(); // Reading directories // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::GetDirectory(int64_t) // Purpose: Return a reference to a directory. Valid only until the // next time a function which affects directories is called. // Mainly this funciton, and creation of files. // Created: 2003/09/02 // // -------------------------------------------------------------------------- const BackupStoreDirectory &GetDirectory(int64_t ObjectID) { // External callers aren't allowed to change it -- this function // merely turns the the returned directory const. return GetDirectoryInternal(ObjectID); } // Manipulating files/directories int64_t AddFile(IOStream &rFile, int64_t InDirectory, int64_t ModificationTime, int64_t AttributesHash, int64_t DiffFromFileID, const BackupStoreFilename &rFilename, bool MarkFileWithSameNameAsOldVersions); int64_t AddDirectory(int64_t InDirectory, const BackupStoreFilename &rFilename, const StreamableMemBlock &Attributes, int64_t AttributesModTime, bool &rAlreadyExists); void ChangeDirAttributes(int64_t Directory, const StreamableMemBlock &Attributes, int64_t AttributesModTime); bool ChangeFileAttributes(const BackupStoreFilename &rFilename, int64_t InDirectory, const StreamableMemBlock &Attributes, int64_t AttributesHash, int64_t &rObjectIDOut); bool DeleteFile(const BackupStoreFilename &rFilename, int64_t InDirectory, int64_t &rObjectIDOut); bool UndeleteFile(int64_t ObjectID, int64_t InDirectory); void DeleteDirectory(int64_t ObjectID, bool Undelete = false); void MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int64_t MoveToDirectory, const BackupStoreFilename &rNewFilename, bool MoveAllWithSameName, bool AllowMoveOverDeletedObject); // Manipulating objects enum { ObjectExists_Anything = 0, ObjectExists_File = 1, ObjectExists_Directory = 2 }; bool ObjectExists(int64_t ObjectID, int MustBe = ObjectExists_Anything); std::auto_ptr OpenObject(int64_t ObjectID); // Info int32_t GetClientID() const {return mClientID;} private: void MakeObjectFilename(int64_t ObjectID, std::string &rOutput, bool EnsureDirectoryExists = false); BackupStoreDirectory &GetDirectoryInternal(int64_t ObjectID); void SaveDirectory(BackupStoreDirectory &rDir, int64_t ObjectID); void RemoveDirectoryFromCache(int64_t ObjectID); void DeleteDirectoryRecurse(int64_t ObjectID, int64_t &rBlocksDeletedOut, bool Undelete); int64_t AllocateObjectID(); int32_t mClientID; HousekeepingInterface &mrDaemon; int mProtocolPhase; bool mClientHasAccount; std::string mStoreRoot; // has final directory separator int mStoreDiscSet; bool mReadOnly; NamedLock mWriteLock; int mSaveStoreInfoDelay; // how many times to delay saving the store info // Store info std::auto_ptr mpStoreInfo; // Refcount database std::auto_ptr mapRefCount; // Directory cache std::map mDirectoryCache; public: class TestHook { public: virtual std::auto_ptr StartCommand(BackupProtocolObject& rCommand) = 0; virtual ~TestHook() { } }; void SetTestHook(TestHook& rTestHook) { mpTestHook = &rTestHook; } std::auto_ptr StartCommandHook(BackupProtocolObject& rCommand) { if(mpTestHook) { return mpTestHook->StartCommand(rCommand); } return std::auto_ptr(); } private: TestHook* mpTestHook; }; #endif // BACKUPCONTEXT__H boxbackup/bin/bbstored/bbstored-config.in0000775000175000017500000001325011167477470021307 0ustar siretartsiretart#!@PERL@ use strict; # should be running as root if($> != 0) { printf "\nWARNING: this should be run as root\n\n" } # check and get command line parameters if($#ARGV < 2) { print <<__E; Setup bbstored config utility. Bad command line parameters. Usage: bbstored-config config-dir server-hostname username [raidfile-config] Parameters: config-dir is usually @sysconfdir_expanded@/boxbackup server-hostname is the hostname that clients will use to connect to this server username is the user to run the server under raidfile-config is optional. Use if you have a non-standard raidfile.conf file. __E exit(1); } # check for OPENSSL_CONF environment var being set if(exists $ENV{'OPENSSL_CONF'}) { print <<__E; --------------------------------------- WARNING: You have the OPENSSL_CONF environment variable set. Use of non-standard openssl configs may cause problems. --------------------------------------- __E } # default locations my $default_config_location = '@sysconfdir_expanded@/boxbackup/bbstored.conf'; # command line parameters my ($config_dir,$server,$username,$raidfile_config) = @ARGV; $raidfile_config = $config_dir . '/raidfile.conf' unless $raidfile_config ne ''; # check server exists, but don't bother checking that it's actually this machine. { my @r = gethostbyname($server); if($#r < 0) { die "Server '$server' not found. (check server name, test DNS lookup failed.)" } } # check this exists if(!-f $raidfile_config) { print "The RaidFile configuration file $raidfile_config doesn't exist.\nYou may need to create it with raidfile-config.\nWon't configure bbstored without it.\n"; exit(1); } # check that the user exists die "You shouldn't run bbstored as root" if $username eq 'root'; my $user_uid = 0; (undef,undef,$user_uid) = getpwnam($username); if($user_uid == 0) { die "User $username doesn't exist\n"; } # check that directories are writeable open RAIDCONF,$raidfile_config or die "Can't open $raidfile_config"; { my %done = (); while() { next unless m/Dir\d\s*=\s*(.+)/; my $d = $1; $d = $d.'/backup' if -e $d.'/backup'; print "Checking permissions on $d\n"; my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($d); my $req_perms = ($uid == $user_uid)?0700:0007; if(($mode & $req_perms) != $req_perms) { print "$username doesn't appear to have the necessary permissions on $d\n"; print "Either adjust permissions, or create a directory 'backup' inside the\n"; print "directory specified in raidfile.conf which is writable.\n"; exit(1); } } } close RAIDCONF; # ssl stuff my $private_key = "$config_dir/bbstored/$server-key.pem"; my $certificate_request = "$config_dir/bbstored/$server-csr.pem"; my $certificate = "$config_dir/bbstored/$server-cert.pem"; my $ca_root_cert = "$config_dir/bbstored/clientCA.pem"; # other files my $config_file = "$config_dir/bbstored.conf"; my $accounts_file = "$config_dir/bbstored/accounts.txt"; # summarise configuration print <<__E; Setup bbstored config utility. Configuration: Writing configuration file: $config_file Writing empty accounts file: $accounts_file Server hostname: $server RaidFile config: $raidfile_config __E # create directories if(!-d $config_dir) { print "Creating $config_dir...\n"; mkdir $config_dir,0755 or die "Can't create $config_dir"; } if(!-d "$config_dir/bbstored") { print "Creating $config_dir/bbstored\n"; mkdir "$config_dir/bbstored",0755 or die "Can't create $config_dir/bbstored"; } # create blank accounts file if(!-f $accounts_file) { print "Creating blank accounts file\n"; open ACC,">$accounts_file"; close ACC; } # generate the private key for the server if(!-f $private_key) { print "Generating private key...\n"; if(system("openssl genrsa -out $private_key 2048") != 0) { die "Couldn't generate private key." } } # generate a certificate request if(!-f $certificate_request) { die "Couldn't run openssl for CSR generation" unless open(CSR,"|openssl req -new -key $private_key -sha1 -out $certificate_request"); print CSR <<__E; . . . . . $server . . . __E close CSR; print "\n\n"; die "Certificate request wasn't created.\n" unless -f $certificate_request } # write the configuration file print "Writing configuration file $config_file\n"; open CONFIG,">$config_file" or die "Can't open config file for writing"; print CONFIG <<__E; RaidFileConf = $raidfile_config AccountDatabase = $accounts_file # Uncomment this line to see exactly what commands are being received from clients. # ExtendedLogging = yes # scan all accounts for files which need deleting every 15 minutes. TimeBetweenHousekeeping = 900 Server { PidFile = @localstatedir_expanded@/run/bbstored.pid User = $username ListenAddresses = inet:$server CertificateFile = $certificate PrivateKeyFile = $private_key TrustedCAsFile = $ca_root_cert } __E close CONFIG; # explain to the user what they need to do next my $daemon_args = ($config_file eq $default_config_location)?'':" $config_file"; print <<__E; =================================================================== bbstored basic configuration complete. What you need to do now... 1) Sign $certificate_request using the bbstored-certs utility. 2) Install the server certificate and root CA certificate as $certificate $ca_root_cert 3) You may wish to read the configuration file $config_file and adjust as appropraite. 4) Create accounts with bbstoreaccounts 5) Start the backup store daemon with the command @sbindir_expanded@/bbstored$daemon_args in /etc/rc.local, or your local equivalent. =================================================================== __E boxbackup/bin/bbstored/BackupConstants.h0000664000175000017500000000107211076365637021157 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupConstants.h // Purpose: Constants for the backup server and client // Created: 2003/08/20 // // -------------------------------------------------------------------------- #ifndef BACKUPCONSTANTS__H #define BACKUPCONSTANTS__H // 15 minutes to timeout (milliseconds) #define BACKUP_STORE_TIMEOUT (15*60*1000) // Should the store daemon convert files to Raid immediately? #define BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY true #endif // BACKUPCONSTANTS__H boxbackup/bin/bbstored/bbstored-certs.in0000775000175000017500000001413611512145740021150 0ustar siretartsiretart#!@PERL@ use strict; # validity period for root certificates -- default is 2038, the best we can do for now my $root_sign_period = int(((1<<31) - time()) / 86400); # but less so for client certificates my $sign_period = '5000'; # check and get command line parameters if($#ARGV < 1) { print <<__E; bbstored certificates utility. Bad command line parameters. Usage: bbstored-certs certs-dir command [arguments] certs-dir is the directory holding the root keys and certificates for the backup system command is the action to perform, taking parameters. Commands are init -- generate initial root certificates (certs-dir must not already exist) sign certificate-name -- sign a client certificate sign-server certificate-name -- sign a server certificate Signing requires confirmation that the certificate is correct and should be signed. __E exit(1); } # check for OPENSSL_CONF environment var being set if(exists $ENV{'OPENSSL_CONF'}) { print <<__E; --------------------------------------- WARNING: You have the OPENSSL_CONF environment variable set. Use of non-standard openssl configs may cause problems. --------------------------------------- __E } # directory structure: # # roots/ # clientCA.pem -- root certificate for client (used on server) # serverCA.pem -- root certificate for servers (used on clients) # keys/ # clientRootKey.pem -- root key for clients # serverRootKey.pem -- root key for servers # servers/ # hostname.pem -- certificate for server 'hostname' # clients/ # account.pem -- certficiate for account 'account' (ID in hex) # # check parameters my ($cert_dir,$command,@args) = @ARGV; # check directory exists if($command ne 'init') { if(!-d $cert_dir) { die "$cert_dir does not exist"; } } # run command if($command eq 'init') {&cmd_init;} elsif($command eq 'sign') {&cmd_sign;} elsif($command eq 'sign-server') {&cmd_sign_server;} else { die "Unknown command $command" } sub cmd_init { # create directories unless(mkdir($cert_dir,0700) && mkdir($cert_dir.'/roots',0700) && mkdir($cert_dir.'/keys',0700) && mkdir($cert_dir.'/servers',0700) && mkdir($cert_dir.'/clients',0700)) { die "Failed to create directory structure" } # create root keys and certrs cmd_init_create_root('client'); cmd_init_create_root('server'); } sub cmd_init_create_root { my $entity = $_[0]; my $cert = "$cert_dir/roots/".$entity.'CA.pem'; my $serial = "$cert_dir/roots/".$entity.'CA.srl'; my $key = "$cert_dir/keys/".$entity.'RootKey.pem'; my $csr = "$cert_dir/keys/".$entity.'RootCSR.pem'; # generate key if(system("openssl genrsa -out $key 2048") != 0) { die "Couldn't generate private key." } # make CSR die "Couldn't run openssl for CSR generation" unless open(CSR,"|openssl req -new -key $key -sha1 -out $csr"); print CSR <<__E; . . . . . Backup system $entity root . . . __E close CSR; print "\n\n"; die "Certificate request wasn't created.\n" unless -f $csr; # sign it to make a self-signed root CA key if(system("openssl x509 -req -in $csr -sha1 -extensions v3_ca -signkey $key -out $cert -days $root_sign_period") != 0) { die "Couldn't generate root certificate." } # write the initial serial number open SERIAL,">$serial" or die "Can't open $serial for writing"; print SERIAL "00\n"; close SERIAL; } sub cmd_sign { my $csr = $args[0]; if(!-f $csr) { die "$csr does not exist"; } # get the common name specified in this certificate my $common_name = get_csr_common_name($csr); # look OK? unless($common_name =~ m/\ABACKUP-([A-Fa-f0-9]+)\Z/) { die "The certificate presented does not appear to be a backup client certificate" } my $acc = $1; # check against filename if(!($csr =~ m/(\A|\/)([A-Fa-f0-9]+)-/) || $2 ne $acc) { die "Certificate request filename does not match name in certificate ($common_name)" } print <<__E; This certificate is for backup account $acc Ensure this matches the account number you are expecting. The filename is $csr which should include this account number, and additionally, you should check that you received it from the right person. Signing the wrong certificate compromises the security of your backup system. Would you like to sign this certificate? (type 'yes' to confirm) __E return unless get_confirmation(); # out certificate my $out_cert = "$cert_dir/clients/$acc"."-cert.pem"; # sign it! if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/clientCA.pem -CAkey $cert_dir/keys/clientRootKey.pem -out $out_cert -days $sign_period") != 0) { die "Signing failed" } # tell user what to do next print <<__E; Certificate signed. Send the files $out_cert $cert_dir/roots/serverCA.pem to the client. __E } sub cmd_sign_server { my $csr = $args[0]; if(!-f $csr) { die "$csr does not exist"; } # get the common name specified in this certificate my $common_name = get_csr_common_name($csr); # look OK? if($common_name !~ m/\A[-a-zA-Z0-9.]+\Z/) { die "Invalid server name" } print <<__E; This certificate is for backup server $common_name Signing the wrong certificate compromises the security of your backup system. Would you like to sign this certificate? (type 'yes' to confirm) __E return unless get_confirmation(); # out certificate my $out_cert = "$cert_dir/servers/$common_name"."-cert.pem"; # sign it! if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/serverCA.pem -CAkey $cert_dir/keys/serverRootKey.pem -out $out_cert -days $sign_period") != 0) { die "Signing failed" } # tell user what to do next print <<__E; Certificate signed. Install the files $out_cert $cert_dir/roots/clientCA.pem on the server. __E } sub get_csr_common_name { my $csr = $_[0]; open CSRTEXT,"openssl req -text -in $csr |" or die "Can't open openssl for reading"; my $subject; while() { $subject = $1 if m/Subject:.+?CN=([-\.\w]+)/ } close CSRTEXT; if($subject eq '') { die "No subject found in CSR $csr" } return $subject } sub get_confirmation() { my $line = ; chomp $line; if(lc $line ne 'yes') { print "CANCELLED\n"; return 0; } return 1; } boxbackup/bin/bbstored/HousekeepStoreAccount.h0000664000175000017500000000563611221742206022330 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: HousekeepStoreAccount.h // Purpose: Action class to perform housekeeping on a store account // Created: 11/12/03 // // -------------------------------------------------------------------------- #ifndef HOUSEKEEPSTOREACCOUNT__H #define HOUSEKEEPSTOREACCOUNT__H #include #include #include class BackupStoreDaemon; class BackupStoreDirectory; class HousekeepingCallback { public: virtual ~HousekeepingCallback() {} virtual bool CheckForInterProcessMsg(int AccountNum = 0, int MaximumWaitTime = 0) = 0; }; // -------------------------------------------------------------------------- // // Class // Name: HousekeepStoreAccount // Purpose: Action class to perform housekeeping on a store account // Created: 11/12/03 // // -------------------------------------------------------------------------- class HousekeepStoreAccount { public: HousekeepStoreAccount(int AccountID, const std::string &rStoreRoot, int StoreDiscSet, HousekeepingCallback* pHousekeepingCallback); ~HousekeepStoreAccount(); void DoHousekeeping(bool KeepTryingForever = false); private: // utility functions void MakeObjectFilename(int64_t ObjectID, std::string &rFilenameOut); bool ScanDirectory(int64_t ObjectID); bool DeleteFiles(); bool DeleteEmptyDirectories(); void DeleteEmptyDirectory(int64_t dirId, std::vector& rToExamine); void DeleteFile(int64_t InDirectory, int64_t ObjectID, BackupStoreDirectory &rDirectory, const std::string &rDirectoryFilename, int64_t OriginalDirSizeInBlocks); private: typedef struct { int64_t mObjectID; int64_t mInDirectory; int64_t mSizeInBlocks; int32_t mMarkNumber; int32_t mVersionAgeWithinMark; // 0 == current, 1 latest old version, etc bool mIsFlagDeleted; // false for files flagged "Old" } DelEn; struct DelEnCompare { bool operator()(const DelEn &x, const DelEn &y); }; int mAccountID; std::string mStoreRoot; int mStoreDiscSet; HousekeepingCallback* mpHousekeepingCallback; int64_t mDeletionSizeTarget; std::set mPotentialDeletions; int64_t mPotentialDeletionsTotalSize; int64_t mMaxSizeInPotentialDeletions; // List of directories which are empty, and might be good for deleting std::vector mEmptyDirectories; // The re-calculated blocks used stats int64_t mBlocksUsed; int64_t mBlocksInOldFiles; int64_t mBlocksInDeletedFiles; int64_t mBlocksInDirectories; // Deltas from deletion int64_t mBlocksUsedDelta; int64_t mBlocksInOldFilesDelta; int64_t mBlocksInDeletedFilesDelta; int64_t mBlocksInDirectoriesDelta; // Deletion count int64_t mFilesDeleted; int64_t mEmptyDirectoriesDeleted; // New reference count list std::vector mNewRefCounts; bool mSuppressRefCountChangeWarnings; // Poll frequency int mCountUntilNextInterprocessMsgCheck; }; #endif // HOUSEKEEPSTOREACCOUNT__H boxbackup/bin/bbstored/backupprotocol.txt0000664000175000017500000001420011062756126021461 0ustar siretartsiretart# # backup protocol definition # Name Backup IdentString Box-Backup:v=C ServerContextClass BackupStoreContext BackupStoreContext.h ClientType Filename BackupStoreFilenameClear BackupStoreFilenameClear.h ServerType Filename BackupStoreFilename BackupStoreFilename.h ImplementLog Server syslog ImplementLog Client syslog ImplementLog Client file LogTypeToText Client Filename \"%s\" VAR.GetClearFilename().c_str() BEGIN_OBJECTS # ------------------------------------------------------------------------------------- # Session commands # ------------------------------------------------------------------------------------- Error 0 IsError(Type,SubType) Reply int32 Type int32 SubType CONSTANT ErrorType 1000 CONSTANT Err_WrongVersion 1 CONSTANT Err_NotInRightProtocolPhase 2 CONSTANT Err_BadLogin 3 CONSTANT Err_CannotLockStoreForWriting 4 CONSTANT Err_SessionReadOnly 5 CONSTANT Err_FileDoesNotVerify 6 CONSTANT Err_DoesNotExist 7 CONSTANT Err_DirectoryAlreadyExists 8 CONSTANT Err_CannotDeleteRoot 9 CONSTANT Err_TargetNameExists 10 CONSTANT Err_StorageLimitExceeded 11 CONSTANT Err_DiffFromFileDoesNotExist 12 CONSTANT Err_DoesNotExistInDirectory 13 CONSTANT Err_PatchConsistencyError 14 Version 1 Command(Version) Reply int32 Version Login 2 Command(LoginConfirmed) int32 ClientID int32 Flags CONSTANT Flags_ReadOnly 1 LoginConfirmed 3 Reply int64 ClientStoreMarker int64 BlocksUsed int64 BlocksSoftLimit int64 BlocksHardLimit Finished 4 Command(Finished) Reply EndsConversation # generic success object Success 5 Reply int64 ObjectID SetClientStoreMarker 6 Command(Success) int64 ClientStoreMarker # ------------------------------------------------------------------------------------- # Generic object commands # ------------------------------------------------------------------------------------- GetObject 10 Command(Success) int64 ObjectID CONSTANT NoObject 0 # reply has stream following, if ObjectID != NoObject MoveObject 11 Command(Success) int64 ObjectID int64 MoveFromDirectory int64 MoveToDirectory int32 Flags Filename NewFilename CONSTANT Flags_MoveAllWithSameName 1 CONSTANT Flags_AllowMoveOverDeletedObject 2 # consider this an object command as, although it deals with directory entries, # it's not specific to either a file or a directory GetObjectName 12 Command(ObjectName) int64 ObjectID int64 ContainingDirectoryID CONSTANT ObjectID_DirectoryOnly 0 # set ObjectID to ObjectID_DirectoryOnly to only get info on the directory ObjectName 13 Reply int32 NumNameElements int64 ModificationTime int64 AttributesHash int16 Flags # NumNameElements is zero if the object doesn't exist CONSTANT NumNameElements_ObjectDoesntExist 0 # a stream of Filename objects follows, if and only if NumNameElements > 0 # ------------------------------------------------------------------------------------- # Directory commands # ------------------------------------------------------------------------------------- CreateDirectory 20 Command(Success) StreamWithCommand int64 ContainingDirectoryID int64 AttributesModTime Filename DirectoryName # stream following containing attributes ListDirectory 21 Command(Success) int64 ObjectID int16 FlagsMustBeSet int16 FlagsNotToBeSet bool SendAttributes # make sure these flags are synced with those in BackupStoreDirectory CONSTANT Flags_INCLUDE_EVERYTHING -1 CONSTANT Flags_EXCLUDE_NOTHING 0 CONSTANT Flags_EXCLUDE_EVERYTHING 15 CONSTANT Flags_File 1 CONSTANT Flags_Dir 2 CONSTANT Flags_Deleted 4 CONSTANT Flags_OldVersion 8 # make sure this is the same as in BackupStoreConstants.h CONSTANT RootDirectory 1 # reply has stream following Success object, containing a stored BackupStoreDirectory ChangeDirAttributes 22 Command(Success) StreamWithCommand int64 ObjectID int64 AttributesModTime # stream following containing attributes DeleteDirectory 23 Command(Success) int64 ObjectID UndeleteDirectory 24 Command(Success) int64 ObjectID # may not have exactly the desired effect if files within in have been deleted before the directory was deleted. # ------------------------------------------------------------------------------------- # File commands # ------------------------------------------------------------------------------------- StoreFile 30 Command(Success) StreamWithCommand int64 DirectoryObjectID int64 ModificationTime int64 AttributesHash int64 DiffFromFileID # 0 if the file is not a diff Filename Filename # then send a stream containing the encoded file GetFile 31 Command(Success) int64 InDirectory int64 ObjectID # error returned if not a file, or does not exist # reply has stream following, containing an encoded file IN STREAM ORDER # (use GetObject to get it in file order) SetReplacementFileAttributes 32 Command(Success) StreamWithCommand int64 InDirectory int64 AttributesHash Filename Filename # stream follows containing attributes DeleteFile 33 Command(Success) int64 InDirectory Filename Filename # will return 0 if the object couldn't be found in the specified directory GetBlockIndexByID 34 Command(Success) int64 ObjectID # stream of the block index follows the reply # returns an error if the object didn't exist GetBlockIndexByName 35 Command(Success) int64 InDirectory Filename Filename # Success object contains the found ID -- or 0 if the entry wasn't found in the directory # stream of the block index follows the reply if found ID != 0 UndeleteFile 36 Command(Success) int64 InDirectory int64 ObjectID # will return 0 if the object couldn't be found in the specified directory # ------------------------------------------------------------------------------------- # Information commands # ------------------------------------------------------------------------------------- GetAccountUsage 40 Command(AccountUsage) # no data members AccountUsage 41 Reply int64 BlocksUsed int64 BlocksInOldFiles int64 BlocksInDeletedFiles int64 BlocksInDirectories int64 BlocksSoftLimit int64 BlocksHardLimit int32 BlockSize GetIsAlive 42 Command(IsAlive) # no data members IsAlive 43 Reply # no data members boxbackup/bin/bbstored/BackupStoreDaemon.cpp0000664000175000017500000002241111062754121021737 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreDaemon.cpp // Purpose: Backup store daemon // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #ifdef HAVE_SYSLOG_H #include #endif #include "BackupStoreContext.h" #include "BackupStoreDaemon.h" #include "BackupStoreConfigVerify.h" #include "autogen_BackupProtocolServer.h" #include "RaidFileController.h" #include "BackupStoreAccountDatabase.h" #include "BackupStoreAccounts.h" #include "BannerText.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::BackupStoreDaemon() // Purpose: Constructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- BackupStoreDaemon::BackupStoreDaemon() : mpAccountDatabase(0), mpAccounts(0), mExtendedLogging(false), mHaveForkedHousekeeping(false), mIsHousekeepingProcess(false), mHousekeepingInited(false), mInterProcessComms(mInterProcessCommsSocket), mpTestHook(NULL) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::~BackupStoreDaemon() // Purpose: Destructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- BackupStoreDaemon::~BackupStoreDaemon() { // Must delete this one before the database ... if(mpAccounts != 0) { delete mpAccounts; mpAccounts = 0; } // ... as the mpAccounts object has a reference to it if(mpAccountDatabase != 0) { delete mpAccountDatabase; mpAccountDatabase = 0; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::DaemonName() // Purpose: Name of daemon // Created: 2003/08/20 // // -------------------------------------------------------------------------- const char *BackupStoreDaemon::DaemonName() const { return "bbstored"; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::DaemonBanner() // Purpose: Daemon banner // Created: 1/1/04 // // -------------------------------------------------------------------------- std::string BackupStoreDaemon::DaemonBanner() const { return BANNER_TEXT("Backup Store Server"); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::GetConfigVerify() // Purpose: Configuration definition // Created: 2003/08/20 // // -------------------------------------------------------------------------- const ConfigurationVerify *BackupStoreDaemon::GetConfigVerify() const { return &BackupConfigFileVerify; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::SetupInInitialProcess() // Purpose: Setup before we fork -- get raid file controller going // Created: 2003/08/20 // // -------------------------------------------------------------------------- void BackupStoreDaemon::SetupInInitialProcess() { const Configuration &config(GetConfiguration()); // Initialise the raid files controller RaidFileController &rcontroller = RaidFileController::GetController(); std::string raidFileConfig; #ifdef WIN32 if (!config.KeyExists("RaidFileConf")) { raidFileConfig = BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE; } else { raidFileConfig = config.GetKeyValue("RaidFileConf"); } #else raidFileConfig = config.GetKeyValue("RaidFileConf"); #endif rcontroller.Initialise(raidFileConfig); // Load the account database std::auto_ptr pdb(BackupStoreAccountDatabase::Read(config.GetKeyValue("AccountDatabase").c_str())); mpAccountDatabase = pdb.release(); // Create a accounts object mpAccounts = new BackupStoreAccounts(*mpAccountDatabase); // Ready to go! } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::Run() // Purpose: Run shim for the store daemon -- read some config details // Created: 2003/10/24 // // -------------------------------------------------------------------------- void BackupStoreDaemon::Run() { // Get extended logging flag mExtendedLogging = false; const Configuration &config(GetConfiguration()); mExtendedLogging = config.GetKeyValueBool("ExtendedLogging"); // Fork off housekeeping daemon -- must only do this the first // time Run() is called. Housekeeping runs synchronously on Win32 // because IsSingleProcess() is always true #ifndef WIN32 if(!IsSingleProcess() && !mHaveForkedHousekeeping) { // Open a socket pair for communication int sv[2] = {-1,-1}; if(::socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) != 0) { THROW_EXCEPTION(ServerException, SocketPairFailed) } int whichSocket = 0; // Fork switch(::fork()) { case -1: { // Error THROW_EXCEPTION(ServerException, ServerForkError) } break; case 0: { // In child process mIsHousekeepingProcess = true; SetProcessTitle("housekeeping, idle"); whichSocket = 1; // Change the log name ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); // Log that housekeeping started BOX_INFO("Housekeeping process started"); // Ignore term and hup // Parent will handle these and alert the // child via the socket, don't want to // randomly die! ::signal(SIGHUP, SIG_IGN); ::signal(SIGTERM, SIG_IGN); } break; default: { // Parent process whichSocket = 0; } break; } // Mark that this has been, so -HUP doesn't try and do this again mHaveForkedHousekeeping = true; // Attach the comms thing to the right socket, and close the other one mInterProcessCommsSocket.Attach(sv[whichSocket]); if(::close(sv[(whichSocket == 0)?1:0]) != 0) { THROW_EXCEPTION(ServerException, SocketCloseError) } } #endif // WIN32 if(mIsHousekeepingProcess) { // Housekeeping process -- do other stuff HousekeepingProcess(); } else { // In server process -- use the base class to do the magic ServerTLS::Run(); if (!mInterProcessCommsSocket.IsOpened()) { return; } // Why did it stop? Tell the housekeeping process to do the same if(IsReloadConfigWanted()) { mInterProcessCommsSocket.Write("h\n", 2); } if(IsTerminateWanted()) { mInterProcessCommsSocket.Write("t\n", 2); } } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::Connection(SocketStreamTLS &) // Purpose: Handles a connection, by catching exceptions and // delegating to Connection2 // Created: 2003/08/20 // // -------------------------------------------------------------------------- void BackupStoreDaemon::Connection(SocketStreamTLS &rStream) { try { Connection2(rStream); } catch(BoxException &e) { BOX_ERROR("Error in child process, terminating connection: " << e.what() << " (" << e.GetType() << "/" << e.GetSubType() << ")"); } catch(std::exception &e) { BOX_ERROR("Error in child process, terminating connection: " << e.what()); } catch(...) { BOX_ERROR("Error in child process, terminating connection: " << "unknown exception"); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::Connection2(SocketStreamTLS &) // Purpose: Handles a connection from bbackupd // Created: 2006/11/12 // // -------------------------------------------------------------------------- void BackupStoreDaemon::Connection2(SocketStreamTLS &rStream) { // Get the common name from the certificate std::string clientCommonName(rStream.GetPeerCommonName()); // Log the name BOX_INFO("Client certificate CN: " << clientCommonName); // Check it int32_t id; if(::sscanf(clientCommonName.c_str(), "BACKUP-%x", &id) != 1) { // Bad! Disconnect immediately return; } // Make ps listings clearer std::ostringstream tag; tag << "client=" << BOX_FORMAT_ACCOUNT(id); SetProcessTitle(tag.str().c_str()); Logging::Tagger tagWithClientID(tag.str()); // Create a context, using this ID BackupStoreContext context(id, *this); if (mpTestHook) { context.SetTestHook(*mpTestHook); } // See if the client has an account? if(mpAccounts && mpAccounts->AccountExists(id)) { std::string root; int discSet; mpAccounts->GetAccountRoot(id, root, discSet); context.SetClientHasAccount(root, discSet); } // Handle a connection with the backup protocol BackupProtocolServer server(rStream); server.SetLogToSysLog(mExtendedLogging); server.SetTimeout(BACKUP_STORE_TIMEOUT); try { server.DoServer(context); } catch(...) { LogConnectionStats(clientCommonName.c_str(), rStream); throw; } LogConnectionStats(clientCommonName.c_str(), rStream); context.CleanUp(); } void BackupStoreDaemon::LogConnectionStats(const char *commonName, const SocketStreamTLS &s) { // Log the amount of data transferred BOX_NOTICE("Connection statistics for " << commonName << ":" " IN=" << s.GetBytesRead() << " OUT=" << s.GetBytesWritten() << " TOTAL=" << (s.GetBytesRead() + s.GetBytesWritten())); } boxbackup/bin/bbstored/Makefile.extra0000664000175000017500000000037311345266370020462 0ustar siretartsiretart MAKEPROTOCOL = ../../lib/server/makeprotocol.pl GEN_CMD_SRV = $(MAKEPROTOCOL) Server backupprotocol.txt # AUTOGEN SEEDING autogen_BackupProtocolServer.cpp autogen_BackupProtocolServer.h: $(MAKEPROTOCOL) backupprotocol.txt $(_PERL) $(GEN_CMD_SRV) boxbackup/bin/bbstored/BBStoreDHousekeeping.cpp0000664000175000017500000001353011221742206022344 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BBStoreDHousekeeping.cpp // Purpose: Implementation of housekeeping functions for bbstored // Created: 11/12/03 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupStoreDaemon.h" #include "BackupStoreAccountDatabase.h" #include "BackupStoreAccounts.h" #include "HousekeepStoreAccount.h" #include "BoxTime.h" #include "Configuration.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::HousekeepingProcess() // Purpose: Do housekeeping // Created: 11/12/03 // // -------------------------------------------------------------------------- void BackupStoreDaemon::HousekeepingInit() { mLastHousekeepingRun = 0; } void BackupStoreDaemon::HousekeepingProcess() { HousekeepingInit(); // Get the time between housekeeping runs const Configuration &rconfig(GetConfiguration()); int64_t housekeepingInterval = SecondsToBoxTime(rconfig.GetKeyValueInt("TimeBetweenHousekeeping")); while(!StopRun()) { RunHousekeepingIfNeeded(); // Stop early? if(StopRun()) { break; } // Calculate how long should wait before doing the next // housekeeping run int64_t timeNow = GetCurrentBoxTime(); time_t secondsToGo = BoxTimeToSeconds( (mLastHousekeepingRun + housekeepingInterval) - timeNow); if(secondsToGo < 1) secondsToGo = 1; if(secondsToGo > 60) secondsToGo = 60; int32_t millisecondsToGo = ((int)secondsToGo) * 1000; // Check to see if there's any message pending CheckForInterProcessMsg(0 /* no account */, millisecondsToGo); } } void BackupStoreDaemon::RunHousekeepingIfNeeded() { // Get the time between housekeeping runs const Configuration &rconfig(GetConfiguration()); int64_t housekeepingInterval = SecondsToBoxTime(rconfig.GetKeyValueInt("TimeBetweenHousekeeping")); // Time now int64_t timeNow = GetCurrentBoxTime(); // Do housekeeping if the time interval has elapsed since the last check if((timeNow - mLastHousekeepingRun) < housekeepingInterval) { return; } // Store the time mLastHousekeepingRun = timeNow; BOX_INFO("Starting housekeeping"); // Get the list of accounts std::vector accounts; if(mpAccountDatabase) { mpAccountDatabase->GetAllAccountIDs(accounts); } SetProcessTitle("housekeeping, active"); // Check them all for(std::vector::const_iterator i = accounts.begin(); i != accounts.end(); ++i) { try { if(mpAccounts) { // Get the account root std::string rootDir; int discSet = 0; mpAccounts->GetAccountRoot(*i, rootDir, discSet); // Do housekeeping on this account HousekeepStoreAccount housekeeping(*i, rootDir, discSet, this); housekeeping.DoHousekeeping(); } } catch(BoxException &e) { BOX_ERROR("Housekeeping on account " << BOX_FORMAT_ACCOUNT(*i) << " threw exception, " "aborting run for this account: " << e.what() << " (" << e.GetType() << "/" << e.GetSubType() << ")"); } catch(std::exception &e) { BOX_ERROR("Housekeeping on account " << BOX_FORMAT_ACCOUNT(*i) << " threw exception, " "aborting run for this account: " << e.what()); } catch(...) { BOX_ERROR("Housekeeping on account " << BOX_FORMAT_ACCOUNT(*i) << " threw exception, " "aborting run for this account: " "unknown exception"); } int64_t timeNow = GetCurrentBoxTime(); time_t secondsToGo = BoxTimeToSeconds( (mLastHousekeepingRun + housekeepingInterval) - timeNow); if(secondsToGo < 1) secondsToGo = 1; if(secondsToGo > 60) secondsToGo = 60; int32_t millisecondsToGo = ((int)secondsToGo) * 1000; // Check to see if there's any message pending CheckForInterProcessMsg(0 /* no account */, millisecondsToGo); // Stop early? if(StopRun()) { break; } } BOX_INFO("Finished housekeeping"); // Placed here for accuracy, if StopRun() is true, for example. SetProcessTitle("housekeeping, idle"); } void BackupStoreDaemon::OnIdle() { if (!IsSingleProcess()) { return; } if (!mHousekeepingInited) { HousekeepingInit(); mHousekeepingInited = true; } RunHousekeepingIfNeeded(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::CheckForInterProcessMsg(int, int) // Purpose: Process a message, returning true if the housekeeping process // should abort for the specified account. // Created: 11/12/03 // // -------------------------------------------------------------------------- bool BackupStoreDaemon::CheckForInterProcessMsg(int AccountNum, int MaximumWaitTime) { if(!mInterProcessCommsSocket.IsOpened()) { return false; } // First, check to see if it's EOF -- this means something has gone wrong, and the housekeeping should terminate. if(mInterProcessComms.IsEOF()) { SetTerminateWanted(); return true; } // Get a line, and process the message std::string line; if(mInterProcessComms.GetLine(line, false /* no pre-processing */, MaximumWaitTime)) { BOX_TRACE("Housekeeping received command '" << line << "' over interprocess comms"); int account = 0; if(line == "h") { // HUP signal received by main process SetReloadConfigWanted(); return true; } else if(line == "t") { // Terminate signal received by main process SetTerminateWanted(); return true; } else if(sscanf(line.c_str(), "r%x", &account) == 1) { // Main process is trying to lock an account -- are we processing it? if(account == AccountNum) { // Yes! -- need to stop now so when it retries to get the lock, it will succeed BOX_INFO("Housekeeping on account " << BOX_FORMAT_ACCOUNT(AccountNum) << "giving way to client connection"); return true; } } } return false; } boxbackup/bin/bbstored/BackupStoreContext.cpp0000664000175000017500000014452111443463447022202 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreContext.cpp // Purpose: Context for backup store server // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include #include "BackupConstants.h" #include "BackupStoreContext.h" #include "BackupStoreDaemon.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" #include "BackupStoreInfo.h" #include "BackupStoreObjectMagic.h" #include "BufferedStream.h" #include "BufferedWriteStream.h" #include "FileStream.h" #include "InvisibleTempFileStream.h" #include "RaidFileController.h" #include "RaidFileRead.h" #include "RaidFileWrite.h" #include "StoreStructure.h" #include "MemLeakFindOn.h" // Maximum number of directories to keep in the cache // When the cache is bigger than this, everything gets // deleted. #ifdef BOX_RELEASE_BUILD #define MAX_CACHE_SIZE 32 #else #define MAX_CACHE_SIZE 2 #endif // Allow the housekeeping process 4 seconds to release an account #define MAX_WAIT_FOR_HOUSEKEEPING_TO_RELEASE_ACCOUNT 4 // Maximum amount of store info updates before it's actually saved to disc. #define STORE_INFO_SAVE_DELAY 96 // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::BackupStoreContext() // Purpose: Constructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- BackupStoreContext::BackupStoreContext(int32_t ClientID, HousekeepingInterface &rDaemon) : mClientID(ClientID), mrDaemon(rDaemon), mProtocolPhase(Phase_START), mClientHasAccount(false), mStoreDiscSet(-1), mReadOnly(true), mSaveStoreInfoDelay(STORE_INFO_SAVE_DELAY), mpTestHook(NULL) { } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::~BackupStoreContext() // Purpose: Destructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- BackupStoreContext::~BackupStoreContext() { // Delete the objects in the cache for(std::map::iterator i(mDirectoryCache.begin()); i != mDirectoryCache.end(); ++i) { delete (i->second); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::CleanUp() // Purpose: Clean up after a connection // Created: 16/12/03 // // -------------------------------------------------------------------------- void BackupStoreContext::CleanUp() { // Make sure the store info is saved, if it has been loaded, isn't read only and has been modified if(mpStoreInfo.get() && !(mpStoreInfo->IsReadOnly()) && mpStoreInfo->IsModified()) { mpStoreInfo->Save(); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::ReceivedFinishCommand() // Purpose: Called when the finish command is received by the protocol // Created: 16/12/03 // // -------------------------------------------------------------------------- void BackupStoreContext::ReceivedFinishCommand() { if(!mReadOnly && mpStoreInfo.get()) { // Save the store info, not delayed SaveStoreInfo(false); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::AttemptToGetWriteLock() // Purpose: Attempt to get a write lock for the store, and if so, unset the read only flags // Created: 2003/09/02 // // -------------------------------------------------------------------------- bool BackupStoreContext::AttemptToGetWriteLock() { // Make the filename of the write lock file std::string writeLockFile; StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet, writeLockFile); // Request the lock bool gotLock = mWriteLock.TryAndGetLock(writeLockFile.c_str(), 0600 /* restrictive file permissions */); if(!gotLock) { // The housekeeping process might have the thing open -- ask it to stop char msg[256]; int msgLen = sprintf(msg, "r%x\n", mClientID); // Send message mrDaemon.SendMessageToHousekeepingProcess(msg, msgLen); // Then try again a few times int tries = MAX_WAIT_FOR_HOUSEKEEPING_TO_RELEASE_ACCOUNT; do { ::sleep(1 /* second */); --tries; gotLock = mWriteLock.TryAndGetLock(writeLockFile.c_str(), 0600 /* restrictive file permissions */); } while(!gotLock && tries > 0); } if(gotLock) { // Got the lock, mark as not read only mReadOnly = false; } return gotLock; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::LoadStoreInfo() // Purpose: Load the store info from disc // Created: 2003/09/03 // // -------------------------------------------------------------------------- void BackupStoreContext::LoadStoreInfo() { if(mpStoreInfo.get() != 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoAlreadyLoaded) } // Load it up! std::auto_ptr i(BackupStoreInfo::Load(mClientID, mStoreRoot, mStoreDiscSet, mReadOnly)); // Check it if(i->GetAccountID() != mClientID) { THROW_EXCEPTION(BackupStoreException, StoreInfoForWrongAccount) } // Keep the pointer to it mpStoreInfo = i; BackupStoreAccountDatabase::Entry account(mClientID, mStoreDiscSet); // try to load the reference count database try { mapRefCount = BackupStoreRefCountDatabase::Load(account, false); } catch(BoxException &e) { BOX_WARNING("Reference count database is missing or corrupted, " "creating a new one, expect housekeeping to find and " "fix problems with reference counts later."); BackupStoreRefCountDatabase::CreateForRegeneration(account); mapRefCount = BackupStoreRefCountDatabase::Load(account, false); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::SaveStoreInfo(bool) // Purpose: Potentially delayed saving of the store info // Created: 16/12/03 // // -------------------------------------------------------------------------- void BackupStoreContext::SaveStoreInfo(bool AllowDelay) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } // Can delay saving it a little while? if(AllowDelay) { --mSaveStoreInfoDelay; if(mSaveStoreInfoDelay > 0) { return; } } // Want to save now mpStoreInfo->Save(); // Set count for next delay mSaveStoreInfoDelay = STORE_INFO_SAVE_DELAY; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::MakeObjectFilename(int64_t, std::string &, bool) // Purpose: Create the filename of an object in the store, optionally creating the // containing directory if it doesn't already exist. // Created: 2003/09/02 // // -------------------------------------------------------------------------- void BackupStoreContext::MakeObjectFilename(int64_t ObjectID, std::string &rOutput, bool EnsureDirectoryExists) { // Delegate to utility function StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, mStoreDiscSet, rOutput, EnsureDirectoryExists); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::GetDirectoryInternal(int64_t) // Purpose: Return a reference to a directory. Valid only until the // next time a function which affects directories is called. // Mainly this funciton, and creation of files. // Private version of this, which returns non-const directories. // Created: 2003/09/02 // // -------------------------------------------------------------------------- BackupStoreDirectory &BackupStoreContext::GetDirectoryInternal(int64_t ObjectID) { // Get the filename std::string filename; MakeObjectFilename(ObjectID, filename); // Already in cache? std::map::iterator item(mDirectoryCache.find(ObjectID)); if(item != mDirectoryCache.end()) { // Check the revision ID of the file -- does it need refreshing? int64_t revID = 0; if(!RaidFileRead::FileExists(mStoreDiscSet, filename, &revID)) { THROW_EXCEPTION(BackupStoreException, DirectoryHasBeenDeleted) } if(revID == item->second->GetRevisionID()) { // Looks good... return the cached object BOX_TRACE("Returning object " << BOX_FORMAT_OBJECTID(ObjectID) << " from cache, modtime = " << revID); return *(item->second); } BOX_TRACE("Refreshing object " << BOX_FORMAT_OBJECTID(ObjectID) << " in cache, modtime changed from " << item->second->GetRevisionID() << " to " << revID); // Delete this cached object delete item->second; mDirectoryCache.erase(item); } // Need to load it up // First check to see if the cache is too big if(mDirectoryCache.size() > MAX_CACHE_SIZE) { // Very simple. Just delete everything! for(std::map::iterator i(mDirectoryCache.begin()); i != mDirectoryCache.end(); ++i) { delete (i->second); } mDirectoryCache.clear(); } // Get a RaidFileRead to read it int64_t revID = 0; std::auto_ptr objectFile(RaidFileRead::Open(mStoreDiscSet, filename, &revID)); ASSERT(revID != 0); // New directory object std::auto_ptr dir(new BackupStoreDirectory); // Read it from the stream, then set it's revision ID BufferedStream buf(*objectFile); dir->ReadFromStream(buf, IOStream::TimeOutInfinite); dir->SetRevisionID(revID); // Make sure the size of the directory is available for writing the dir back int64_t dirSize = objectFile->GetDiscUsageInBlocks(); ASSERT(dirSize > 0); dir->SetUserInfo1_SizeInBlocks(dirSize); // Store in cache BackupStoreDirectory *pdir = dir.release(); try { mDirectoryCache[ObjectID] = pdir; } catch(...) { delete pdir; throw; } // Return it return *pdir; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::AllocateObjectID() // Purpose: Allocate a new object ID, tolerant of failures to save store info // Created: 16/12/03 // // -------------------------------------------------------------------------- int64_t BackupStoreContext::AllocateObjectID() { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } // Given that the store info may not be saved for STORE_INFO_SAVE_DELAY // times after it has been updated, this is a reasonable number of times // to try for finding an unused ID. // (Sizes used in the store info are fixed by the housekeeping process) int retryLimit = (STORE_INFO_SAVE_DELAY * 2); while(retryLimit > 0) { // Attempt to allocate an ID from the store int64_t id = mpStoreInfo->AllocateObjectID(); // Generate filename std::string filename; MakeObjectFilename(id, filename); // Check it doesn't exist if(!RaidFileRead::FileExists(mStoreDiscSet, filename)) { // Success! return id; } // Decrement retry count, and try again --retryLimit; // Mark that the store info should be saved as soon as possible mSaveStoreInfoDelay = 0; BOX_WARNING("When allocating object ID, found that " << BOX_FORMAT_OBJECTID(id) << " is already in use"); } THROW_EXCEPTION(BackupStoreException, CouldNotFindUnusedIDDuringAllocation) } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::AddFile(IOStream &, int64_t, // int64_t, int64_t, const BackupStoreFilename &, bool) // Purpose: Add a file to the store, from a given stream, into // a specified directory. Returns object ID of the new // file. // Created: 2003/09/03 // // -------------------------------------------------------------------------- int64_t BackupStoreContext::AddFile(IOStream &rFile, int64_t InDirectory, int64_t ModificationTime, int64_t AttributesHash, int64_t DiffFromFileID, const BackupStoreFilename &rFilename, bool MarkFileWithSameNameAsOldVersions) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } // This is going to be a bit complex to make sure it copes OK // with things going wrong. // The only thing which isn't safe is incrementing the object ID // and keeping the blocks used entirely accurate -- but these // aren't big problems if they go horribly wrong. The sizes will // be corrected the next time the account has a housekeeping run, // and the object ID allocation code is tolerant of missed IDs. // (the info is written lazily, so these are necessary) // Get the directory we want to modify BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory)); // Allocate the next ID int64_t id = AllocateObjectID(); // Stream the file to disc std::string fn; MakeObjectFilename(id, fn, true /* make sure the directory it's in exists */); int64_t blocksUsed = 0; RaidFileWrite *ppreviousVerStoreFile = 0; bool reversedDiffIsCompletelyDifferent = false; int64_t oldVersionNewBlocksUsed = 0; try { RaidFileWrite storeFile(mStoreDiscSet, fn); storeFile.Open(false /* no overwriting */); int64_t spaceAdjustFromDiff = 0; // size adjustment from use of patch in old file // Diff or full file? if(DiffFromFileID == 0) { // A full file, just store to disc if(!rFile.CopyStreamTo(storeFile, BACKUP_STORE_TIMEOUT)) { THROW_EXCEPTION(BackupStoreException, ReadFileFromStreamTimedOut) } } else { // Check that the diffed from ID actually exists in the directory if(dir.FindEntryByID(DiffFromFileID) == 0) { THROW_EXCEPTION(BackupStoreException, DiffFromIDNotFoundInDirectory) } // Diff file, needs to be recreated. // Choose a temporary filename. std::string tempFn(RaidFileController::DiscSetPathToFileSystemPath(mStoreDiscSet, fn + ".difftemp", 1 /* NOT the same disc as the write file, to avoid using lots of space on the same disc unnecessarily */)); try { // Open it twice #ifdef WIN32 InvisibleTempFileStream diff(tempFn.c_str(), O_RDWR | O_CREAT | O_BINARY); InvisibleTempFileStream diff2(tempFn.c_str(), O_RDWR | O_BINARY); #else FileStream diff(tempFn.c_str(), O_RDWR | O_CREAT | O_EXCL); FileStream diff2(tempFn.c_str(), O_RDONLY); // Unlink it immediately, so it definitely goes away if(::unlink(tempFn.c_str()) != 0) { THROW_EXCEPTION(CommonException, OSFileError); } #endif // Stream the incoming diff to this temporary file if(!rFile.CopyStreamTo(diff, BACKUP_STORE_TIMEOUT)) { THROW_EXCEPTION(BackupStoreException, ReadFileFromStreamTimedOut) } // Verify the diff diff.Seek(0, IOStream::SeekType_Absolute); if(!BackupStoreFile::VerifyEncodedFileFormat(diff)) { THROW_EXCEPTION(BackupStoreException, AddedFileDoesNotVerify) } // Seek to beginning of diff file diff.Seek(0, IOStream::SeekType_Absolute); // Filename of the old version std::string oldVersionFilename; MakeObjectFilename(DiffFromFileID, oldVersionFilename, false /* no need to make sure the directory it's in exists */); // Reassemble that diff -- open previous file, and combine the patch and file std::auto_ptr from(RaidFileRead::Open(mStoreDiscSet, oldVersionFilename)); BackupStoreFile::CombineFile(diff, diff2, *from, storeFile); // Then... reverse the patch back (open the from file again, and create a write file to overwrite it) std::auto_ptr from2(RaidFileRead::Open(mStoreDiscSet, oldVersionFilename)); ppreviousVerStoreFile = new RaidFileWrite(mStoreDiscSet, oldVersionFilename); ppreviousVerStoreFile->Open(true /* allow overwriting */); from->Seek(0, IOStream::SeekType_Absolute); diff.Seek(0, IOStream::SeekType_Absolute); BackupStoreFile::ReverseDiffFile(diff, *from, *from2, *ppreviousVerStoreFile, DiffFromFileID, &reversedDiffIsCompletelyDifferent); // Store disc space used oldVersionNewBlocksUsed = ppreviousVerStoreFile->GetDiscUsageInBlocks(); // And make a space adjustment for the size calculation spaceAdjustFromDiff = from->GetDiscUsageInBlocks() - oldVersionNewBlocksUsed; // Everything cleans up here... } catch(...) { // Be very paranoid about deleting this temp file -- we could only leave a zero byte file anyway ::unlink(tempFn.c_str()); throw; } } // Get the blocks used blocksUsed = storeFile.GetDiscUsageInBlocks(); // Exceeds the hard limit? if((mpStoreInfo->GetBlocksUsed() + blocksUsed - spaceAdjustFromDiff) > mpStoreInfo->GetBlocksHardLimit()) { THROW_EXCEPTION(BackupStoreException, AddedFileExceedsStorageLimit) // The store file will be deleted automatically by the RaidFile object } // Commit the file storeFile.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); } catch(...) { // Delete any previous version store file if(ppreviousVerStoreFile != 0) { delete ppreviousVerStoreFile; ppreviousVerStoreFile = 0; } throw; } // Verify the file -- only necessary for non-diffed versions // NOTE: No need to catch exceptions and delete ppreviousVerStoreFile, because // in the non-diffed code path it's never allocated. if(DiffFromFileID == 0) { std::auto_ptr checkFile(RaidFileRead::Open(mStoreDiscSet, fn)); if(!BackupStoreFile::VerifyEncodedFileFormat(*checkFile)) { // Error! Delete the file RaidFileWrite del(mStoreDiscSet, fn); del.Delete(); // Exception THROW_EXCEPTION(BackupStoreException, AddedFileDoesNotVerify) } } // Modify the directory -- first make all files with the same name // marked as an old version int64_t blocksInOldFiles = 0; try { if(MarkFileWithSameNameAsOldVersions) { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *e = 0; while((e = i.Next()) != 0) { // First, check it's not an old version (cheaper comparison) if((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0) { // Compare name if(e->GetName() == rFilename) { // Check that it's definately not an old version ASSERT((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0); // Set old version flag e->AddFlags(BackupStoreDirectory::Entry::Flags_OldVersion); // Can safely do this, because we know we won't be here if it's already // an old version blocksInOldFiles += e->GetSizeInBlocks(); } } } } // Then the new entry BackupStoreDirectory::Entry *pnewEntry = dir.AddEntry(rFilename, ModificationTime, id, blocksUsed, BackupStoreDirectory::Entry::Flags_File, AttributesHash); // Adjust for the patch back stuff? if(DiffFromFileID != 0) { // Get old version entry BackupStoreDirectory::Entry *poldEntry = dir.FindEntryByID(DiffFromFileID); ASSERT(poldEntry != 0); // Adjust dependency info of file? if(!reversedDiffIsCompletelyDifferent) { poldEntry->SetDependsNewer(id); pnewEntry->SetDependsOlder(DiffFromFileID); } // Adjust size of old entry int64_t oldSize = poldEntry->GetSizeInBlocks(); poldEntry->SetSizeInBlocks(oldVersionNewBlocksUsed); // And adjust blocks used count, for later adjustment blocksUsed += (oldVersionNewBlocksUsed - oldSize); blocksInOldFiles += (oldVersionNewBlocksUsed - oldSize); } // Write the directory back to disc SaveDirectory(dir, InDirectory); // Commit the old version's new patched version, now that the directory safely reflects // the state of the files on disc. if(ppreviousVerStoreFile != 0) { ppreviousVerStoreFile->Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); delete ppreviousVerStoreFile; ppreviousVerStoreFile = 0; } } catch(...) { // Back out on adding that file RaidFileWrite del(mStoreDiscSet, fn); del.Delete(); // Remove this entry from the cache RemoveDirectoryFromCache(InDirectory); // Delete any previous version store file if(ppreviousVerStoreFile != 0) { delete ppreviousVerStoreFile; ppreviousVerStoreFile = 0; } // Don't worry about the incremented number in the store info throw; } // Check logic ASSERT(ppreviousVerStoreFile == 0); // Modify the store info mpStoreInfo->ChangeBlocksUsed(blocksUsed); mpStoreInfo->ChangeBlocksInOldFiles(blocksInOldFiles); // Increment reference count on the new directory to one mapRefCount->AddReference(id); // Save the store info -- can cope if this exceptions because infomation // will be rebuilt by housekeeping, and ID allocation can recover. SaveStoreInfo(); // Return the ID to the caller return id; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &) // Purpose: Deletes a file, returning true if the file existed. Object ID returned too, set to zero if not found. // Created: 2003/10/21 // // -------------------------------------------------------------------------- bool BackupStoreContext::DeleteFile(const BackupStoreFilename &rFilename, int64_t InDirectory, int64_t &rObjectIDOut) { // Essential checks! if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } // Find the directory the file is in (will exception if it fails) BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory)); // Setup flags bool fileExisted = false; bool madeChanges = false; rObjectIDOut = 0; // not found // Count of deleted blocks int64_t blocksDel = 0; try { // Iterate through directory, only looking at files which haven't been deleted BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *e = 0; while((e = i.Next(BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_Deleted)) != 0) { // Compare name if(e->GetName() == rFilename) { // Check that it's definately not already deleted ASSERT((e->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) == 0); // Set deleted flag e->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted); // Mark as made a change madeChanges = true; // Can safely do this, because we know we won't be here if it's already // an old version blocksDel += e->GetSizeInBlocks(); // Is this the last version? if((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0) { // Yes. It's been found. rObjectIDOut = e->GetObjectID(); fileExisted = true; } } } // Save changes? if(madeChanges) { // Save the directory back SaveDirectory(dir, InDirectory); // Modify the store info, and write mpStoreInfo->ChangeBlocksInDeletedFiles(blocksDel); // Maybe postponed save of store info SaveStoreInfo(); } } catch(...) { RemoveDirectoryFromCache(InDirectory); throw; } return fileExisted; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::UndeleteFile(int64_t, int64_t) // Purpose: Undeletes a file, if it exists, returning true if // the file existed. // Created: 2003/10/21 // // -------------------------------------------------------------------------- bool BackupStoreContext::UndeleteFile(int64_t ObjectID, int64_t InDirectory) { // Essential checks! if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } // Find the directory the file is in (will exception if it fails) BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory)); // Setup flags bool fileExisted = false; bool madeChanges = false; // Count of deleted blocks int64_t blocksDel = 0; try { // Iterate through directory, only looking at files which have been deleted BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *e = 0; while((e = i.Next(BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 0)) != 0) { // Compare name if(e->GetObjectID() == ObjectID) { // Check that it's definitely already deleted ASSERT((e->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0); // Clear deleted flag e->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted); // Mark as made a change madeChanges = true; blocksDel -= e->GetSizeInBlocks(); // Is this the last version? if((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0) { // Yes. It's been found. fileExisted = true; } } } // Save changes? if(madeChanges) { // Save the directory back SaveDirectory(dir, InDirectory); // Modify the store info, and write mpStoreInfo->ChangeBlocksInDeletedFiles(blocksDel); // Maybe postponed save of store info SaveStoreInfo(); } } catch(...) { RemoveDirectoryFromCache(InDirectory); throw; } return fileExisted; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::RemoveDirectoryFromCache(int64_t) // Purpose: Remove directory from cache // Created: 2003/09/04 // // -------------------------------------------------------------------------- void BackupStoreContext::RemoveDirectoryFromCache(int64_t ObjectID) { std::map::iterator item(mDirectoryCache.find(ObjectID)); if(item != mDirectoryCache.end()) { // Delete this cached object delete item->second; // Erase the entry form the map mDirectoryCache.erase(item); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::SaveDirectory(BackupStoreDirectory &, int64_t) // Purpose: Save directory back to disc, update time in cache // Created: 2003/09/04 // // -------------------------------------------------------------------------- void BackupStoreContext::SaveDirectory(BackupStoreDirectory &rDir, int64_t ObjectID) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(rDir.GetObjectID() != ObjectID) { THROW_EXCEPTION(BackupStoreException, Internal) } try { // Write to disc, adjust size in store info std::string dirfn; MakeObjectFilename(ObjectID, dirfn); { RaidFileWrite writeDir(mStoreDiscSet, dirfn); writeDir.Open(true /* allow overwriting */); BufferedWriteStream buffer(writeDir); rDir.WriteToStream(buffer); buffer.Flush(); // get the disc usage (must do this before commiting it) int64_t dirSize = writeDir.GetDiscUsageInBlocks(); // Commit directory writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); // Make sure the size of the directory is available for writing the dir back ASSERT(dirSize > 0); int64_t sizeAdjustment = dirSize - rDir.GetUserInfo1_SizeInBlocks(); mpStoreInfo->ChangeBlocksUsed(sizeAdjustment); mpStoreInfo->ChangeBlocksInDirectories(sizeAdjustment); // Update size stored in directory rDir.SetUserInfo1_SizeInBlocks(dirSize); } // Refresh revision ID in cache { int64_t revid = 0; if(!RaidFileRead::FileExists(mStoreDiscSet, dirfn, &revid)) { THROW_EXCEPTION(BackupStoreException, Internal) } rDir.SetRevisionID(revid); } } catch(...) { // Remove it from the cache if anything went wrong RemoveDirectoryFromCache(ObjectID); throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::AddDirectory(int64_t, // const BackupStoreFilename &, bool &) // Purpose: Creates a directory (or just returns the ID of an // existing one). rAlreadyExists set appropraitely. // Created: 2003/09/04 // // -------------------------------------------------------------------------- int64_t BackupStoreContext::AddDirectory(int64_t InDirectory, const BackupStoreFilename &rFilename, const StreamableMemBlock &Attributes, int64_t AttributesModTime, bool &rAlreadyExists) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } // Flags as not already existing rAlreadyExists = false; // Get the directory we want to modify BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory)); // Scan the directory for the name (only looking for directories which already exist) { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) != 0) // Ignore deleted and old directories { if(en->GetName() == rFilename) { // Already exists rAlreadyExists = true; return en->GetObjectID(); } } } // Allocate the next ID int64_t id = AllocateObjectID(); // Create an empty directory with the given attributes on disc std::string fn; MakeObjectFilename(id, fn, true /* make sure the directory it's in exists */); { BackupStoreDirectory emptyDir(id, InDirectory); // add the atttribues emptyDir.SetAttributes(Attributes, AttributesModTime); // Write... RaidFileWrite dirFile(mStoreDiscSet, fn); dirFile.Open(false /* no overwriting */); emptyDir.WriteToStream(dirFile); // Get disc usage, before it's commited int64_t dirSize = dirFile.GetDiscUsageInBlocks(); // Commit the file dirFile.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); // Make sure the size of the directory is added to the usage counts in the info ASSERT(dirSize > 0); mpStoreInfo->ChangeBlocksUsed(dirSize); mpStoreInfo->ChangeBlocksInDirectories(dirSize); // Not added to cache, so don't set the size in the directory } // Then add it into the parent directory try { dir.AddEntry(rFilename, 0 /* modification time */, id, 0 /* blocks used */, BackupStoreDirectory::Entry::Flags_Dir, 0 /* attributes mod time */); SaveDirectory(dir, InDirectory); // Increment reference count on the new directory to one mapRefCount->AddReference(id); } catch(...) { // Back out on adding that directory RaidFileWrite del(mStoreDiscSet, fn); del.Delete(); // Remove this entry from the cache RemoveDirectoryFromCache(InDirectory); // Don't worry about the incremented number in the store info throw; } // Save the store info (may be postponed) SaveStoreInfo(); // tell caller what the ID was return id; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &, bool) // Purpose: Recusively deletes a directory (or undeletes if Undelete = true) // Created: 2003/10/21 // // -------------------------------------------------------------------------- void BackupStoreContext::DeleteDirectory(int64_t ObjectID, bool Undelete) { // Essential checks! if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } // Containing directory int64_t InDirectory = 0; // Count of blocks deleted int64_t blocksDeleted = 0; try { // Get the directory that's to be deleted { // In block, because dir may not be valid after the delete directory call BackupStoreDirectory &dir(GetDirectoryInternal(ObjectID)); // Store the directory it's in for later InDirectory = dir.GetContainerID(); // Depth first delete of contents DeleteDirectoryRecurse(ObjectID, blocksDeleted, Undelete); } // Remove the entry from the directory it's in ASSERT(InDirectory != 0); BackupStoreDirectory &parentDir(GetDirectoryInternal(InDirectory)); BackupStoreDirectory::Iterator i(parentDir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(Undelete?(BackupStoreDirectory::Entry::Flags_Deleted):(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING), Undelete?(0):(BackupStoreDirectory::Entry::Flags_Deleted))) != 0) // Ignore deleted directories (or not deleted if Undelete) { if(en->GetObjectID() == ObjectID) { // This is the one to delete if(Undelete) { en->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted); } else { en->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted); } // Save it SaveDirectory(parentDir, InDirectory); // Done break; } } // Update blocks deleted count mpStoreInfo->ChangeBlocksInDeletedFiles(Undelete?(0 - blocksDeleted):(blocksDeleted)); // Save store info, may be postponed SaveStoreInfo(); } catch(...) { RemoveDirectoryFromCache(InDirectory); throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::DeleteDirectoryRecurse(BackupStoreDirectory &, int64_t) // Purpose: Private. Deletes a directory depth-first recusively. // Created: 2003/10/21 // // -------------------------------------------------------------------------- void BackupStoreContext::DeleteDirectoryRecurse(int64_t ObjectID, int64_t &rBlocksDeletedOut, bool Undelete) { try { // Does things carefully to avoid using a directory in the cache after recursive call // because it may have been deleted. // Do sub directories { // Get the directory... BackupStoreDirectory &dir(GetDirectoryInternal(ObjectID)); // Then scan it for directories std::vector subDirs; BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; if(Undelete) { while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_Deleted, // deleted dirs BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING)) != 0) { // Store the directory ID. subDirs.push_back(en->GetObjectID()); } } else { while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir, // dirs only BackupStoreDirectory::Entry::Flags_Deleted)) != 0) // but not deleted ones { // Store the directory ID. subDirs.push_back(en->GetObjectID()); } } // Done with the directory for now. Recurse to sub directories for(std::vector::const_iterator i = subDirs.begin(); i != subDirs.end(); ++i) { DeleteDirectoryRecurse((*i), rBlocksDeletedOut, Undelete); } } // Then, delete the files. Will need to load the directory again because it might have // been removed from the cache. { // Get the directory... BackupStoreDirectory &dir(GetDirectoryInternal(ObjectID)); // Changes made? bool changesMade = false; // Run through files BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *en = 0; while((en = i.Next(Undelete?(BackupStoreDirectory::Entry::Flags_Deleted):(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING), Undelete?(0):(BackupStoreDirectory::Entry::Flags_Deleted))) != 0) // Ignore deleted directories (or not deleted if Undelete) { // Add/remove the deleted flags if(Undelete) { en->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted); } else { en->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted); } // Keep count of the deleted blocks if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) != 0) { rBlocksDeletedOut += en->GetSizeInBlocks(); } // Did something changesMade = true; } // Save the directory if(changesMade) { SaveDirectory(dir, ObjectID); } } } catch(...) { RemoveDirectoryFromCache(ObjectID); throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::ChangeDirAttributes(int64_t, const StreamableMemBlock &, int64_t) // Purpose: Change the attributes of a directory // Created: 2003/09/06 // // -------------------------------------------------------------------------- void BackupStoreContext::ChangeDirAttributes(int64_t Directory, const StreamableMemBlock &Attributes, int64_t AttributesModTime) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } try { // Get the directory we want to modify BackupStoreDirectory &dir(GetDirectoryInternal(Directory)); // Set attributes dir.SetAttributes(Attributes, AttributesModTime); // Save back SaveDirectory(dir, Directory); } catch(...) { RemoveDirectoryFromCache(Directory); throw; } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::ChangeFileAttributes(int64_t, int64_t, const StreamableMemBlock &, int64_t) // Purpose: Sets the attributes on a directory entry. Returns true if the object existed, false if it didn't. // Created: 2003/09/06 // // -------------------------------------------------------------------------- bool BackupStoreContext::ChangeFileAttributes(const BackupStoreFilename &rFilename, int64_t InDirectory, const StreamableMemBlock &Attributes, int64_t AttributesHash, int64_t &rObjectIDOut) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } try { // Get the directory we want to modify BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory)); // Find the file entry BackupStoreDirectory::Entry *en = 0; // Iterate through current versions of files, only BackupStoreDirectory::Iterator i(dir); while((en = i.Next( BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion) ) != 0) { if(en->GetName() == rFilename) { // Set attributes en->SetAttributes(Attributes, AttributesHash); // Tell caller the object ID rObjectIDOut = en->GetObjectID(); // Done break; } } if(en == 0) { // Didn't find it return false; } // Save back SaveDirectory(dir, InDirectory); } catch(...) { RemoveDirectoryFromCache(InDirectory); throw; } // Changed, everything OK return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::ObjectExists(int64_t) // Purpose: Test to see if an object of this ID exists in the store // Created: 2003/09/03 // // -------------------------------------------------------------------------- bool BackupStoreContext::ObjectExists(int64_t ObjectID, int MustBe) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } // Note that we need to allow object IDs a little bit greater than the last one in the store info, // because the store info may not have got saved in an error condition. Max greater ID is // STORE_INFO_SAVE_DELAY in this case, *2 to be safe. if(ObjectID <= 0 || ObjectID > (mpStoreInfo->GetLastObjectIDUsed() + (STORE_INFO_SAVE_DELAY * 2))) { // Obviously bad object ID return false; } // Test to see if it exists on the disc std::string filename; MakeObjectFilename(ObjectID, filename); if(!RaidFileRead::FileExists(mStoreDiscSet, filename)) { // RaidFile reports no file there return false; } // Do we need to be more specific? if(MustBe != ObjectExists_Anything) { // Open the file std::auto_ptr objectFile(RaidFileRead::Open(mStoreDiscSet, filename)); // Read the first integer u_int32_t magic; if(!objectFile->ReadFullBuffer(&magic, sizeof(magic), 0 /* not interested in how many read if failure */)) { // Failed to get any bytes, must have failed return false; } #ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE if(MustBe == ObjectExists_File && ntohl(magic) == OBJECTMAGIC_FILE_MAGIC_VALUE_V0) { // Old version detected return true; } #endif // Right one? u_int32_t requiredMagic = (MustBe == ObjectExists_File)?OBJECTMAGIC_FILE_MAGIC_VALUE_V1:OBJECTMAGIC_DIR_MAGIC_VALUE; // Check if(ntohl(magic) != requiredMagic) { return false; } // File is implicitly closed } return true; } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::OpenObject(int64_t) // Purpose: Opens an object // Created: 2003/09/03 // // -------------------------------------------------------------------------- std::auto_ptr BackupStoreContext::OpenObject(int64_t ObjectID) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } // Attempt to open the file std::string fn; MakeObjectFilename(ObjectID, fn); return std::auto_ptr(RaidFileRead::Open(mStoreDiscSet, fn).release()); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::GetClientStoreMarker() // Purpose: Retrieve the client store marker // Created: 2003/10/29 // // -------------------------------------------------------------------------- int64_t BackupStoreContext::GetClientStoreMarker() { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } return mpStoreInfo->GetClientStoreMarker(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::GetStoreDiscUsageInfo(int64_t &, int64_t &, int64_t &) // Purpose: Get disc usage info from store info // Created: 1/1/04 // // -------------------------------------------------------------------------- void BackupStoreContext::GetStoreDiscUsageInfo(int64_t &rBlocksUsed, int64_t &rBlocksSoftLimit, int64_t &rBlocksHardLimit) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } rBlocksUsed = mpStoreInfo->GetBlocksUsed(); rBlocksSoftLimit = mpStoreInfo->GetBlocksSoftLimit(); rBlocksHardLimit = mpStoreInfo->GetBlocksHardLimit(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::HardLimitExceeded() // Purpose: Returns true if the hard limit has been exceeded // Created: 1/1/04 // // -------------------------------------------------------------------------- bool BackupStoreContext::HardLimitExceeded() { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } return mpStoreInfo->GetBlocksUsed() > mpStoreInfo->GetBlocksHardLimit(); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::SetClientStoreMarker(int64_t) // Purpose: Sets the client store marker, and commits it to disc // Created: 2003/10/29 // // -------------------------------------------------------------------------- void BackupStoreContext::SetClientStoreMarker(int64_t ClientStoreMarker) { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } mpStoreInfo->SetClientStoreMarker(ClientStoreMarker); SaveStoreInfo(false /* don't delay saving this */); } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::MoveObject(int64_t, int64_t, int64_t, const BackupStoreFilename &, bool) // Purpose: Move an object (and all objects with the same name) from one directory to another // Created: 12/11/03 // // -------------------------------------------------------------------------- void BackupStoreContext::MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int64_t MoveToDirectory, const BackupStoreFilename &rNewFilename, bool MoveAllWithSameName, bool AllowMoveOverDeletedObject) { if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) } // Should deleted files be excluded when checking for the existance of objects with the target name? int64_t targetSearchExcludeFlags = (AllowMoveOverDeletedObject) ?(BackupStoreDirectory::Entry::Flags_Deleted) :(BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); // Special case if the directories are the same... if(MoveFromDirectory == MoveToDirectory) { try { // Get the first directory BackupStoreDirectory &dir(GetDirectoryInternal(MoveFromDirectory)); // Find the file entry BackupStoreDirectory::Entry *en = dir.FindEntryByID(ObjectID); // Error if not found if(en == 0) { THROW_EXCEPTION(BackupStoreException, CouldNotFindEntryInDirectory) } // Check the new name doens't already exist (optionally ignoring deleted files) { BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *c = 0; while((c = i.Next(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, targetSearchExcludeFlags)) != 0) { if(c->GetName() == rNewFilename) { THROW_EXCEPTION(BackupStoreException, NameAlreadyExistsInDirectory) } } } // Need to get all the entries with the same name? if(MoveAllWithSameName) { // Iterate through the directory, copying all with matching names BackupStoreDirectory::Iterator i(dir); BackupStoreDirectory::Entry *c = 0; while((c = i.Next()) != 0) { if(c->GetName() == en->GetName()) { // Rename this one c->SetName(rNewFilename); } } } else { // Just copy this one en->SetName(rNewFilename); } // Save the directory back SaveDirectory(dir, MoveFromDirectory); } catch(...) { RemoveDirectoryFromCache(MoveToDirectory); // either will do, as they're the same throw; } return; } // Got to be careful how this is written, as we can't guarentte that if we have two // directories open, the first won't be deleted as the second is opened. (cache) // List of entries to move std::vector moving; // list of directory IDs which need to have containing dir id changed std::vector dirsToChangeContainingID; try { // First of all, get copies of the entries to move to the to directory. { // Get the first directory BackupStoreDirectory &from(GetDirectoryInternal(MoveFromDirectory)); // Find the file entry BackupStoreDirectory::Entry *en = from.FindEntryByID(ObjectID); // Error if not found if(en == 0) { THROW_EXCEPTION(BackupStoreException, CouldNotFindEntryInDirectory) } // Need to get all the entries with the same name? if(MoveAllWithSameName) { // Iterate through the directory, copying all with matching names BackupStoreDirectory::Iterator i(from); BackupStoreDirectory::Entry *c = 0; while((c = i.Next()) != 0) { if(c->GetName() == en->GetName()) { // Copy moving.push_back(new BackupStoreDirectory::Entry(*c)); // Check for containing directory correction if(c->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) dirsToChangeContainingID.push_back(c->GetObjectID()); } } ASSERT(!moving.empty()); } else { // Just copy this one moving.push_back(new BackupStoreDirectory::Entry(*en)); // Check for containing directory correction if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) dirsToChangeContainingID.push_back(en->GetObjectID()); } } // Secondly, insert them into the to directory, and save it { // To directory BackupStoreDirectory &to(GetDirectoryInternal(MoveToDirectory)); // Check the new name doens't already exist { BackupStoreDirectory::Iterator i(to); BackupStoreDirectory::Entry *c = 0; while((c = i.Next(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, targetSearchExcludeFlags)) != 0) { if(c->GetName() == rNewFilename) { THROW_EXCEPTION(BackupStoreException, NameAlreadyExistsInDirectory) } } } // Copy the entries into it, changing the name as we go for(std::vector::iterator i(moving.begin()); i != moving.end(); ++i) { BackupStoreDirectory::Entry *en = (*i); en->SetName(rNewFilename); to.AddEntry(*en); // adds copy } // Save back SaveDirectory(to, MoveToDirectory); } // Thirdly... remove them from the first directory -- but if it fails, attempt to delete them from the to directory try { // Get directory BackupStoreDirectory &from(GetDirectoryInternal(MoveFromDirectory)); // Delete each one for(std::vector::iterator i(moving.begin()); i != moving.end(); ++i) { from.DeleteEntry((*i)->GetObjectID()); } // Save back SaveDirectory(from, MoveFromDirectory); } catch(...) { // UNDO modification to To directory // Get directory BackupStoreDirectory &to(GetDirectoryInternal(MoveToDirectory)); // Delete each one for(std::vector::iterator i(moving.begin()); i != moving.end(); ++i) { to.DeleteEntry((*i)->GetObjectID()); } // Save back SaveDirectory(to, MoveToDirectory); // Throw the error throw; } // Finally... for all the directories we moved, modify their containing directory ID for(std::vector::iterator i(dirsToChangeContainingID.begin()); i != dirsToChangeContainingID.end(); ++i) { // Load the directory BackupStoreDirectory &change(GetDirectoryInternal(*i)); // Modify containing dir ID change.SetContainerID(MoveToDirectory); // Save it back SaveDirectory(change, *i); } } catch(...) { // Make sure directories aren't in the cache, as they may have been modified RemoveDirectoryFromCache(MoveToDirectory); RemoveDirectoryFromCache(MoveFromDirectory); for(std::vector::iterator i(dirsToChangeContainingID.begin()); i != dirsToChangeContainingID.end(); ++i) { RemoveDirectoryFromCache(*i); } while(!moving.empty()) { delete moving.back(); moving.pop_back(); } throw; } // Clean up while(!moving.empty()) { delete moving.back(); moving.pop_back(); } } // -------------------------------------------------------------------------- // // Function // Name: BackupStoreContext::GetBackupStoreInfo() // Purpose: Return the backup store info object, exception if it isn't loaded // Created: 19/4/04 // // -------------------------------------------------------------------------- const BackupStoreInfo &BackupStoreContext::GetBackupStoreInfo() const { if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } return *(mpStoreInfo.get()); } boxbackup/bin/bbstored/bbstored.cpp0000664000175000017500000000137711017267347020216 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: bbstored.cpp // Purpose: main file for backup store daemon // Created: 2003/08/20 // // -------------------------------------------------------------------------- #include "Box.h" #include "BackupStoreDaemon.h" #include "MainHelper.h" #include "Logging.h" #include "MemLeakFindOn.h" int main(int argc, const char *argv[]) { MAINHELPER_START Logging::SetProgramName("bbstored"); Logging::ToConsole(true); Logging::ToSyslog (true); BackupStoreDaemon daemon; #ifdef WIN32 return daemon.Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE, argc, argv); #else return daemon.Main(BOX_FILE_BBSTORED_DEFAULT_CONFIG, argc, argv); #endif MAINHELPER_END } boxbackup/bin/bbstored/BackupStoreDaemon.h0000664000175000017500000000462311221742206021407 0ustar siretartsiretart// -------------------------------------------------------------------------- // // File // Name: BackupStoreDaemon.h // Purpose: Backup store daemon // Created: 2003/08/20 // // -------------------------------------------------------------------------- #ifndef BACKUPSTOREDAEMON__H #define BACKUPSTOREDAEMON__H #include "ServerTLS.h" #include "BoxPortsAndFiles.h" #include "BackupConstants.h" #include "BackupStoreContext.h" #include "HousekeepStoreAccount.h" #include "IOStreamGetLine.h" class BackupStoreAccounts; class BackupStoreAccountDatabase; // -------------------------------------------------------------------------- // // Class // Name: BackupStoreDaemon // Purpose: Backup store daemon implementation // Created: 2003/08/20 // // -------------------------------------------------------------------------- class BackupStoreDaemon : public ServerTLS, HousekeepingInterface, HousekeepingCallback { public: BackupStoreDaemon(); ~BackupStoreDaemon(); private: BackupStoreDaemon(const BackupStoreDaemon &rToCopy); public: // For BackupStoreContext to communicate with housekeeping process void SendMessageToHousekeepingProcess(const void *Msg, int MsgLen) { #ifndef WIN32 mInterProcessCommsSocket.Write(Msg, MsgLen); #endif } protected: virtual void SetupInInitialProcess(); virtual void Run(); virtual void Connection(SocketStreamTLS &rStream); void Connection2(SocketStreamTLS &rStream); virtual const char *DaemonName() const; virtual std::string DaemonBanner() const; const ConfigurationVerify *GetConfigVerify() const; // Housekeeping functions void HousekeepingProcess(); void LogConnectionStats(const char *commonName, const SocketStreamTLS &s); public: // HousekeepingInterface implementation virtual bool CheckForInterProcessMsg(int AccountNum = 0, int MaximumWaitTime = 0); private: BackupStoreAccountDatabase *mpAccountDatabase; BackupStoreAccounts *mpAccounts; bool mExtendedLogging; bool mHaveForkedHousekeeping; bool mIsHousekeepingProcess; bool mHousekeepingInited; SocketStream mInterProcessCommsSocket; IOStreamGetLine mInterProcessComms; virtual void OnIdle(); void HousekeepingInit(); void RunHousekeepingIfNeeded(); int64_t mLastHousekeepingRun; public: void SetTestHook(BackupStoreContext::TestHook& rTestHook) { mpTestHook = &rTestHook; } private: BackupStoreContext::TestHook* mpTestHook; }; #endif // BACKUPSTOREDAEMON__H boxbackup/.svnrevision0000664000175000017500000000000511652362375015705 0ustar siretartsiretart2837 boxbackup/parcels.txt0000664000175000017500000000370111446210200015473 0ustar siretartsiretart # this file describes which binaries and scripts go to make # up a parcel -- a group of files and scripts needed to perform # a particular task backup-client bin bbackupd bin bbackupquery bin bbackupctl script bin/bbackupd/bbackupd-config noinstall script COPYING.txt noinstall script LICENSE-GPL.txt noinstall script LICENSE-DUAL.txt html bbackupd html bbackupquery html bbackupctl html bbackupd-config html bbackupd.conf EXCEPT:mingw32,mingw32msvc man bbackupd.8 man bbackupquery.8 man bbackupctl.8 man bbackupd-config.8 man bbackupd.conf.5 END-EXCEPT ONLY:mingw32,mingw32msvc script bin/bbackupd/win32/installer.iss script bin/bbackupd/win32/bbackupd.conf script bin/bbackupd/win32/NotifySysAdmin.vbs END-ONLY ONLY:mingw32 script /bin/mgwz.dll script /bin/mingwm10.dll script /usr/i686-pc-mingw32/bin/cygpcreposix-0.dll script /usr/i686-pc-mingw32/bin/cygpcre-0.dll END-ONLY ONLY:SunOS script contrib/solaris/bbackupd-manifest.xml lib/svc/manifest script contrib/solaris/bbackupd-smf-method lib/svc/method END-ONLY ONLY:Darwin script contrib/mac_osx/org.boxbackup.bbackupd.plist /Library/LaunchDaemons END-ONLY backup-server bin bbstored bin bbstoreaccounts script bin/bbstored/bbstored-certs script bin/bbstored/bbstored-config script lib/raidfile/raidfile-config noinstall script COPYING.txt noinstall script LICENSE-GPL.txt noinstall script LICENSE-DUAL.txt html bbstored html bbstoreaccounts html bbstored-certs html bbstored-config html raidfile-config html bbstored.conf html raidfile.conf EXCEPT:mingw32,mingw32msvc man bbstored.8 man bbstoreaccounts.8 man bbstored-certs.8 man bbstored-config.8 man raidfile-config.8 man bbstored.conf.5 man raidfile.conf.5 END-EXCEPT ONLY:SunOS script contrib/solaris/bbstored-manifest.xml lib/svc/manifest script contrib/solaris/bbstored-smf-method lib/svc/method END-ONLY ONLY:Darwin script contrib/mac_osx/org.boxbackup.bbstored.plist /Library/LaunchDaemons END-ONLY boxbackup/.hgignore0000664000175000017500000000075110773274120015123 0ustar siretartsiretart.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 boxbackup/docs/0000775000175000017500000000000011652362373014252 5ustar siretartsiretartboxbackup/docs/xsl-generic/0000775000175000017500000000000011652362373016472 5ustar siretartsiretartboxbackup/docs/xsl-generic/highlighting/0000775000175000017500000000000011652362373021137 5ustar siretartsiretartboxbackup/docs/xsl-generic/highlighting/xslthl-config.xml0000664000175000017500000000075011175136613024440 0ustar siretartsiretart boxbackup/docs/xsl-generic/highlighting/myxml-hl.xml0000664000175000017500000000720711175136613023432 0ustar siretartsiretart A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR XMP xsl: boxbackup/docs/xsl-generic/highlighting/delphi-hl.xml0000664000175000017500000001104311175136613023522 0ustar siretartsiretart { } (* *) // ' and else inherited packed then array end initialization procedure threadvar as except inline program to asm exports interface property try begin file is raise type case final label record unit class finalization library repeat unsafe const finally mod resourcestring until constructor for nil sealed uses destructor function not set var dispinterface goto object shl while div if of shr with do implementation or static xor downto in out string at on absolute dynamic local platform requires abstract export message private resident assembler external name protected safecall automated far near public stdcall cdecl forward nodefault published stored contains implements overload read varargs default index override readonly virtual deprecated inline package register write dispid library pascal reintroduce writeonly boxbackup/docs/xsl-generic/highlighting/php-hl.xml0000664000175000017500000000644411175136613023055 0ustar siretartsiretart /* */ // # " \ ' \ <<< and or xor __FILE__ exception __LINE__ array as break case class const continue declare default die do echo else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval exit extends for foreach function global if include include_once isset list new print require require_once return static switch unset use var while __FUNCTION__ __CLASS__ __METHOD__ final php_user_filter interface implements extends public private protected abstract clone try catch throw cfunction old_function boxbackup/docs/xsl-generic/highlighting/common.xsl0000664000175000017500000000411111175136613023150 0ustar siretartsiretart boxbackup/docs/xsl-generic/highlighting/m2-hl.xml0000664000175000017500000000435211175136613022600 0ustar siretartsiretart (* *) " ' and array begin by case const definition div do else elsif end exit export for from if implementation import in loop mod module not of or pointer procedure qualified record repeat return set then to type until var while with boxbackup/docs/xsl-generic/highlighting/java-hl.xml0000664000175000017500000000510211175136613023175 0ustar siretartsiretart /* */ // " \ ' \ abstract boolean break byte case catch char class const continue default do double else extends final finally float for goto if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while boxbackup/docs/xsl-generic/highlighting/c-hl.xml0000664000175000017500000000521411175136613022502 0ustar siretartsiretart /* */ // # " \ ' \ <<< and auto break case char class __CLASS__ const continue declare default do double else enum exit extern __FILE__ float for global goto if include int __LINE__ long new or private protected public register return short signed sizeof static struct switch typedef union unsigned void volatile while boxbackup/docs/xsl-generic/highlighting/ini-hl.xml0000664000175000017500000000235511175136613023042 0ustar siretartsiretart (?m)(;.*)$ (?m)^(\[.+\]\s*)$ (?m)^(.+=) boxbackup/docs/xsl-generic/lib/0000775000175000017500000000000011652362373017240 5ustar siretartsiretartboxbackup/docs/xsl-generic/lib/lib.xsl0000664000175000017500000004410411175136613020535 0ustar siretartsiretart http://... Unrecognized unit of measure: . Unrecognized unit of measure: . filename 1 / / boxbackup/docs/xsl-generic/html/0000775000175000017500000000000011652362373017436 5ustar siretartsiretartboxbackup/docs/xsl-generic/html/param.xsl0000664000175000017500000004414111175136613021266 0ustar siretartsiretart .png images/ margin-left: 0.5in; margin-right: 0.5in; /* ====================================================================== Annotations */ div.annotation-list { visibility: hidden; } div.annotation-nocss { position: absolute; visibility: hidden; } div.annotation-popup { position: absolute; z-index: 4; visibility: hidden; padding: 0px; margin: 2px; border-style: solid; border-width: 1px; width: 200px; background-color: white; } div.annotation-title { padding: 1px; font-weight: bold; border-bottom-style: solid; border-bottom-width: 1px; color: white; background-color: black; } div.annotation-body { padding: 2px; } div.annotation-body p { margin-top: 0px; padding-top: 0px; } div.annotation-close { position: absolute; top: 2px; right: 2px; } http://docbook.sourceforge.net/release/script/AnchorPosition.js http://docbook.sourceforge.net/release/script/PopupWindow.js http://docbook.sourceforge.net/release/images/annot-open.png http://docbook.sourceforge.net/release/images/annot-close.png A . . http://docbook.sourceforge.net/release/bibliography/bibliography.xml normal 60 .png 15 images/callouts/ 10 10102 no 1 left before all maybe http://docbook.sourceforge.net/release/images/draft.png #F5DCB3 ::= DocBook Online Help Sample com.example.help Example provider 1 1 figure before example before equation before table before procedure before task before kr 40 appendix toc,title article/appendix nop article toc,title book toc,title,figure,table,example,equation chapter toc,title part toc,title preface toc,title qandadiv toc qandaset toc reference toc,title sect1 toc sect2 toc sect3 toc sect4 toc sect5 toc section toc set toc,title no .html copyright text/css alias.h User1 User2 htmlhelp.chm iso-8859-1 toc.hhc 5 index.hhk htmlhelp.hhp Main context.h basic no iso-8859-1 en 5 3 HTML.manifest + .gif images/ 1 6in replace no no fragid= .olink pubid /cgi-bin/olink sysid 0 I 90 10 ; . number I index . .!?: 8 0 #E0E0E0 0 solid 0.5pt a solid 0.5pt olinkdb.xml target.db tex-math-equations.tex dl 2 8 _top , 0 : boxbackup/docs/xsl-generic/html/chunktoc.xsl0000664000175000017500000003720711175136613022011 0ustar siretartsiretart 1 0 The chunk.toc file is not set. ID ' ' not found in document. -toc

-toc -toc
boxbackup/docs/xsl-generic/html/titlepage.templates.xsl0000664000175000017500000050727111175136613024151 0ustar siretartsiretart
1
1

1
1

1
1
1
1
1
1

1
1
1
1
1
1
1
1
1
1
1
1

1
1

1
1

1
1

1
1

1
1

1
1

1
1
1
1
1
1
1
1
1
1
boxbackup/docs/xsl-generic/html/chunkfast.xsl0000664000175000017500000000536311175136613022157 0ustar siretartsiretart boxbackup/docs/xsl-generic/html/chunk.xsl0000664000175000017500000000450211175136613021273 0ustar siretartsiretart boxbackup/docs/xsl-generic/html/biblio-iso690.xsl0000664000175000017500000015627111175136613022465 0ustar siretartsiretart In boxbackup/docs/xsl-generic/html/autoidx-kosek.xsl0000664000175000017500000001165011175136613022754 0ustar siretartsiretart ]> ERROR: the 'kosek' index method does not work with the xsltproc XSLT processor. ERROR: the 'kosek' index method does not work with the Saxon 8 XSLT processor. ERROR: the 'kosek' index method requires the exslt:node-set() function. Use a processor that has it, or use a different index method. ERROR: the 'kosek' index method requires the index extension functions be imported: xsl:import href="common/autoidx-kosek.xsl"

boxbackup/docs/xsl-generic/html/manifest.xsl0000664000175000017500000000175411175136613021777 0ustar siretartsiretart boxbackup/docs/xsl-generic/html/autoidx-kimber.xsl0000664000175000017500000001455211175136613023115 0ustar siretartsiretart ]> ERROR: the 'kimber' index method requires the Saxon version 6 or 8 XSLT processor. ERROR: the 'kimber' index method requires the Innodata Isogen Java extensions for internationalized indexes. Install those extensions, or use a different index method. For more information, see: http://www.innodata-isogen.com/knowledge_center/tools_downloads/i18nsupport

boxbackup/docs/xsl-generic/html/titlepage.templates.xml0000664000175000017500000003512511175136613024135 0ustar siretartsiretart <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <hr/> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="set" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <hr/> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="book" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <hr/> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="part" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title t:force="1" t:named-template="division.title" param:node="ancestor-or-self::part[1]"/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <t:titlepage t:element="partintro" t:wrapper="div"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="reference" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <hr/> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="refentry" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <!-- uncomment this if you want refentry titlepages <title t:force="1" t:named-template="refentry.title" param:node="ancestor-or-self::refentry[1]"/> --> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator/> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="dedication" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title t:force="1" t:named-template="component.title" param:node="ancestor-or-self::dedication[1]"/> <subtitle/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="preface" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="chapter" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="appendix" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="section" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <t:titlepage t:element="sect1" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <t:titlepage t:element="sect2" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <t:titlepage t:element="sect3" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <t:titlepage t:element="sect4" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <t:titlepage t:element="sect5" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <t:titlepage t:element="simplesect" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title/> <subtitle/> <corpauthor/> <authorgroup/> <author/> <othercredit/> <releaseinfo/> <copyright/> <legalnotice/> <pubdate/> <revision/> <revhistory/> <abstract/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="bibliography" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title t:force="1" t:named-template="component.title" param:node="ancestor-or-self::bibliography[1]"/> <subtitle/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="glossary" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title t:force="1" t:named-template="component.title" param:node="ancestor-or-self::glossary[1]"/> <subtitle/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="index" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title t:force="1" t:named-template="component.title" param:node="ancestor-or-self::index[1]"/> <subtitle/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> <t:titlepage t:element="setindex" t:wrapper="div" class="titlepage"> <t:titlepage-content t:side="recto"> <title t:force="1" t:named-template="component.title" param:node="ancestor-or-self::setindex[1]"/> <subtitle/> </t:titlepage-content> <t:titlepage-content t:side="verso"> </t:titlepage-content> <t:titlepage-separator> </t:titlepage-separator> <t:titlepage-before t:side="recto"> </t:titlepage-before> <t:titlepage-before t:side="verso"> </t:titlepage-before> </t:titlepage> <!-- ==================================================================== --> </t:templates> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/component.xsl�������������������������������������������������������0000664�0001750�0001750�00000033704�11175136613�022173� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> <!-- ******************************************************************** $Id: component.xsl 7000 2007-07-10 20:41:35Z mzjn $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:template name="component.title"> <xsl:param name="node" select="."/> <xsl:variable name="level"> <xsl:choose> <xsl:when test="ancestor::section"> <xsl:value-of select="count(ancestor::section)+1"/> </xsl:when> <xsl:when test="ancestor::sect5">6</xsl:when> <xsl:when test="ancestor::sect4">5</xsl:when> <xsl:when test="ancestor::sect3">4</xsl:when> <xsl:when test="ancestor::sect2">3</xsl:when> <xsl:when test="ancestor::sect1">2</xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> </xsl:variable> <!-- Let's handle the case where a component (bibliography, for example) occurs inside a section; will we need parameters for this? --> <xsl:element name="h{$level+1}"> <xsl:attribute name="class">title</xsl:attribute> <xsl:if test="$generate.id.attributes = 0"> <xsl:call-template name="anchor"> <xsl:with-param name="node" select="$node"/> <xsl:with-param name="conditional" select="0"/> </xsl:call-template> </xsl:if> <xsl:apply-templates select="$node" mode="object.title.markup"> <xsl:with-param name="allow-anchors" select="1"/> </xsl:apply-templates> </xsl:element> </xsl:template> <xsl:template name="component.subtitle"> <xsl:param name="node" select="."/> <xsl:variable name="subtitle" select="($node/docinfo/subtitle |$node/info/subtitle |$node/prefaceinfo/subtitle |$node/chapterinfo/subtitle |$node/appendixinfo/subtitle |$node/articleinfo/subtitle |$node/artheader/subtitle |$node/subtitle)[1]"/> <xsl:if test="$subtitle"> <h3 class="subtitle"> <i> <xsl:apply-templates select="$node" mode="object.subtitle.markup"/> </i> </h3> </xsl:if> </xsl:template> <xsl:template name="component.separator"> </xsl:template> <!-- ==================================================================== --> <xsl:template match="dedication" mode="dedication"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:call-template name="dedication.titlepage"/> <xsl:apply-templates/> <xsl:call-template name="process.footnotes"/> </div> </xsl:template> <xsl:template match="dedication/title|dedication/info/title" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.title"> <xsl:with-param name="node" select="ancestor::dedication[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="dedication/subtitle|dedication/info/subtitle" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.subtitle"> <xsl:with-param name="node" select="ancestor::dedication[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="dedication"></xsl:template> <!-- see mode="dedication" --> <xsl:template match="dedication/title"></xsl:template> <xsl:template match="dedication/subtitle"></xsl:template> <xsl:template match="dedication/titleabbrev"></xsl:template> <!-- ==================================================================== --> <xsl:template match="colophon"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="component.separator"/> <xsl:call-template name="component.title"/> <xsl:call-template name="component.subtitle"/> <xsl:apply-templates/> <xsl:call-template name="process.footnotes"/> </div> </xsl:template> <xsl:template match="colophon/title"></xsl:template> <xsl:template match="colophon/subtitle"></xsl:template> <xsl:template match="colophon/titleabbrev"></xsl:template> <!-- ==================================================================== --> <xsl:template match="preface"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="component.separator"/> <xsl:call-template name="preface.titlepage"/> <xsl:variable name="toc.params"> <xsl:call-template name="find.path.params"> <xsl:with-param name="table" select="normalize-space($generate.toc)"/> </xsl:call-template> </xsl:variable> <xsl:if test="contains($toc.params, 'toc')"> <xsl:call-template name="component.toc"> <xsl:with-param name="toc.title.p" select="contains($toc.params, 'title')"/> </xsl:call-template> <xsl:call-template name="component.toc.separator"/> </xsl:if> <xsl:apply-templates/> <xsl:call-template name="process.footnotes"/> </div> </xsl:template> <xsl:template match="preface/title" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.title"> <xsl:with-param name="node" select="ancestor::preface[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="preface/subtitle |preface/prefaceinfo/subtitle |preface/info/subtitle |preface/docinfo/subtitle" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.subtitle"> <xsl:with-param name="node" select="ancestor::preface[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="preface/docinfo|prefaceinfo"></xsl:template> <xsl:template match="preface/info"></xsl:template> <xsl:template match="preface/title"></xsl:template> <xsl:template match="preface/titleabbrev"></xsl:template> <xsl:template match="preface/subtitle"></xsl:template> <!-- ==================================================================== --> <xsl:template match="chapter"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="component.separator"/> <xsl:call-template name="chapter.titlepage"/> <xsl:variable name="toc.params"> <xsl:call-template name="find.path.params"> <xsl:with-param name="table" select="normalize-space($generate.toc)"/> </xsl:call-template> </xsl:variable> <xsl:if test="contains($toc.params, 'toc')"> <xsl:call-template name="component.toc"> <xsl:with-param name="toc.title.p" select="contains($toc.params, 'title')"/> </xsl:call-template> <xsl:call-template name="component.toc.separator"/> </xsl:if> <xsl:apply-templates/> <xsl:call-template name="process.footnotes"/> </div> </xsl:template> <xsl:template match="chapter/title" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.title"> <xsl:with-param name="node" select="ancestor::chapter[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="chapter/subtitle |chapter/chapterinfo/subtitle |chapter/info/subtitle |chapter/docinfo/subtitle" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.subtitle"> <xsl:with-param name="node" select="ancestor::chapter[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="chapter/docinfo|chapterinfo"></xsl:template> <xsl:template match="chapter/info"></xsl:template> <xsl:template match="chapter/title"></xsl:template> <xsl:template match="chapter/titleabbrev"></xsl:template> <xsl:template match="chapter/subtitle"></xsl:template> <!-- ==================================================================== --> <xsl:template match="appendix"> <xsl:variable name="ischunk"> <xsl:call-template name="chunk"/> </xsl:variable> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:choose> <xsl:when test="parent::article and $ischunk = 0"> <xsl:call-template name="section.heading"> <xsl:with-param name="level" select="1"/> <xsl:with-param name="title"> <xsl:apply-templates select="." mode="object.title.markup"/> </xsl:with-param> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="component.separator"/> <xsl:call-template name="appendix.titlepage"/> </xsl:otherwise> </xsl:choose> <xsl:variable name="toc.params"> <xsl:call-template name="find.path.params"> <xsl:with-param name="table" select="normalize-space($generate.toc)"/> </xsl:call-template> </xsl:variable> <xsl:if test="contains($toc.params, 'toc')"> <xsl:call-template name="component.toc"> <xsl:with-param name="toc.title.p" select="contains($toc.params, 'title')"/> </xsl:call-template> <xsl:call-template name="component.toc.separator"/> </xsl:if> <xsl:apply-templates/> <xsl:if test="not(parent::article) or $ischunk != 0"> <xsl:call-template name="process.footnotes"/> </xsl:if> </div> </xsl:template> <xsl:template match="appendix/title" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.title"> <xsl:with-param name="node" select="ancestor::appendix[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="appendix/subtitle |appendix/appendixinfo/subtitle |appendix/info/subtitle |appendix/docinfo/subtitle" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.subtitle"> <xsl:with-param name="node" select="ancestor::appendix[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="appendix/docinfo|appendixinfo"></xsl:template> <xsl:template match="appendix/info"></xsl:template> <xsl:template match="appendix/title"></xsl:template> <xsl:template match="appendix/titleabbrev"></xsl:template> <xsl:template match="appendix/subtitle"></xsl:template> <!-- ==================================================================== --> <xsl:template match="article"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="article.titlepage"/> <xsl:variable name="toc.params"> <xsl:call-template name="find.path.params"> <xsl:with-param name="table" select="normalize-space($generate.toc)"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="make.lots"> <xsl:with-param name="toc.params" select="$toc.params"/> <xsl:with-param name="toc"> <xsl:call-template name="component.toc"> <xsl:with-param name="toc.title.p" select="contains($toc.params, 'title')"/> </xsl:call-template> </xsl:with-param> </xsl:call-template> <xsl:apply-templates/> <xsl:call-template name="process.footnotes"/> </div> </xsl:template> <xsl:template match="article/title|article/articleinfo/title" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.title"> <xsl:with-param name="node" select="ancestor::article[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="article/subtitle |article/articleinfo/subtitle |article/info/subtitle |article/artheader/subtitle" mode="titlepage.mode" priority="2"> <xsl:call-template name="component.subtitle"> <xsl:with-param name="node" select="ancestor::article[1]"/> </xsl:call-template> </xsl:template> <xsl:template match="article/artheader|article/articleinfo"></xsl:template> <xsl:template match="article/info"></xsl:template> <xsl:template match="article/title"></xsl:template> <xsl:template match="article/titleabbrev"></xsl:template> <xsl:template match="article/subtitle"></xsl:template> <!-- ==================================================================== --> </xsl:stylesheet> ������������������������������������������������������������boxbackup/docs/xsl-generic/html/toc.xsl�������������������������������������������������������������0000664�0001750�0001750�00000012707�11175136613�020756� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> <!-- ******************************************************************** $Id: toc.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:template match="toc"> <xsl:choose> <xsl:when test="*"> <xsl:if test="$process.source.toc != 0"> <!-- if the toc isn't empty, process it --> <xsl:element name="{$toc.list.type}"> <xsl:apply-templates/> </xsl:element> </xsl:if> </xsl:when> <xsl:otherwise> <xsl:if test="$process.empty.source.toc != 0"> <xsl:choose> <xsl:when test="parent::section or parent::sect1 or parent::sect2 or parent::sect3 or parent::sect4 or parent::sect5"> <xsl:apply-templates select="parent::*" mode="toc.for.section"/> </xsl:when> <xsl:when test="parent::article"> <xsl:apply-templates select="parent::*" mode="toc.for.component"/> </xsl:when> <xsl:when test="parent::book or parent::part"> <xsl:apply-templates select="parent::*" mode="toc.for.division"/> </xsl:when> <xsl:when test="parent::set"> <xsl:apply-templates select="parent::*" mode="toc.for.set"/> </xsl:when> <!-- there aren't any other contexts that allow toc --> <xsl:otherwise> <xsl:message> <xsl:text>I don't know how to make a TOC in this context!</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="tocpart|tocchap |toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"> <xsl:variable name="sub-toc"> <xsl:if test="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"> <xsl:choose> <xsl:when test="$toc.list.type = 'dl'"> <dd> <xsl:element name="{$toc.list.type}"> <xsl:apply-templates select="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"/> </xsl:element> </dd> </xsl:when> <xsl:otherwise> <xsl:element name="{$toc.list.type}"> <xsl:apply-templates select="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"/> </xsl:element> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:variable> <xsl:apply-templates select="tocentry[position() != last()]"/> <xsl:choose> <xsl:when test="$toc.list.type = 'dl'"> <dt> <xsl:apply-templates select="tocentry[position() = last()]"/> </dt> <xsl:copy-of select="$sub-toc"/> </xsl:when> <xsl:otherwise> <li> <xsl:apply-templates select="tocentry[position() = last()]"/> <xsl:copy-of select="$sub-toc"/> </li> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="tocentry|tocfront|tocback"> <xsl:choose> <xsl:when test="$toc.list.type = 'dl'"> <dt> <xsl:call-template name="tocentry-content"/> </dt> </xsl:when> <xsl:otherwise> <li> <xsl:call-template name="tocentry-content"/> </li> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="tocentry[position() = last()]" priority="2"> <xsl:call-template name="tocentry-content"/> </xsl:template> <xsl:template name="tocentry-content"> <xsl:variable name="targets" select="key('id',@linkend)"/> <xsl:variable name="target" select="$targets[1]"/> <xsl:choose> <xsl:when test="@linkend"> <xsl:call-template name="check.id.unique"> <xsl:with-param name="linkend" select="@linkend"/> </xsl:call-template> <a> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target"/> </xsl:call-template> </xsl:attribute> <xsl:apply-templates/> </a> </xsl:when> <xsl:otherwise> <xsl:apply-templates/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- ==================================================================== --> <xsl:template match="*" mode="toc.for.section"> <xsl:call-template name="section.toc"/> </xsl:template> <xsl:template match="*" mode="toc.for.component"> <xsl:call-template name="component.toc"/> </xsl:template> <xsl:template match="*" mode="toc.for.section"> <xsl:call-template name="section.toc"/> </xsl:template> <xsl:template match="*" mode="toc.for.division"> <xsl:call-template name="division.toc"/> </xsl:template> <xsl:template match="*" mode="toc.for.set"> <xsl:call-template name="set.toc"/> </xsl:template> <!-- ==================================================================== --> <xsl:template match="lot|lotentry"> </xsl:template> </xsl:stylesheet> ���������������������������������������������������������boxbackup/docs/xsl-generic/html/inline.xsl����������������������������������������������������������0000664�0001750�0001750�00000131335�11175136613�021446� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <!DOCTYPE xsl:stylesheet [ <!ENTITY comment.block.parents "parent::answer|parent::appendix|parent::article|parent::bibliodiv|parent::bibliography|parent::blockquote|parent::caution|parent::chapter|parent::glossary|parent::glossdiv|parent::important|parent::index|parent::indexdiv|parent::listitem|parent::note|parent::orderedlist|parent::partintro|parent::preface|parent::procedure|parent::qandadiv|parent::qandaset|parent::question|parent::refentry|parent::refnamediv|parent::refsect1|parent::refsect2|parent::refsect3|parent::refsection|parent::refsynopsisdiv|parent::sect1|parent::sect2|parent::sect3|parent::sect4|parent::sect5|parent::section|parent::setindex|parent::sidebar|parent::simplesect|parent::taskprerequisites|parent::taskrelated|parent::tasksummary|parent::warning"> ]> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:suwl="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.UnwrapLinks" exclude-result-prefixes="xlink suwl" version='1.0'> <!-- ******************************************************************** $Id: inline.xsl 7232 2007-08-11 16:10:40Z mzjn $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <xsl:template name="simple.xlink"> <xsl:param name="node" select="."/> <xsl:param name="content"> <xsl:apply-templates/> </xsl:param> <xsl:param name="a.target"/> <xsl:param name="linkend" select="$node/@linkend"/> <xsl:param name="xhref" select="$node/@xlink:href"/> <xsl:variable name="link"> <xsl:choose> <xsl:when test="$xhref and (not($node/@xlink:type) or $node/@xlink:type='simple')"> <!-- Is it a local idref or a uri? --> <xsl:variable name="is.idref"> <xsl:choose> <!-- if the href starts with # and does not contain an "(" --> <!-- or if the href starts with #xpointer(id(, it's just an ID --> <xsl:when test="starts-with($xhref,'#') and (not(contains($xhref,'(')) or starts-with($xhref, '#xpointer(id('))">1</xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:variable> <!-- Is it an olink ? --> <xsl:variable name="is.olink"> <xsl:choose> <!-- If xlink:role="http://docbook.org/xlink/role/olink" --> <!-- and if the href contains # --> <xsl:when test="contains($xhref,'#') and @xlink:role = $xolink.role">1</xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <xsl:when test="$is.idref = 1"> <xsl:variable name="idref"> <xsl:call-template name="xpointer.idref"> <xsl:with-param name="xpointer" select="$xhref"/> </xsl:call-template> </xsl:variable> <xsl:variable name="targets" select="key('id',$idref)"/> <xsl:variable name="target" select="$targets[1]"/> <xsl:call-template name="check.id.unique"> <xsl:with-param name="linkend" select="$idref"/> </xsl:call-template> <xsl:choose> <xsl:when test="count($target) = 0"> <xsl:message> <xsl:text>XLink to nonexistent id: </xsl:text> <xsl:value-of select="$idref"/> </xsl:message> <xsl:copy-of select="$content"/> </xsl:when> <xsl:otherwise> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target"/> </xsl:call-template> </xsl:attribute> <xsl:choose> <xsl:when test="$node/@xlink:title"> <xsl:attribute name="title"> <xsl:value-of select="$node/@xlink:title"/> </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="$target" mode="html.title.attribute"/> </xsl:otherwise> </xsl:choose> <xsl:if test="$a.target"> <xsl:attribute name="target"> <xsl:value-of select="$a.target"/> </xsl:attribute> </xsl:if> <xsl:copy-of select="$content"/> </a> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="$is.olink = 1"> <xsl:call-template name="olink"> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </xsl:when> <!-- otherwise it's a URI --> <xsl:otherwise> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:value-of select="$xhref"/> </xsl:attribute> <xsl:if test="$node/@xlink:title"> <xsl:attribute name="title"> <xsl:value-of select="$node/@xlink:title"/> </xsl:attribute> </xsl:if> <xsl:copy-of select="$content"/> </a> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="$linkend"> <xsl:variable name="targets" select="key('id',$linkend)"/> <xsl:variable name="target" select="$targets[1]"/> <xsl:call-template name="check.id.unique"> <xsl:with-param name="linkend" select="$linkend"/> </xsl:call-template> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target"/> </xsl:call-template> </xsl:attribute> <xsl:apply-templates select="$target" mode="html.title.attribute"/> <xsl:copy-of select="$content"/> </a> </xsl:when> <xsl:otherwise> <xsl:copy-of select="$content"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <xsl:when test="function-available('suwl:unwrapLinks')"> <xsl:copy-of select="suwl:unwrapLinks($link)"/> </xsl:when> <xsl:otherwise> <xsl:copy-of select="$link"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="inline.charseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <!-- * if you want output from the inline.charseq template wrapped in --> <!-- * something other than a Span, call the template with some value --> <!-- * for the 'wrapper-name' param --> <xsl:param name="wrapper-name">span</xsl:param> <xsl:element name="{$wrapper-name}"> <xsl:attribute name="class"> <xsl:value-of select="local-name(.)"/> </xsl:attribute> <xsl:call-template name="dir"/> <xsl:call-template name="generate.html.title"/> <xsl:copy-of select="$content"/> <xsl:call-template name="apply-annotations"/> </xsl:element> </xsl:template> <xsl:template name="inline.monoseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <code> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"/> <xsl:call-template name="generate.html.title"/> <xsl:copy-of select="$content"/> <xsl:call-template name="apply-annotations"/> </code> </xsl:template> <xsl:template name="inline.boldseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="dir"/> <!-- don't put <strong> inside figure, example, or table titles --> <xsl:choose> <xsl:when test="local-name(..) = 'title' and (local-name(../..) = 'figure' or local-name(../..) = 'example' or local-name(../..) = 'table')"> <xsl:copy-of select="$content"/> </xsl:when> <xsl:otherwise> <strong> <xsl:copy-of select="$content"/> </strong> </xsl:otherwise> </xsl:choose> <xsl:call-template name="apply-annotations"/> </span> </xsl:template> <xsl:template name="inline.italicseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <em> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="dir"/> <xsl:copy-of select="$content"/> <xsl:call-template name="apply-annotations"/> </em> </xsl:template> <xsl:template name="inline.boldmonoseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <!-- don't put <strong> inside figure, example, or table titles --> <!-- or other titles that may already be represented with <strong>'s. --> <xsl:choose> <xsl:when test="local-name(..) = 'title' and (local-name(../..) = 'figure' or local-name(../..) = 'example' or local-name(../..) = 'table' or local-name(../..) = 'formalpara')"> <code> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="dir"/> <xsl:copy-of select="$content"/> <xsl:call-template name="apply-annotations"/> </code> </xsl:when> <xsl:otherwise> <strong> <xsl:apply-templates select="." mode="class.attribute"/> <code> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="dir"/> <xsl:copy-of select="$content"/> </code> <xsl:call-template name="apply-annotations"/> </strong> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="inline.italicmonoseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <em> <xsl:apply-templates select="." mode="class.attribute"/> <code> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="dir"/> <xsl:copy-of select="$content"/> <xsl:call-template name="apply-annotations"/> </code> </em> </xsl:template> <xsl:template name="inline.superscriptseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <sup> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="dir"/> <xsl:copy-of select="$content"/> <xsl:call-template name="apply-annotations"/> </sup> </xsl:template> <xsl:template name="inline.subscriptseq"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:param> <sub> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="dir"/> <xsl:copy-of select="$content"/> <xsl:call-template name="apply-annotations"/> </sub> </xsl:template> <!-- ==================================================================== --> <!-- some special cases --> <xsl:template match="author"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:call-template name="person.name"/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <xsl:template match="editor"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:call-template name="person.name"/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <xsl:template match="othercredit"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:call-template name="person.name"/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <xsl:template match="authorinitials"> <xsl:call-template name="inline.charseq"/> </xsl:template> <!-- ==================================================================== --> <xsl:template match="accel"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="action"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="application"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="classname"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="exceptionname"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="interfacename"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="methodname"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="command"> <xsl:call-template name="inline.boldseq"/> </xsl:template> <xsl:template match="computeroutput"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="constant"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="database"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="date"> <!-- should this support locale-specific formatting? how? --> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="errorcode"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="errorname"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="errortype"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="errortext"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="envar"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="filename"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="function"> <xsl:choose> <xsl:when test="$function.parens != '0' and (parameter or function or replaceable)"> <xsl:variable name="nodes" select="text()|*"/> <xsl:call-template name="inline.monoseq"> <xsl:with-param name="content"> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates select="$nodes[1]"/> </xsl:with-param> </xsl:call-template> </xsl:with-param> </xsl:call-template> <xsl:text>(</xsl:text> <xsl:apply-templates select="$nodes[position()>1]"/> <xsl:text>)</xsl:text> </xsl:when> <xsl:otherwise> <xsl:call-template name="inline.monoseq"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="function/parameter" priority="2"> <xsl:call-template name="inline.italicmonoseq"/> <xsl:if test="following-sibling::*"> <xsl:text>, </xsl:text> </xsl:if> </xsl:template> <xsl:template match="function/replaceable" priority="2"> <xsl:call-template name="inline.italicmonoseq"/> <xsl:if test="following-sibling::*"> <xsl:text>, </xsl:text> </xsl:if> </xsl:template> <xsl:template match="guibutton"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="guiicon"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="guilabel"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="guimenu"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="guimenuitem"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="guisubmenu"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="hardware"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="interface"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="interfacedefinition"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="keycap"> <xsl:call-template name="inline.boldseq"/> </xsl:template> <xsl:template match="keycode"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="keysym"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="literal"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="code"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="medialabel"> <xsl:call-template name="inline.italicseq"/> </xsl:template> <xsl:template match="shortcut"> <xsl:call-template name="inline.boldseq"/> </xsl:template> <xsl:template match="mousebutton"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="option"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="package"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="parameter"> <xsl:call-template name="inline.italicmonoseq"/> </xsl:template> <xsl:template match="property"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="prompt"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="replaceable" priority="1"> <xsl:call-template name="inline.italicmonoseq"/> </xsl:template> <xsl:template match="returnvalue"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="structfield"> <xsl:call-template name="inline.italicmonoseq"/> </xsl:template> <xsl:template match="structname"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="symbol"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="systemitem"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="token"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="type"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="userinput"> <xsl:call-template name="inline.boldmonoseq"/> </xsl:template> <xsl:template match="abbrev"> <xsl:call-template name="inline.charseq"> <xsl:with-param name="wrapper-name">abbr</xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template match="acronym"> <xsl:call-template name="inline.charseq"> <xsl:with-param name="wrapper-name">acronym</xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template match="citerefentry"> <xsl:choose> <xsl:when test="$citerefentry.link != '0'"> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:call-template name="generate.citerefentry.link"/> </xsl:attribute> <xsl:call-template name="inline.charseq"/> </a> </xsl:when> <xsl:otherwise> <xsl:call-template name="inline.charseq"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="generate.citerefentry.link"> <!-- nop --> </xsl:template> <xsl:template name="x.generate.citerefentry.link"> <xsl:text>http://example.com/cgi-bin/man.cgi?</xsl:text> <xsl:value-of select="refentrytitle"/> <xsl:text>(</xsl:text> <xsl:value-of select="manvolnum"/> <xsl:text>)</xsl:text> </xsl:template> <xsl:template match="citetitle"> <xsl:choose> <xsl:when test="@pubwork = 'article'"> <xsl:call-template name="gentext.startquote"/> <xsl:call-template name="inline.charseq"/> <xsl:call-template name="gentext.endquote"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="inline.italicseq"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="emphasis"> <span> <xsl:choose> <xsl:when test="@role and $emphasis.propagates.style != 0"> <xsl:apply-templates select="." mode="class.attribute"> <xsl:with-param name="class" select="@role"/> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="." mode="class.attribute"/> </xsl:otherwise> </xsl:choose> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:choose> <xsl:when test="@role = 'bold' or @role='strong'"> <!-- backwards compatibility: make bold into b elements, but --> <!-- don't put bold inside figure, example, or table titles --> <xsl:choose> <xsl:when test="local-name(..) = 'title' and (local-name(../..) = 'figure' or local-name(../..) = 'example' or local-name(../..) = 'table')"> <xsl:apply-templates/> </xsl:when> <xsl:otherwise> <strong><xsl:apply-templates/></strong> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="@role and $emphasis.propagates.style != 0"> <xsl:apply-templates/> </xsl:when> <xsl:otherwise> <em><xsl:apply-templates/></em> </xsl:otherwise> </xsl:choose> </xsl:with-param> </xsl:call-template> </span> </xsl:template> <xsl:template match="foreignphrase"> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:if test="@lang or @xml:lang"> <xsl:call-template name="language.attribute"/> </xsl:if> <xsl:call-template name="inline.italicseq"/> </span> </xsl:template> <xsl:template match="markup"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="phrase"> <span> <xsl:call-template name="generate.html.title"/> <xsl:if test="@lang or @xml:lang"> <xsl:call-template name="language.attribute"/> </xsl:if> <xsl:if test="@role and $phrase.propagates.style != 0"> <xsl:apply-templates select="." mode="class.attribute"> <xsl:with-param name="class" select="@role"/> </xsl:apply-templates> </xsl:if> <xsl:call-template name="dir"/> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </span> </xsl:template> <xsl:template match="quote"> <xsl:variable name="depth"> <xsl:call-template name="dot.count"> <xsl:with-param name="string"> <xsl:number level="multiple"/> </xsl:with-param> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$depth mod 2 = 0"> <xsl:call-template name="gentext.startquote"/> <xsl:call-template name="inline.charseq"/> <xsl:call-template name="gentext.endquote"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="gentext.nestedstartquote"/> <xsl:call-template name="inline.charseq"/> <xsl:call-template name="gentext.nestedendquote"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="varname"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <xsl:template match="wordasword"> <xsl:call-template name="inline.italicseq"/> </xsl:template> <xsl:template match="lineannotation"> <em> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="inline.charseq"/> </em> </xsl:template> <xsl:template match="superscript"> <xsl:call-template name="inline.superscriptseq"/> </xsl:template> <xsl:template match="subscript"> <xsl:call-template name="inline.subscriptseq"/> </xsl:template> <xsl:template match="trademark"> <xsl:call-template name="inline.charseq"/> <xsl:choose> <xsl:when test="@class = 'copyright' or @class = 'registered'"> <xsl:call-template name="dingbat"> <xsl:with-param name="dingbat" select="@class"/> </xsl:call-template> </xsl:when> <xsl:when test="@class = 'service'"> <sup>SM</sup> </xsl:when> <xsl:otherwise> <xsl:call-template name="dingbat"> <xsl:with-param name="dingbat" select="'trademark'"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="firstterm"> <xsl:call-template name="glossterm"> <xsl:with-param name="firstterm" select="1"/> </xsl:call-template> </xsl:template> <xsl:template match="glossterm" name="glossterm"> <xsl:param name="firstterm" select="0"/> <!-- To avoid extra <a name=""> anchor from inline.italicseq --> <xsl:variable name="content"> <xsl:apply-templates/> </xsl:variable> <xsl:choose> <xsl:when test="($firstterm.only.link = 0 or $firstterm = 1) and @linkend"> <xsl:variable name="targets" select="key('id',@linkend)"/> <xsl:variable name="target" select="$targets[1]"/> <xsl:call-template name="check.id.unique"> <xsl:with-param name="linkend" select="@linkend"/> </xsl:call-template> <xsl:choose> <xsl:when test="$target"> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:if test="@id or @xml:id"> <xsl:attribute name="name"> <xsl:value-of select="(@id|@xml:id)[1]"/> </xsl:attribute> </xsl:if> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target"/> </xsl:call-template> </xsl:attribute> <xsl:call-template name="inline.italicseq"> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </a> </xsl:when> <xsl:otherwise> <xsl:call-template name="inline.italicseq"> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="not(@linkend) and ($firstterm.only.link = 0 or $firstterm = 1) and ($glossterm.auto.link != 0) and $glossary.collection != ''"> <xsl:variable name="term"> <xsl:choose> <xsl:when test="@baseform"><xsl:value-of select="@baseform"/></xsl:when> <xsl:otherwise><xsl:value-of select="."/></xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="cterm" select="(document($glossary.collection,.)//glossentry[glossterm=$term])[1]"/> <!-- HACK HACK HACK! But it works... --> <!-- You'd need to do more work if you wanted to chunk on glossdiv, though --> <xsl:variable name="glossary" select="//glossary[@role='auto']"/> <xsl:if test="count($glossary) != 1"> <xsl:message> <xsl:text>Warning: glossary.collection specified, but there are </xsl:text> <xsl:value-of select="count($glossary)"/> <xsl:text> automatic glossaries</xsl:text> </xsl:message> </xsl:if> <xsl:variable name="glosschunk"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$glossary"/> </xsl:call-template> </xsl:variable> <xsl:variable name="chunkbase"> <xsl:choose> <xsl:when test="contains($glosschunk, '#')"> <xsl:value-of select="substring-before($glosschunk, '#')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$glosschunk"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <xsl:when test="not($cterm)"> <xsl:message> <xsl:text>There's no entry for </xsl:text> <xsl:value-of select="$term"/> <xsl:text> in </xsl:text> <xsl:value-of select="$glossary.collection"/> </xsl:message> <xsl:call-template name="inline.italicseq"/> </xsl:when> <xsl:otherwise> <xsl:variable name="id"> <xsl:call-template name="object.id"> <xsl:with-param name="object" select="$cterm"/> </xsl:call-template> </xsl:variable> <a href="{$chunkbase}#{$id}"> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="inline.italicseq"> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </a> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="not(@linkend) and ($firstterm.only.link = 0 or $firstterm = 1) and $glossterm.auto.link != 0"> <xsl:variable name="term"> <xsl:choose> <xsl:when test="@baseform"> <xsl:value-of select="normalize-space(@baseform)"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="normalize-space(.)"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="targets" select="//glossentry[normalize-space(glossterm)=$term or normalize-space(glossterm/@baseform)=$term]"/> <xsl:variable name="target" select="$targets[1]"/> <xsl:choose> <xsl:when test="count($targets)=0"> <xsl:message> <xsl:text>Error: no glossentry for glossterm: </xsl:text> <xsl:value-of select="."/> <xsl:text>.</xsl:text> </xsl:message> <xsl:call-template name="inline.italicseq"/> </xsl:when> <xsl:otherwise> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:if test="@id or @xml:id"> <xsl:attribute name="name"> <xsl:value-of select="(@id|@xml:id)[1]"/> </xsl:attribute> </xsl:if> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target"/> </xsl:call-template> </xsl:attribute> <xsl:call-template name="inline.italicseq"> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </a> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:call-template name="inline.italicseq"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="termdef"> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="gentext.template"> <xsl:with-param name="context" select="'termdef'"/> <xsl:with-param name="name" select="'prefix'"/> </xsl:call-template> <xsl:apply-templates/> <xsl:call-template name="gentext.template"> <xsl:with-param name="context" select="'termdef'"/> <xsl:with-param name="name" select="'suffix'"/> </xsl:call-template> </span> </xsl:template> <xsl:template match="sgmltag|tag"> <xsl:call-template name="format.sgmltag"/> </xsl:template> <xsl:template name="format.sgmltag"> <xsl:param name="class"> <xsl:choose> <xsl:when test="@class"> <xsl:value-of select="@class"/> </xsl:when> <xsl:otherwise>element</xsl:otherwise> </xsl:choose> </xsl:param> <xsl:variable name="content"> <xsl:choose> <xsl:when test="$class='attribute'"> <xsl:apply-templates/> </xsl:when> <xsl:when test="$class='attvalue'"> <xsl:apply-templates/> </xsl:when> <xsl:when test="$class='element'"> <xsl:apply-templates/> </xsl:when> <xsl:when test="$class='endtag'"> <xsl:text></</xsl:text> <xsl:apply-templates/> <xsl:text>></xsl:text> </xsl:when> <xsl:when test="$class='genentity'"> <xsl:text>&</xsl:text> <xsl:apply-templates/> <xsl:text>;</xsl:text> </xsl:when> <xsl:when test="$class='numcharref'"> <xsl:text>&#</xsl:text> <xsl:apply-templates/> <xsl:text>;</xsl:text> </xsl:when> <xsl:when test="$class='paramentity'"> <xsl:text>%</xsl:text> <xsl:apply-templates/> <xsl:text>;</xsl:text> </xsl:when> <xsl:when test="$class='pi'"> <xsl:text><?</xsl:text> <xsl:apply-templates/> <xsl:text>></xsl:text> </xsl:when> <xsl:when test="$class='xmlpi'"> <xsl:text><?</xsl:text> <xsl:apply-templates/> <xsl:text>?></xsl:text> </xsl:when> <xsl:when test="$class='starttag'"> <xsl:text><</xsl:text> <xsl:apply-templates/> <xsl:text>></xsl:text> </xsl:when> <xsl:when test="$class='emptytag'"> <xsl:text><</xsl:text> <xsl:apply-templates/> <xsl:text>/></xsl:text> </xsl:when> <xsl:when test="$class='sgmlcomment' or $class='comment'"> <xsl:text><!--</xsl:text> <xsl:apply-templates/> <xsl:text>--></xsl:text> </xsl:when> <xsl:otherwise> <xsl:apply-templates/> </xsl:otherwise> </xsl:choose> </xsl:variable> <code> <xsl:apply-templates select="." mode="class.attribute"> <xsl:with-param name="class" select="concat('sgmltag-', $class)"/> </xsl:apply-templates> <xsl:call-template name="generate.html.title"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </code> </xsl:template> <xsl:template match="email"> <xsl:call-template name="inline.monoseq"> <xsl:with-param name="content"> <xsl:if test="not($email.delimiters.enabled = 0)"> <xsl:text><</xsl:text> </xsl:if> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:text>mailto:</xsl:text> <xsl:value-of select="."/> </xsl:attribute> <xsl:apply-templates/> </a> <xsl:if test="not($email.delimiters.enabled = 0)"> <xsl:text>></xsl:text> </xsl:if> </xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template match="keycombo"> <xsl:variable name="action" select="@action"/> <xsl:variable name="joinchar"> <xsl:choose> <xsl:when test="$action='seq'"><xsl:text> </xsl:text></xsl:when> <xsl:when test="$action='simul'">+</xsl:when> <xsl:when test="$action='press'">-</xsl:when> <xsl:when test="$action='click'">-</xsl:when> <xsl:when test="$action='double-click'">-</xsl:when> <xsl:when test="$action='other'"></xsl:when> <xsl:otherwise>+</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:for-each select="*"> <xsl:if test="position()>1"><xsl:value-of select="$joinchar"/></xsl:if> <xsl:apply-templates select="."/> </xsl:for-each> </xsl:template> <xsl:template match="uri"> <xsl:call-template name="inline.monoseq"/> </xsl:template> <!-- ==================================================================== --> <xsl:template match="menuchoice"> <xsl:variable name="shortcut" select="./shortcut"/> <xsl:call-template name="process.menuchoice"/> <xsl:if test="$shortcut"> <xsl:text> (</xsl:text> <xsl:apply-templates select="$shortcut"/> <xsl:text>)</xsl:text> </xsl:if> </xsl:template> <xsl:template name="process.menuchoice"> <xsl:param name="nodelist" select="guibutton|guiicon|guilabel|guimenu|guimenuitem|guisubmenu|interface"/><!-- not(shortcut) --> <xsl:param name="count" select="1"/> <xsl:choose> <xsl:when test="$count>count($nodelist)"></xsl:when> <xsl:when test="$count=1"> <xsl:apply-templates select="$nodelist[$count=position()]"/> <xsl:call-template name="process.menuchoice"> <xsl:with-param name="nodelist" select="$nodelist"/> <xsl:with-param name="count" select="$count+1"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:variable name="node" select="$nodelist[$count=position()]"/> <xsl:choose> <xsl:when test="local-name($node)='guimenuitem' or local-name($node)='guisubmenu'"> <xsl:value-of select="$menuchoice.menu.separator"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$menuchoice.separator"/> </xsl:otherwise> </xsl:choose> <xsl:apply-templates select="$node"/> <xsl:call-template name="process.menuchoice"> <xsl:with-param name="nodelist" select="$nodelist"/> <xsl:with-param name="count" select="$count+1"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- ==================================================================== --> <xsl:template match="optional"> <xsl:value-of select="$arg.choice.opt.open.str"/> <xsl:call-template name="inline.charseq"/> <xsl:value-of select="$arg.choice.opt.close.str"/> </xsl:template> <xsl:template match="citation"> <!-- todo: integrate with bibliography collection --> <xsl:variable name="targets" select="(//biblioentry | //bibliomixed)[abbrev = string(current())]"/> <xsl:variable name="target" select="$targets[1]"/> <xsl:choose> <!-- try automatic linking based on match to abbrev --> <xsl:when test="$target and not(xref) and not(link)"> <xsl:text>[</xsl:text> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target"/> </xsl:call-template> </xsl:attribute> <xsl:choose> <xsl:when test="$bibliography.numbered != 0"> <xsl:apply-templates select="$target" mode="citation"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="inline.charseq"/> </xsl:otherwise> </xsl:choose> </a> <xsl:text>]</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>[</xsl:text> <xsl:call-template name="inline.charseq"/> <xsl:text>]</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="biblioentry|bibliomixed" mode="citation"> <xsl:number from="bibliography" count="biblioentry|bibliomixed" level="any" format="1"/> </xsl:template> <!-- ==================================================================== --> <xsl:template match="comment[&comment.block.parents;]|remark[&comment.block.parents;]"> <xsl:if test="$show.comments != 0"> <p class="remark"><i><xsl:call-template name="inline.charseq"/></i></p> </xsl:if> </xsl:template> <xsl:template match="comment|remark"> <xsl:if test="$show.comments != 0"> <em><xsl:call-template name="inline.charseq"/></em> </xsl:if> </xsl:template> <!-- ==================================================================== --> <xsl:template match="productname"> <xsl:call-template name="inline.charseq"/> <xsl:if test="@class"> <xsl:call-template name="dingbat"> <xsl:with-param name="dingbat" select="@class"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template match="productnumber"> <xsl:call-template name="inline.charseq"/> </xsl:template> <!-- ==================================================================== --> <xsl:template match="pob|street|city|state|postcode|country|otheraddr"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="phone|fax"> <xsl:call-template name="inline.charseq"/> </xsl:template> <!-- in Addresses, for example --> <xsl:template match="honorific|firstname|surname|lineage|othername"> <xsl:call-template name="inline.charseq"/> </xsl:template> <!-- ==================================================================== --> <xsl:template match="person"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates select="personname"/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <xsl:template match="personname"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:call-template name="person.name"/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <!-- ==================================================================== --> <xsl:template match="org"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <xsl:template match="orgname"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <xsl:template match="orgdiv"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <xsl:template match="affiliation"> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> <xsl:with-param name="content"> <xsl:call-template name="person.name"/> </xsl:with-param> </xsl:call-template> <xsl:call-template name="apply-annotations"/> </xsl:param> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$content"/> </span> </xsl:template> <!-- ==================================================================== --> <xsl:template match="beginpage"> <!-- does nothing; this *is not* markup to force a page break. --> </xsl:template> </xsl:stylesheet> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/htmltbl.xsl���������������������������������������������������������0000664�0001750�0001750�00000003073�11175136613�021633� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <!-- ******************************************************************** $Id: htmltbl.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:template match="colgroup" mode="htmlTable"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="htmlTable"/> </xsl:copy> </xsl:template> <xsl:template match="col" mode="htmlTable"> <xsl:copy> <xsl:copy-of select="@*"/> </xsl:copy> </xsl:template> <xsl:template match="caption" mode="htmlTable"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates select=".." mode="object.title.markup"> <xsl:with-param name="allow-anchors" select="1"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <xsl:template match="thead|tbody|tgroup|tr" mode="htmlTable"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="htmlTable"/> </xsl:copy> </xsl:template> <xsl:template match="th|td" mode="htmlTable"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates/> <!-- *not* mode=htmlTable --> </xsl:copy> </xsl:template> </xsl:stylesheet> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/keywords.xsl��������������������������������������������������������0000664�0001750�0001750�00000002315�11175136613�022032� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> <!-- ******************************************************************** $Id: keywords.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <xsl:template match="keywordset"></xsl:template> <xsl:template match="subjectset"></xsl:template> <!-- ==================================================================== --> <xsl:template match="keywordset" mode="html.header"> <meta name="keywords"> <xsl:attribute name="content"> <xsl:apply-templates select="keyword" mode="html.header"/> </xsl:attribute> </meta> </xsl:template> <xsl:template match="keyword" mode="html.header"> <xsl:apply-templates/> <xsl:if test="following-sibling::keyword">, </xsl:if> </xsl:template> <!-- ==================================================================== --> </xsl:stylesheet> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/index.xsl�����������������������������������������������������������0000664�0001750�0001750�00000016032�11175136613�021273� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> <!-- ******************************************************************** $Id: index.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:template match="index"> <!-- some implementations use completely empty index tags to indicate --> <!-- where an automatically generated index should be inserted. so --> <!-- if the index is completely empty, skip it. Unless generate.index --> <!-- is non-zero, in which case, this is where the automatically --> <!-- generated index should go. --> <xsl:call-template name="id.warning"/> <xsl:if test="count(*)>0 or $generate.index != '0'"> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="index.titlepage"/> <xsl:choose> <xsl:when test="indexdiv"> <xsl:apply-templates/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="*[not(self::indexentry)]"/> <!-- Because it's actually valid for Index to have neither any --> <!-- Indexdivs nor any Indexentries, we need to check and make --> <!-- sure that at least one Indexentry exists, and generate a --> <!-- wrapper dl if there is at least one; otherwise, do nothing. --> <xsl:if test="indexentry"> <!-- The indexentry template assumes a parent dl wrapper has --> <!-- been generated; for Indexes that have Indexdivs, the dl --> <!-- wrapper is generated by the indexdiv template; however, --> <!-- for Indexes that lack Indexdivs, if we don't generate a --> <!-- dl here, HTML output will not be valid. --> <dl> <xsl:apply-templates select="indexentry"/> </dl> </xsl:if> </xsl:otherwise> </xsl:choose> <xsl:if test="count(indexentry) = 0 and count(indexdiv) = 0"> <xsl:call-template name="generate-index"> <xsl:with-param name="scope" select="(ancestor::book|/)[last()]"/> </xsl:call-template> </xsl:if> <xsl:if test="not(parent::article)"> <xsl:call-template name="process.footnotes"/> </xsl:if> </div> </xsl:if> </xsl:template> <xsl:template match="setindex"> <!-- some implementations use completely empty index tags to indicate --> <!-- where an automatically generated index should be inserted. so --> <!-- if the index is completely empty, skip it. Unless generate.index --> <!-- is non-zero, in which case, this is where the automatically --> <!-- generated index should go. --> <xsl:call-template name="id.warning"/> <xsl:if test="count(*)>0 or $generate.index != '0'"> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="setindex.titlepage"/> <xsl:apply-templates/> <xsl:if test="count(indexentry) = 0 and count(indexdiv) = 0"> <xsl:call-template name="generate-index"> <xsl:with-param name="scope" select="/"/> </xsl:call-template> </xsl:if> <xsl:if test="not(parent::article)"> <xsl:call-template name="process.footnotes"/> </xsl:if> </div> </xsl:if> </xsl:template> <xsl:template match="index/indexinfo"></xsl:template> <xsl:template match="index/info"></xsl:template> <xsl:template match="index/title"></xsl:template> <xsl:template match="index/subtitle"></xsl:template> <xsl:template match="index/titleabbrev"></xsl:template> <!-- ==================================================================== --> <xsl:template match="indexdiv"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="anchor"/> <xsl:apply-templates select="*[not(self::indexentry)]"/> <dl> <xsl:apply-templates select="indexentry"/> </dl> </div> </xsl:template> <xsl:template match="indexdiv/title"> <h3> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:apply-templates/> </h3> </xsl:template> <!-- ==================================================================== --> <xsl:template match="indexterm"> <!-- this one must have a name, even if it doesn't have an ID --> <xsl:variable name="id"> <xsl:call-template name="object.id"/> </xsl:variable> <a class="indexterm" name="{$id}"/> </xsl:template> <xsl:template match="primary|secondary|tertiary|see|seealso"> </xsl:template> <!-- ==================================================================== --> <xsl:template match="indexentry"> <xsl:apply-templates select="primaryie"/> </xsl:template> <xsl:template match="primaryie"> <dt> <xsl:apply-templates/> </dt> <xsl:choose> <xsl:when test="following-sibling::secondaryie"> <dd> <dl> <xsl:apply-templates select="following-sibling::secondaryie"/> </dl> </dd> </xsl:when> <xsl:when test="following-sibling::seeie |following-sibling::seealsoie"> <dd> <dl> <xsl:apply-templates select="following-sibling::seeie |following-sibling::seealsoie"/> </dl> </dd> </xsl:when> </xsl:choose> </xsl:template> <xsl:template match="secondaryie"> <dt> <xsl:apply-templates/> </dt> <xsl:choose> <xsl:when test="following-sibling::tertiaryie"> <dd> <dl> <xsl:apply-templates select="following-sibling::tertiaryie"/> </dl> </dd> </xsl:when> <xsl:when test="following-sibling::seeie |following-sibling::seealsoie"> <dd> <dl> <xsl:apply-templates select="following-sibling::seeie |following-sibling::seealsoie"/> </dl> </dd> </xsl:when> </xsl:choose> </xsl:template> <xsl:template match="tertiaryie"> <dt> <xsl:apply-templates/> </dt> <xsl:if test="following-sibling::seeie |following-sibling::seealsoie"> <dd> <dl> <xsl:apply-templates select="following-sibling::seeie |following-sibling::seealsoie"/> </dl> </dd> </xsl:if> </xsl:template> <xsl:template match="seeie|seealsoie"> <dt> <xsl:apply-templates/> </dt> </xsl:template> </xsl:stylesheet> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/math.xsl������������������������������������������������������������0000664�0001750�0001750�00000021636�11175136613�021123� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:mml="http://www.w3.org/1998/Math/MathML" exclude-result-prefixes="mml" version='1.0'> <!-- ******************************************************************** $Id: math.xsl 6961 2007-07-07 02:05:56Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <xsl:template match="inlineequation"> <xsl:apply-templates/> </xsl:template> <xsl:template match="alt"> </xsl:template> <xsl:template match="mathphrase"> <span> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:apply-templates/> </span> </xsl:template> <!-- "Support" for MathML --> <xsl:template match="mml:*" xmlns:mml="http://www.w3.org/1998/Math/MathML"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates/> </xsl:copy> </xsl:template> <!-- Support for TeX math in alt --> <xsl:template match="*" mode="collect.tex.math"> <xsl:call-template name="write.text.chunk"> <xsl:with-param name="filename" select="$tex.math.file"/> <xsl:with-param name="method" select="'text'"/> <xsl:with-param name="content"> <xsl:choose> <xsl:when test="$tex.math.in.alt = 'plain'"> <xsl:call-template name="tex.math.plain.head"/> <xsl:apply-templates select="." mode="collect.tex.math.plain"/> <xsl:call-template name="tex.math.plain.tail"/> </xsl:when> <xsl:when test="$tex.math.in.alt = 'latex'"> <xsl:call-template name="tex.math.latex.head"/> <xsl:apply-templates select="." mode="collect.tex.math.latex"/> <xsl:call-template name="tex.math.latex.tail"/> </xsl:when> <xsl:otherwise> <xsl:message> Unsupported TeX math notation: <xsl:value-of select="$tex.math.in.alt"/> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:with-param> <xsl:with-param name="encoding" select="$chunker.output.encoding"/> </xsl:call-template> </xsl:template> <!-- PlainTeX --> <xsl:template name="tex.math.plain.head"> <xsl:text>\nopagenumbers </xsl:text> </xsl:template> <xsl:template name="tex.math.plain.tail"> <xsl:text>\bye </xsl:text> </xsl:template> <xsl:template match="inlineequation" mode="collect.tex.math.plain"> <xsl:variable name="filename"> <xsl:choose> <xsl:when test="graphic"> <xsl:call-template name="mediaobject.filename"> <xsl:with-param name="object" select="graphic"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="select.mediaobject.filename"> <xsl:with-param name="olist" select="inlinemediaobject/*"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="output.delims"> <xsl:call-template name="tex.math.output.delims"/> </xsl:variable> <xsl:variable name="tex" select="alt[@role='tex'] | inlinemediaobject/textobject[@role='tex']"/> <xsl:if test="$tex"> <xsl:text>\special{dvi2bitmap outputfile </xsl:text> <xsl:value-of select="$filename"/> <xsl:text>} </xsl:text> <xsl:if test="$output.delims != 0"> <xsl:text>$</xsl:text> </xsl:if> <xsl:value-of select="$tex"/> <xsl:if test="$output.delims != 0"> <xsl:text>$ </xsl:text> </xsl:if> <xsl:text>\vfill\eject </xsl:text> </xsl:if> </xsl:template> <xsl:template match="equation|informalequation" mode="collect.tex.math.plain"> <xsl:variable name="filename"> <xsl:choose> <xsl:when test="graphic"> <xsl:call-template name="mediaobject.filename"> <xsl:with-param name="object" select="graphic"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="select.mediaobject.filename"> <xsl:with-param name="olist" select="mediaobject/*"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="output.delims"> <xsl:call-template name="tex.math.output.delims"/> </xsl:variable> <xsl:variable name="tex" select="alt[@role='tex'] | mediaobject/textobject[@role='tex']"/> <xsl:if test="$tex"> <xsl:text>\special{dvi2bitmap outputfile </xsl:text> <xsl:value-of select="$filename"/> <xsl:text>} </xsl:text> <xsl:if test="$output.delims != 0"> <xsl:text>$$</xsl:text> </xsl:if> <xsl:value-of select="$tex"/> <xsl:if test="$output.delims != 0"> <xsl:text>$$ </xsl:text> </xsl:if> <xsl:text>\vfill\eject </xsl:text> </xsl:if> </xsl:template> <xsl:template match="text()" mode="collect.tex.math.plain"/> <!-- LaTeX --> <xsl:template name="tex.math.latex.head"> <xsl:text>\documentclass{article} </xsl:text> <xsl:text>\pagestyle{empty} </xsl:text> <xsl:text>\begin{document} </xsl:text> </xsl:template> <xsl:template name="tex.math.latex.tail"> <xsl:text>\end{document} </xsl:text> </xsl:template> <xsl:template match="inlineequation" mode="collect.tex.math.latex"> <xsl:variable name="filename"> <xsl:choose> <xsl:when test="graphic"> <xsl:call-template name="mediaobject.filename"> <xsl:with-param name="object" select="graphic"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="select.mediaobject.filename"> <xsl:with-param name="olist" select="inlinemediaobject/*"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="output.delims"> <xsl:call-template name="tex.math.output.delims"/> </xsl:variable> <xsl:variable name="tex" select="alt[@role='tex'] | inlinemediaobject/textobject[@role='tex']"/> <xsl:if test="$tex"> <xsl:text>\special{dvi2bitmap outputfile </xsl:text> <xsl:value-of select="$filename"/> <xsl:text>} </xsl:text> <xsl:if test="$output.delims != 0"> <xsl:text>$</xsl:text> </xsl:if> <xsl:value-of select="$tex"/> <xsl:if test="$output.delims != 0"> <xsl:text>$ </xsl:text> </xsl:if> <xsl:text>\newpage </xsl:text> </xsl:if> </xsl:template> <xsl:template match="equation|informalequation" mode="collect.tex.math.latex"> <xsl:variable name="filename"> <xsl:choose> <xsl:when test="graphic"> <xsl:call-template name="mediaobject.filename"> <xsl:with-param name="object" select="graphic"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="select.mediaobject.filename"> <xsl:with-param name="olist" select="mediaobject/*"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="output.delims"> <xsl:call-template name="tex.math.output.delims"/> </xsl:variable> <xsl:variable name="tex" select="alt[@role='tex'] | mediaobject/textobject[@role='tex']"/> <xsl:if test="$tex"> <xsl:text>\special{dvi2bitmap outputfile </xsl:text> <xsl:value-of select="$filename"/> <xsl:text>} </xsl:text> <xsl:if test="$output.delims != 0"> <xsl:text>$$</xsl:text> </xsl:if> <xsl:value-of select="$tex"/> <xsl:if test="$output.delims != 0"> <xsl:text>$$ </xsl:text> </xsl:if> <xsl:text>\newpage </xsl:text> </xsl:if> </xsl:template> <xsl:template match="text()" mode="collect.tex.math.latex"/> <!-- Extracting image filename from mediaobject and graphic elements --> <xsl:template name="select.mediaobject.filename"> <xsl:param name="olist" select="imageobject|imageobjectco |videoobject|audioobject|textobject"/> <xsl:variable name="mediaobject.index"> <xsl:call-template name="select.mediaobject.index"> <xsl:with-param name="olist" select="$olist"/> <xsl:with-param name="count" select="1"/> </xsl:call-template> </xsl:variable> <xsl:if test="$mediaobject.index != ''"> <xsl:call-template name="mediaobject.filename"> <xsl:with-param name="object" select="$olist[position() = $mediaobject.index]"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="tex.math.output.delims"> <xsl:variable name="pi.delims"> <xsl:call-template name="pi.dbtex_delims"> <xsl:with-param name="node" select="descendant-or-self::*"/> </xsl:call-template> </xsl:variable> <xsl:variable name="result"> <xsl:choose> <xsl:when test="$pi.delims = 'no'">0</xsl:when> <xsl:when test="$pi.delims = '' and $tex.math.delims = 0">0</xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:value-of select="$result"/> </xsl:template> </xsl:stylesheet> ��������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/formal.xsl����������������������������������������������������������0000664�0001750�0001750�00000031271�11175136613�021446� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> <!-- ******************************************************************** $Id: formal.xsl 7249 2007-08-18 09:34:34Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <xsl:param name="formal.object.break.after">1</xsl:param> <xsl:template name="formal.object"> <xsl:param name="placement" select="'before'"/> <xsl:param name="class"> <xsl:apply-templates select="." mode="class.value"/> </xsl:param> <xsl:call-template name="id.warning"/> <xsl:variable name="content"> <div class="{$class}"> <xsl:call-template name="anchor"> <xsl:with-param name="conditional" select="0"/> </xsl:call-template> <xsl:choose> <xsl:when test="$placement = 'before'"> <xsl:call-template name="formal.object.heading"/> <div class="{$class}-contents"> <xsl:apply-templates/> </div> <!-- HACK: This doesn't belong inside formal.object; it should be done by the table template, but I want the link to be inside the DIV, so... --> <xsl:if test="local-name(.) = 'table'"> <xsl:call-template name="table.longdesc"/> </xsl:if> <xsl:if test="$spacing.paras != 0"><p/></xsl:if> </xsl:when> <xsl:otherwise> <xsl:if test="$spacing.paras != 0"><p/></xsl:if> <div class="{$class}-contents"><xsl:apply-templates/></div> <!-- HACK: This doesn't belong inside formal.object; it should be done by the table template, but I want the link to be inside the DIV, so... --> <xsl:if test="local-name(.) = 'table'"> <xsl:call-template name="table.longdesc"/> </xsl:if> <xsl:call-template name="formal.object.heading"/> </xsl:otherwise> </xsl:choose> </div> <xsl:if test="not($formal.object.break.after = '0')"> <br class="{$class}-break"/> </xsl:if> </xsl:variable> <xsl:variable name="floatstyle"> <xsl:call-template name="floatstyle"/> </xsl:variable> <xsl:choose> <xsl:when test="$floatstyle != ''"> <xsl:call-template name="floater"> <xsl:with-param name="class"><xsl:value-of select="$class"/>-float</xsl:with-param> <xsl:with-param name="floatstyle" select="$floatstyle"/> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:copy-of select="$content"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="formal.object.heading"> <xsl:param name="object" select="."/> <xsl:param name="title"> <xsl:apply-templates select="$object" mode="object.title.markup"> <xsl:with-param name="allow-anchors" select="1"/> </xsl:apply-templates> </xsl:param> <p class="title"> <b> <xsl:copy-of select="$title"/> </b> </p> </xsl:template> <xsl:template name="informal.object"> <xsl:param name="class" select="local-name(.)"/> <xsl:variable name="content"> <div class="{$class}"> <xsl:if test="$spacing.paras != 0"><p/></xsl:if> <xsl:call-template name="anchor"/> <xsl:apply-templates/> <!-- HACK: This doesn't belong inside formal.object; it should be done by the table template, but I want the link to be inside the DIV, so... --> <xsl:if test="local-name(.) = 'informaltable'"> <xsl:call-template name="table.longdesc"/> </xsl:if> <xsl:if test="$spacing.paras != 0"><p/></xsl:if> </div> </xsl:variable> <xsl:variable name="floatstyle"> <xsl:call-template name="floatstyle"/> </xsl:variable> <xsl:choose> <xsl:when test="$floatstyle != ''"> <xsl:call-template name="floater"> <xsl:with-param name="class"><xsl:value-of select="$class"/>-float</xsl:with-param> <xsl:with-param name="floatstyle" select="$floatstyle"/> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:copy-of select="$content"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="semiformal.object"> <xsl:param name="placement" select="'before'"/> <xsl:param name="class" select="local-name(.)"/> <xsl:choose> <xsl:when test="title"> <xsl:call-template name="formal.object"> <xsl:with-param name="placement" select="$placement"/> <xsl:with-param name="class" select="$class"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="informal.object"> <xsl:with-param name="class" select="$class"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="figure"> <xsl:variable name="param.placement" select="substring-after(normalize-space($formal.title.placement), concat(local-name(.), ' '))"/> <xsl:variable name="placement"> <xsl:choose> <xsl:when test="contains($param.placement, ' ')"> <xsl:value-of select="substring-before($param.placement, ' ')"/> </xsl:when> <xsl:when test="$param.placement = ''">before</xsl:when> <xsl:otherwise> <xsl:value-of select="$param.placement"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:call-template name="formal.object"> <xsl:with-param name="placement" select="$placement"/> </xsl:call-template> </xsl:template> <xsl:template match="table"> <xsl:choose> <xsl:when test="tgroup|mediaobject|graphic"> <xsl:call-template name="calsTable"/> </xsl:when> <xsl:otherwise> <xsl:copy> <xsl:copy-of select="@*[not(local-name()='id')]"/> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> <xsl:call-template name="htmlTable"/> </xsl:copy> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="calsTable"> <xsl:if test="tgroup/tbody/tr |tgroup/thead/tr |tgroup/tfoot/tr"> <xsl:message terminate="yes">Broken table: tr descendent of CALS Table.</xsl:message> </xsl:if> <xsl:variable name="param.placement" select="substring-after(normalize-space($formal.title.placement), concat(local-name(.), ' '))"/> <xsl:variable name="placement"> <xsl:choose> <xsl:when test="contains($param.placement, ' ')"> <xsl:value-of select="substring-before($param.placement, ' ')"/> </xsl:when> <xsl:when test="$param.placement = ''">before</xsl:when> <xsl:otherwise> <xsl:value-of select="$param.placement"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:call-template name="formal.object"> <xsl:with-param name="placement" select="$placement"/> <xsl:with-param name="class"> <xsl:choose> <xsl:when test="@tabstyle"> <!-- hack, this will only ever occur on table, not example --> <xsl:value-of select="@tabstyle"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="local-name(.)"/> </xsl:otherwise> </xsl:choose> </xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template name="htmlTable"> <xsl:if test="tgroup/tbody/row |tgroup/thead/row |tgroup/tfoot/row"> <xsl:message terminate="yes">Broken table: row descendent of HTML table.</xsl:message> </xsl:if> <xsl:apply-templates mode="htmlTable"/> </xsl:template> <xsl:template match="example"> <xsl:variable name="param.placement" select="substring-after(normalize-space($formal.title.placement), concat(local-name(.), ' '))"/> <xsl:variable name="placement"> <xsl:choose> <xsl:when test="contains($param.placement, ' ')"> <xsl:value-of select="substring-before($param.placement, ' ')"/> </xsl:when> <xsl:when test="$param.placement = ''">before</xsl:when> <xsl:otherwise> <xsl:value-of select="$param.placement"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:call-template name="formal.object"> <xsl:with-param name="placement" select="$placement"/> </xsl:call-template> </xsl:template> <xsl:template match="equation"> <xsl:variable name="param.placement" select="substring-after(normalize-space($formal.title.placement), concat(local-name(.), ' '))"/> <xsl:variable name="placement"> <xsl:choose> <xsl:when test="contains($param.placement, ' ')"> <xsl:value-of select="substring-before($param.placement, ' ')"/> </xsl:when> <xsl:when test="$param.placement = ''">before</xsl:when> <xsl:otherwise> <xsl:value-of select="$param.placement"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:call-template name="formal.object"> <xsl:with-param name="placement" select="$placement"/> </xsl:call-template> </xsl:template> <xsl:template match="figure/title"></xsl:template> <xsl:template match="figure/titleabbrev"></xsl:template> <xsl:template match="table/title"></xsl:template> <xsl:template match="table/titleabbrev"></xsl:template> <xsl:template match="table/textobject"></xsl:template> <xsl:template match="example/title"></xsl:template> <xsl:template match="example/titleabbrev"></xsl:template> <xsl:template match="equation/title"></xsl:template> <xsl:template match="equation/titleabbrev"></xsl:template> <xsl:template match="informalfigure"> <xsl:call-template name="informal.object"/> </xsl:template> <xsl:template match="informalexample"> <xsl:call-template name="informal.object"/> </xsl:template> <xsl:template match="informaltable"> <xsl:choose> <xsl:when test="tgroup|mediaobject|graphic"> <xsl:call-template name="informal.object"> <xsl:with-param name="class"> <xsl:choose> <xsl:when test="@tabstyle"> <xsl:value-of select="@tabstyle"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="local-name(.)"/> </xsl:otherwise> </xsl:choose> </xsl:with-param> </xsl:call-template> </xsl:when> <xsl:otherwise> <table> <xsl:copy-of select="@*"/> <xsl:call-template name="htmlTable"/> </table> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="informaltable/textobject"></xsl:template> <xsl:template name="table.longdesc"> <!-- HACK: This doesn't belong inside formal.objectt; it should be done by --> <!-- the table template, but I want the link to be inside the DIV, so... --> <xsl:variable name="longdesc.uri"> <xsl:call-template name="longdesc.uri"> <xsl:with-param name="mediaobject" select="."/> </xsl:call-template> </xsl:variable> <xsl:variable name="irrelevant"> <!-- write.longdesc returns the filename ... --> <xsl:call-template name="write.longdesc"> <xsl:with-param name="mediaobject" select="."/> </xsl:call-template> </xsl:variable> <xsl:if test="$html.longdesc != 0 and $html.longdesc.link != 0 and textobject[not(phrase)]"> <xsl:call-template name="longdesc.link"> <xsl:with-param name="longdesc.uri" select="$longdesc.uri"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template match="informalequation"> <xsl:call-template name="informal.object"/> </xsl:template> <xsl:template name="floatstyle"> <xsl:if test="(@float and @float != '0') or @floatstyle != ''"> <xsl:choose> <xsl:when test="@floatstyle != ''"> <xsl:value-of select="@floatstyle"/> </xsl:when> <xsl:when test="@float = '1'"> <xsl:value-of select="$default.float.class"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="@float"/> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:template> <xsl:template name="floater"> <xsl:param name="content"/> <xsl:param name="class" select="'float'"/> <xsl:param name="floatstyle" select="'left'"/> <div class="{$class}"> <xsl:if test="$floatstyle = 'left' or $floatstyle = 'right'"> <xsl:attribute name="style"> <xsl:text>float: </xsl:text> <xsl:value-of select="$floatstyle"/> <xsl:text>;</xsl:text> </xsl:attribute> </xsl:if> <xsl:copy-of select="$content"/> </div> </xsl:template> </xsl:stylesheet> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/table.xsl�����������������������������������������������������������0000664�0001750�0001750�00000115557�11175136613�021267� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:doc="http://nwalsh.com/xsl/documentation/1.0" xmlns:stbl="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.Table" xmlns:xtbl="xalan://com.nwalsh.xalan.Table" xmlns:lxslt="http://xml.apache.org/xslt" xmlns:ptbl="http://nwalsh.com/xslt/ext/xsltproc/python/Table" exclude-result-prefixes="doc stbl xtbl lxslt ptbl" version='1.0'> <xsl:include href="../common/table.xsl"/> <!-- ******************************************************************** $Id: table.xsl 7009 2007-07-11 09:42:54Z mzjn $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <lxslt:component prefix="xtbl" functions="adjustColumnWidths"/> <xsl:template name="empty.table.cell"> <xsl:param name="colnum" select="0"/> <xsl:variable name="rowsep"> <xsl:choose> <!-- If this is the last row, rowsep never applies. --> <xsl:when test="not(ancestor-or-self::row[1]/following-sibling::row or ancestor-or-self::thead/following-sibling::tbody or ancestor-or-self::tbody/preceding-sibling::tfoot)"> <xsl:value-of select="0"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="NOT-AN-ELEMENT-NAME"/> <xsl:with-param name="row" select="ancestor-or-self::row[1]"/> <xsl:with-param name="colnum" select="$colnum"/> <xsl:with-param name="attribute" select="'rowsep'"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="colsep"> <xsl:choose> <!-- If this is the last column, colsep never applies. --> <xsl:when test="number($colnum) >= ancestor::tgroup/@cols">0</xsl:when> <xsl:otherwise> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="NOT-AN-ELEMENT-NAME"/> <xsl:with-param name="row" select="ancestor-or-self::row[1]"/> <xsl:with-param name="colnum" select="$colnum"/> <xsl:with-param name="attribute" select="'colsep'"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <td class="auto-generated"> <xsl:if test="$table.borders.with.css != 0"> <xsl:attribute name="style"> <xsl:if test="$colsep > 0"> <xsl:call-template name="border"> <xsl:with-param name="side" select="'right'"/> </xsl:call-template> </xsl:if> <xsl:if test="$rowsep > 0"> <xsl:call-template name="border"> <xsl:with-param name="side" select="'bottom'"/> </xsl:call-template> </xsl:if> </xsl:attribute> </xsl:if> <xsl:text> </xsl:text> </td> </xsl:template> <!-- ==================================================================== --> <xsl:template name="border"> <xsl:param name="side" select="'left'"/> <xsl:param name="padding" select="0"/> <xsl:param name="style" select="$table.cell.border.style"/> <xsl:param name="color" select="$table.cell.border.color"/> <xsl:param name="thickness" select="$table.cell.border.thickness"/> <!-- Note: Some browsers (mozilla) require at least a width and style. --> <xsl:choose> <xsl:when test="($thickness != '' and $style != '' and $color != '') or ($thickness != '' and $style != '') or ($thickness != '')"> <!-- use the compound property if we can: --> <!-- it saves space and probably works more reliably --> <xsl:text>border-</xsl:text> <xsl:value-of select="$side"/> <xsl:text>: </xsl:text> <xsl:value-of select="$thickness"/> <xsl:text> </xsl:text> <xsl:value-of select="$style"/> <xsl:text> </xsl:text> <xsl:value-of select="$color"/> <xsl:text>; </xsl:text> </xsl:when> <xsl:otherwise> <!-- we need to specify the styles individually --> <xsl:if test="$thickness != ''"> <xsl:text>border-</xsl:text> <xsl:value-of select="$side"/> <xsl:text>-width: </xsl:text> <xsl:value-of select="$thickness"/> <xsl:text>; </xsl:text> </xsl:if> <xsl:if test="$style != ''"> <xsl:text>border-</xsl:text> <xsl:value-of select="$side"/> <xsl:text>-style: </xsl:text> <xsl:value-of select="$style"/> <xsl:text>; </xsl:text> </xsl:if> <xsl:if test="$color != ''"> <xsl:text>border-</xsl:text> <xsl:value-of select="$side"/> <xsl:text>-color: </xsl:text> <xsl:value-of select="$color"/> <xsl:text>; </xsl:text> </xsl:if> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- ==================================================================== --> <xsl:template match="tgroup" name="tgroup"> <xsl:if test="not(@cols) or @cols = '' or string(number(@cols)) = 'NaN'"> <xsl:message terminate="yes"> <xsl:text>Error: CALS tables must specify the number of columns.</xsl:text> </xsl:message> </xsl:if> <xsl:variable name="summary"> <xsl:call-template name="pi.dbhtml_table-summary"/> </xsl:variable> <xsl:variable name="cellspacing"> <xsl:call-template name="pi.dbhtml_cellspacing"/> </xsl:variable> <xsl:variable name="cellpadding"> <xsl:call-template name="pi.dbhtml_cellpadding"/> </xsl:variable> <table> <xsl:choose> <!-- If there's a textobject/phrase for the table summary, use it --> <xsl:when test="../textobject/phrase"> <xsl:attribute name="summary"> <xsl:value-of select="../textobject/phrase"/> </xsl:attribute> </xsl:when> <!-- If there's a <?dbhtml table-summary="foo"?> PI, use it for the HTML table summary attribute --> <xsl:when test="$summary != ''"> <xsl:attribute name="summary"> <xsl:value-of select="$summary"/> </xsl:attribute> </xsl:when> <!-- Otherwise, if there's a title, use that --> <xsl:when test="../title"> <xsl:attribute name="summary"> <xsl:value-of select="string(../title)"/> </xsl:attribute> </xsl:when> <!-- Otherwise, forget the whole idea --> <xsl:otherwise><!-- nevermind --></xsl:otherwise> </xsl:choose> <xsl:if test="$cellspacing != '' or $html.cellspacing != ''"> <xsl:attribute name="cellspacing"> <xsl:choose> <xsl:when test="$cellspacing != ''"> <xsl:value-of select="$cellspacing"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$html.cellspacing"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> </xsl:if> <xsl:if test="$cellpadding != '' or $html.cellpadding != ''"> <xsl:attribute name="cellpadding"> <xsl:choose> <xsl:when test="$cellpadding != ''"> <xsl:value-of select="$cellpadding"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$html.cellpadding"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> </xsl:if> <xsl:if test="../@pgwide=1 or local-name(.) = 'entrytbl'"> <xsl:attribute name="width">100%</xsl:attribute> </xsl:if> <xsl:choose> <xsl:when test="$table.borders.with.css != 0"> <xsl:choose> <xsl:when test="../@frame='all' or (not(../@frame) and $default.table.frame='all')"> <xsl:attribute name="style"> <xsl:text>border-collapse: collapse;</xsl:text> <xsl:call-template name="border"> <xsl:with-param name="side" select="'top'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> <xsl:call-template name="border"> <xsl:with-param name="side" select="'bottom'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> <xsl:call-template name="border"> <xsl:with-param name="side" select="'left'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> <xsl:call-template name="border"> <xsl:with-param name="side" select="'right'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> </xsl:attribute> </xsl:when> <xsl:when test="../@frame='topbot' or (not(../@frame) and $default.table.frame='topbot')"> <xsl:attribute name="style"> <xsl:text>border-collapse: collapse;</xsl:text> <xsl:call-template name="border"> <xsl:with-param name="side" select="'top'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> <xsl:call-template name="border"> <xsl:with-param name="side" select="'bottom'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> </xsl:attribute> </xsl:when> <xsl:when test="../@frame='top' or (not(../@frame) and $default.table.frame='top')"> <xsl:attribute name="style"> <xsl:text>border-collapse: collapse;</xsl:text> <xsl:call-template name="border"> <xsl:with-param name="side" select="'top'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> </xsl:attribute> </xsl:when> <xsl:when test="../@frame='bottom' or (not(../@frame) and $default.table.frame='bottom')"> <xsl:attribute name="style"> <xsl:text>border-collapse: collapse;</xsl:text> <xsl:call-template name="border"> <xsl:with-param name="side" select="'bottom'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> </xsl:attribute> </xsl:when> <xsl:when test="../@frame='sides' or (not(../@frame) and $default.table.frame='sides')"> <xsl:attribute name="style"> <xsl:text>border-collapse: collapse;</xsl:text> <xsl:call-template name="border"> <xsl:with-param name="side" select="'left'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> <xsl:call-template name="border"> <xsl:with-param name="side" select="'right'"/> <xsl:with-param name="style" select="$table.frame.border.style"/> <xsl:with-param name="color" select="$table.frame.border.color"/> <xsl:with-param name="thickness" select="$table.frame.border.thickness"/> </xsl:call-template> </xsl:attribute> </xsl:when> <xsl:when test="../@frame='none'"> <xsl:attribute name="style"> <xsl:text>border: none;</xsl:text> </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style"> <xsl:text>border-collapse: collapse;</xsl:text> </xsl:attribute> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="../@frame='none' or (not(../@frame) and $default.table.frame='none') or local-name(.) = 'entrytbl'"> <xsl:attribute name="border">0</xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="border">1</xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:variable name="colgroup"> <colgroup> <xsl:call-template name="generate.colgroup"> <xsl:with-param name="cols" select="@cols"/> </xsl:call-template> </colgroup> </xsl:variable> <xsl:variable name="explicit.table.width"> <xsl:call-template name="pi.dbhtml_table-width"> <xsl:with-param name="node" select=".."/> </xsl:call-template> </xsl:variable> <xsl:variable name="table.width"> <xsl:choose> <xsl:when test="$explicit.table.width != ''"> <xsl:value-of select="$explicit.table.width"/> </xsl:when> <xsl:when test="$default.table.width = ''"> <xsl:text>100%</xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="$default.table.width"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:if test="$default.table.width != '' or $explicit.table.width != ''"> <xsl:attribute name="width"> <xsl:choose> <xsl:when test="contains($table.width, '%')"> <xsl:value-of select="$table.width"/> </xsl:when> <xsl:when test="$use.extensions != 0 and $tablecolumns.extension != 0"> <xsl:choose> <xsl:when test="function-available('stbl:convertLength')"> <xsl:value-of select="stbl:convertLength($table.width)"/> </xsl:when> <xsl:when test="function-available('xtbl:convertLength')"> <xsl:value-of select="xtbl:convertLength($table.width)"/> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>No convertLength function available.</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:value-of select="$table.width"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> </xsl:if> <xsl:choose> <xsl:when test="$use.extensions != 0 and $tablecolumns.extension != 0"> <xsl:choose> <xsl:when test="function-available('stbl:adjustColumnWidths')"> <xsl:copy-of select="stbl:adjustColumnWidths($colgroup)"/> </xsl:when> <xsl:when test="function-available('xtbl:adjustColumnWidths')"> <xsl:copy-of select="xtbl:adjustColumnWidths($colgroup)"/> </xsl:when> <xsl:when test="function-available('ptbl:adjustColumnWidths')"> <xsl:copy-of select="ptbl:adjustColumnWidths($colgroup)"/> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>No adjustColumnWidths function available.</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:copy-of select="$colgroup"/> </xsl:otherwise> </xsl:choose> <xsl:apply-templates select="thead"/> <xsl:apply-templates select="tfoot"/> <xsl:apply-templates select="tbody"/> <xsl:if test=".//footnote"> <tbody class="footnotes"> <tr> <td colspan="{@cols}"> <xsl:apply-templates select=".//footnote" mode="table.footnote.mode"/> </td> </tr> </tbody> </xsl:if> </table> </xsl:template> <xsl:template match="tgroup/processing-instruction('dbhtml')"> <xsl:variable name="summary"> <xsl:call-template name="pi.dbhtml_table-summary"/> </xsl:variable> <!-- Suppress the table-summary PI --> <xsl:if test="$summary = ''"> <xsl:processing-instruction name="dbhtml"> <xsl:value-of select="."/> </xsl:processing-instruction> </xsl:if> </xsl:template> <xsl:template match="colspec"></xsl:template> <xsl:template match="spanspec"></xsl:template> <xsl:template match="thead|tfoot"> <xsl:element name="{local-name(.)}"> <xsl:if test="@align"> <xsl:attribute name="align"> <xsl:value-of select="@align"/> </xsl:attribute> </xsl:if> <xsl:if test="@char"> <xsl:attribute name="char"> <xsl:value-of select="@char"/> </xsl:attribute> </xsl:if> <xsl:if test="@charoff"> <xsl:attribute name="charoff"> <xsl:value-of select="@charoff"/> </xsl:attribute> </xsl:if> <xsl:if test="@valign"> <xsl:attribute name="valign"> <xsl:value-of select="@valign"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="row[1]"> <xsl:with-param name="spans"> <xsl:call-template name="blank.spans"> <xsl:with-param name="cols" select="../@cols"/> </xsl:call-template> </xsl:with-param> </xsl:apply-templates> </xsl:element> </xsl:template> <xsl:template match="tbody"> <tbody> <xsl:if test="@align"> <xsl:attribute name="align"> <xsl:value-of select="@align"/> </xsl:attribute> </xsl:if> <xsl:if test="@char"> <xsl:attribute name="char"> <xsl:value-of select="@char"/> </xsl:attribute> </xsl:if> <xsl:if test="@charoff"> <xsl:attribute name="charoff"> <xsl:value-of select="@charoff"/> </xsl:attribute> </xsl:if> <xsl:if test="@valign"> <xsl:attribute name="valign"> <xsl:value-of select="@valign"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="row[1]"> <xsl:with-param name="spans"> <xsl:call-template name="blank.spans"> <xsl:with-param name="cols" select="../@cols"/> </xsl:call-template> </xsl:with-param> </xsl:apply-templates> </tbody> </xsl:template> <xsl:template match="row"> <xsl:param name="spans"/> <xsl:choose> <xsl:when test="contains($spans, '0')"> <xsl:call-template name="normal-row"> <xsl:with-param name="spans" select="$spans"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <!-- <xsl:message> <xsl:text>Ignoring row: </xsl:text> <xsl:value-of select="$spans"/> <xsl:text> = </xsl:text> <xsl:call-template name="consume-row"> <xsl:with-param name="spans" select="$spans"/> </xsl:call-template> </xsl:message> --> <xsl:if test="normalize-space(.//text()) != ''"> <xsl:message>Warning: overlapped row contains content!</xsl:message> </xsl:if> <tr><xsl:comment> This row intentionally left blank </xsl:comment></tr> <xsl:apply-templates select="following-sibling::row[1]"> <xsl:with-param name="spans"> <xsl:call-template name="consume-row"> <xsl:with-param name="spans" select="$spans"/> </xsl:call-template> </xsl:with-param> </xsl:apply-templates> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="normal-row"> <xsl:param name="spans"/> <xsl:variable name="row-height"> <xsl:if test="processing-instruction('dbhtml')"> <xsl:call-template name="pi.dbhtml_row-height"/> </xsl:if> </xsl:variable> <xsl:variable name="bgcolor"> <xsl:if test="processing-instruction('dbhtml')"> <xsl:call-template name="pi.dbhtml_bgcolor"/> </xsl:if> </xsl:variable> <xsl:variable name="class"> <xsl:if test="processing-instruction('dbhtml')"> <xsl:call-template name="pi.dbhtml_class"/> </xsl:if> </xsl:variable> <tr> <xsl:call-template name="tr.attributes"> <xsl:with-param name="rownum"> <xsl:number from="tgroup" count="row"/> </xsl:with-param> </xsl:call-template> <xsl:if test="$row-height != ''"> <xsl:attribute name="height"> <xsl:value-of select="$row-height"/> </xsl:attribute> </xsl:if> <xsl:if test="$bgcolor != ''"> <xsl:attribute name="bgcolor"> <xsl:value-of select="$bgcolor"/> </xsl:attribute> </xsl:if> <xsl:if test="$class != ''"> <xsl:attribute name="class"> <xsl:value-of select="$class"/> </xsl:attribute> </xsl:if> <xsl:if test="$table.borders.with.css != 0"> <xsl:if test="@rowsep = 1 and following-sibling::row"> <xsl:attribute name="style"> <xsl:call-template name="border"> <xsl:with-param name="side" select="'bottom'"/> </xsl:call-template> </xsl:attribute> </xsl:if> </xsl:if> <xsl:if test="@align"> <xsl:attribute name="align"> <xsl:value-of select="@align"/> </xsl:attribute> </xsl:if> <xsl:if test="@char"> <xsl:attribute name="char"> <xsl:value-of select="@char"/> </xsl:attribute> </xsl:if> <xsl:if test="@charoff"> <xsl:attribute name="charoff"> <xsl:value-of select="@charoff"/> </xsl:attribute> </xsl:if> <xsl:if test="@valign"> <xsl:attribute name="valign"> <xsl:value-of select="@valign"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="(entry|entrytbl)[1]"> <xsl:with-param name="spans" select="$spans"/> </xsl:apply-templates> </tr> <xsl:if test="following-sibling::row"> <xsl:variable name="nextspans"> <xsl:apply-templates select="(entry|entrytbl)[1]" mode="span"> <xsl:with-param name="spans" select="$spans"/> </xsl:apply-templates> </xsl:variable> <xsl:apply-templates select="following-sibling::row[1]"> <xsl:with-param name="spans" select="$nextspans"/> </xsl:apply-templates> </xsl:if> </xsl:template> <xsl:template match="entry|entrytbl" name="entry"> <xsl:param name="col" select="1"/> <xsl:param name="spans"/> <xsl:variable name="cellgi"> <xsl:choose> <xsl:when test="ancestor::thead">th</xsl:when> <xsl:when test="ancestor::tfoot">th</xsl:when> <xsl:otherwise>td</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="empty.cell" select="count(node()) = 0"/> <xsl:variable name="named.colnum"> <xsl:call-template name="entry.colnum"/> </xsl:variable> <xsl:variable name="entry.colnum"> <xsl:choose> <xsl:when test="$named.colnum > 0"> <xsl:value-of select="$named.colnum"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$col"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="entry.colspan"> <xsl:choose> <xsl:when test="@spanname or @namest"> <xsl:call-template name="calculate.colspan"/> </xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="following.spans"> <xsl:call-template name="calculate.following.spans"> <xsl:with-param name="colspan" select="$entry.colspan"/> <xsl:with-param name="spans" select="$spans"/> </xsl:call-template> </xsl:variable> <xsl:variable name="rowsep"> <xsl:choose> <!-- If this is the last row, rowsep never applies. --> <xsl:when test="ancestor::entrytbl and not (ancestor-or-self::row[1]/following-sibling::row)"> <xsl:value-of select="0"/> </xsl:when> <xsl:when test="not(ancestor-or-self::row[1]/following-sibling::row or ancestor-or-self::thead/following-sibling::tbody or ancestor-or-self::tbody/preceding-sibling::tfoot)"> <xsl:value-of select="0"/> </xsl:when> <xsl:when test="@morerows and not(@morerows < count(ancestor-or-self::row[1]/following-sibling::row))"> <xsl:value-of select="0"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="."/> <xsl:with-param name="colnum" select="$entry.colnum"/> <xsl:with-param name="attribute" select="'rowsep'"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="colsep"> <xsl:choose> <!-- If this is the last column, colsep never applies. --> <xsl:when test="$following.spans = ''">0</xsl:when> <xsl:otherwise> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="."/> <xsl:with-param name="colnum" select="$entry.colnum"/> <xsl:with-param name="attribute" select="'colsep'"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="valign"> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="."/> <xsl:with-param name="colnum" select="$entry.colnum"/> <xsl:with-param name="attribute" select="'valign'"/> </xsl:call-template> </xsl:variable> <xsl:variable name="align"> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="."/> <xsl:with-param name="colnum" select="$entry.colnum"/> <xsl:with-param name="attribute" select="'align'"/> </xsl:call-template> </xsl:variable> <xsl:variable name="char"> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="."/> <xsl:with-param name="colnum" select="$entry.colnum"/> <xsl:with-param name="attribute" select="'char'"/> </xsl:call-template> </xsl:variable> <xsl:variable name="charoff"> <xsl:call-template name="inherited.table.attribute"> <xsl:with-param name="entry" select="."/> <xsl:with-param name="colnum" select="$entry.colnum"/> <xsl:with-param name="attribute" select="'charoff'"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$spans != '' and not(starts-with($spans,'0:'))"> <xsl:call-template name="entry"> <xsl:with-param name="col" select="$col+1"/> <xsl:with-param name="spans" select="substring-after($spans,':')"/> </xsl:call-template> </xsl:when> <xsl:when test="number($entry.colnum) > $col"> <xsl:call-template name="empty.table.cell"/> <xsl:call-template name="entry"> <xsl:with-param name="col" select="$col+1"/> <xsl:with-param name="spans" select="substring-after($spans,':')"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:variable name="bgcolor"> <xsl:if test="processing-instruction('dbhtml')"> <xsl:call-template name="pi.dbhtml_bgcolor"/> </xsl:if> </xsl:variable> <xsl:element name="{$cellgi}"> <xsl:if test="$bgcolor != ''"> <xsl:attribute name="bgcolor"> <xsl:value-of select="$bgcolor"/> </xsl:attribute> </xsl:if> <xsl:if test="$entry.propagates.style != 0 and @role"> <xsl:apply-templates select="." mode="class.attribute"> <xsl:with-param name="class" select="@role"/> </xsl:apply-templates> </xsl:if> <xsl:if test="$show.revisionflag and @revisionflag"> <xsl:attribute name="class"> <xsl:value-of select="@revisionflag"/> </xsl:attribute> </xsl:if> <xsl:if test="$table.borders.with.css != 0"> <xsl:attribute name="style"> <xsl:if test="$colsep > 0"> <xsl:call-template name="border"> <xsl:with-param name="side" select="'right'"/> </xsl:call-template> </xsl:if> <xsl:if test="$rowsep > 0"> <xsl:call-template name="border"> <xsl:with-param name="side" select="'bottom'"/> </xsl:call-template> </xsl:if> </xsl:attribute> </xsl:if> <xsl:if test="@morerows > 0"> <xsl:attribute name="rowspan"> <xsl:value-of select="1+@morerows"/> </xsl:attribute> </xsl:if> <xsl:if test="$entry.colspan > 1"> <xsl:attribute name="colspan"> <xsl:value-of select="$entry.colspan"/> </xsl:attribute> </xsl:if> <xsl:if test="$align != ''"> <xsl:attribute name="align"> <xsl:value-of select="$align"/> </xsl:attribute> </xsl:if> <xsl:if test="$valign != ''"> <xsl:attribute name="valign"> <xsl:value-of select="$valign"/> </xsl:attribute> </xsl:if> <xsl:if test="$char != ''"> <xsl:attribute name="char"> <xsl:value-of select="$char"/> </xsl:attribute> </xsl:if> <xsl:if test="$charoff != ''"> <xsl:attribute name="charoff"> <xsl:value-of select="$charoff"/> </xsl:attribute> </xsl:if> <xsl:if test="not(preceding-sibling::*) and (ancestor::row[1]/@id or ancestor::row[1]/@xml:id)"> <xsl:call-template name="anchor"> <xsl:with-param name="node" select="ancestor::row[1]"/> </xsl:call-template> </xsl:if> <xsl:call-template name="anchor"/> <xsl:choose> <xsl:when test="$empty.cell"> <xsl:text> </xsl:text> </xsl:when> <xsl:when test="self::entrytbl"> <xsl:call-template name="tgroup"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates/> </xsl:otherwise> </xsl:choose> </xsl:element> <xsl:choose> <xsl:when test="following-sibling::entry|following-sibling::entrytbl"> <xsl:apply-templates select="(following-sibling::entry |following-sibling::entrytbl)[1]"> <xsl:with-param name="col" select="$col+$entry.colspan"/> <xsl:with-param name="spans" select="$following.spans"/> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <xsl:call-template name="finaltd"> <xsl:with-param name="spans" select="$following.spans"/> <xsl:with-param name="col" select="$col+$entry.colspan"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="entry|entrytbl" name="sentry" mode="span"> <xsl:param name="col" select="1"/> <xsl:param name="spans"/> <xsl:variable name="entry.colnum"> <xsl:call-template name="entry.colnum"/> </xsl:variable> <xsl:variable name="entry.colspan"> <xsl:choose> <xsl:when test="@spanname or @namest"> <xsl:call-template name="calculate.colspan"/> </xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="following.spans"> <xsl:call-template name="calculate.following.spans"> <xsl:with-param name="colspan" select="$entry.colspan"/> <xsl:with-param name="spans" select="$spans"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$spans != '' and not(starts-with($spans,'0:'))"> <xsl:value-of select="substring-before($spans,':')-1"/> <xsl:text>:</xsl:text> <xsl:call-template name="sentry"> <xsl:with-param name="col" select="$col+1"/> <xsl:with-param name="spans" select="substring-after($spans,':')"/> </xsl:call-template> </xsl:when> <xsl:when test="number($entry.colnum) > $col"> <xsl:text>0:</xsl:text> <xsl:call-template name="sentry"> <xsl:with-param name="col" select="$col+$entry.colspan"/> <xsl:with-param name="spans" select="$following.spans"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="copy-string"> <xsl:with-param name="count" select="$entry.colspan"/> <xsl:with-param name="string"> <xsl:choose> <xsl:when test="@morerows"> <xsl:value-of select="@morerows"/> </xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> <xsl:text>:</xsl:text> </xsl:with-param> </xsl:call-template> <xsl:choose> <xsl:when test="following-sibling::entry|following-sibling::entrytbl"> <xsl:apply-templates select="(following-sibling::entry |following-sibling::entrytbl)[1]" mode="span"> <xsl:with-param name="col" select="$col+$entry.colspan"/> <xsl:with-param name="spans" select="$following.spans"/> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <xsl:call-template name="sfinaltd"> <xsl:with-param name="spans" select="$following.spans"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="generate.colgroup"> <xsl:param name="cols" select="1"/> <xsl:param name="count" select="1"/> <xsl:choose> <xsl:when test="$count > $cols"></xsl:when> <xsl:otherwise> <xsl:call-template name="generate.col"> <xsl:with-param name="countcol" select="$count"/> </xsl:call-template> <xsl:call-template name="generate.colgroup"> <xsl:with-param name="cols" select="$cols"/> <xsl:with-param name="count" select="$count+1"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="generate.col"> <xsl:param name="countcol">1</xsl:param> <xsl:param name="colspecs" select="./colspec"/> <xsl:param name="count">1</xsl:param> <xsl:param name="colnum">1</xsl:param> <xsl:choose> <xsl:when test="$count>count($colspecs)"> <col/> </xsl:when> <xsl:otherwise> <xsl:variable name="colspec" select="$colspecs[$count=position()]"/> <xsl:variable name="colspec.colnum"> <xsl:choose> <xsl:when test="$colspec/@colnum"> <xsl:value-of select="$colspec/@colnum"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$colnum"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <xsl:when test="$colspec.colnum=$countcol"> <col> <xsl:if test="$colspec/@colwidth and $use.extensions != 0 and $tablecolumns.extension != 0"> <xsl:attribute name="width"> <xsl:choose> <xsl:when test="normalize-space($colspec/@colwidth) = '*'"> <xsl:value-of select="'1*'"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$colspec/@colwidth"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> </xsl:if> <xsl:choose> <xsl:when test="$colspec/@align"> <xsl:attribute name="align"> <xsl:value-of select="$colspec/@align"/> </xsl:attribute> </xsl:when> <!-- Suggested by Pavel ZAMPACH <zampach@nemcb.cz> --> <xsl:when test="$colspecs/ancestor::tgroup/@align"> <xsl:attribute name="align"> <xsl:value-of select="$colspecs/ancestor::tgroup/@align"/> </xsl:attribute> </xsl:when> </xsl:choose> <xsl:if test="$colspec/@char"> <xsl:attribute name="char"> <xsl:value-of select="$colspec/@char"/> </xsl:attribute> </xsl:if> <xsl:if test="$colspec/@charoff"> <xsl:attribute name="charoff"> <xsl:value-of select="$colspec/@charoff"/> </xsl:attribute> </xsl:if> </col> </xsl:when> <xsl:otherwise> <xsl:call-template name="generate.col"> <xsl:with-param name="countcol" select="$countcol"/> <xsl:with-param name="colspecs" select="$colspecs"/> <xsl:with-param name="count" select="$count+1"/> <xsl:with-param name="colnum"> <xsl:choose> <xsl:when test="$colspec/@colnum"> <xsl:value-of select="$colspec/@colnum + 1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$colnum + 1"/> </xsl:otherwise> </xsl:choose> </xsl:with-param> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="colspec.colwidth"> <!-- when this macro is called, the current context must be an entry --> <xsl:param name="colname"></xsl:param> <!-- .. = row, ../.. = thead|tbody, ../../.. = tgroup --> <xsl:param name="colspecs" select="../../../../tgroup/colspec"/> <xsl:param name="count">1</xsl:param> <xsl:choose> <xsl:when test="$count>count($colspecs)"></xsl:when> <xsl:otherwise> <xsl:variable name="colspec" select="$colspecs[$count=position()]"/> <xsl:choose> <xsl:when test="$colspec/@colname=$colname"> <xsl:value-of select="$colspec/@colwidth"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="colspec.colwidth"> <xsl:with-param name="colname" select="$colname"/> <xsl:with-param name="colspecs" select="$colspecs"/> <xsl:with-param name="count" select="$count+1"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- ====================================================================== --> <xsl:template name="tr.attributes"> <xsl:param name="row" select="."/> <xsl:param name="rownum" select="0"/> <!-- by default, do nothing. But you might want to say: <xsl:if test="$rownum mod 2 = 0"> <xsl:attribute name="class">oddrow</xsl:attribute> </xsl:if> --> </xsl:template> </xsl:stylesheet> �������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/verbatim.xsl��������������������������������������������������������0000664�0001750�0001750�00000030332�11175136613�021774� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sverb="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.Verbatim" xmlns:xverb="xalan://com.nwalsh.xalan.Verbatim" xmlns:lxslt="http://xml.apache.org/xslt" xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="sverb xverb lxslt exsl" version='1.0'> <!-- ******************************************************************** $Id: verbatim.xsl 6946 2007-07-04 10:21:57Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <xsl:include href="../highlighting/common.xsl"/> <xsl:include href="highlight.xsl"/> <lxslt:component prefix="xverb" functions="numberLines"/> <xsl:template match="programlisting|screen|synopsis"> <xsl:param name="suppress-numbers" select="'0'"/> <xsl:variable name="id"> <xsl:call-template name="object.id"/> </xsl:variable> <xsl:call-template name="anchor"/> <xsl:if test="$shade.verbatim != 0"> <xsl:message> <xsl:text>The shade.verbatim parameter is deprecated. </xsl:text> <xsl:text>Use CSS instead,</xsl:text> </xsl:message> <xsl:message> <xsl:text>for example: pre.</xsl:text> <xsl:value-of select="local-name(.)"/> <xsl:text> { background-color: #E0E0E0; }</xsl:text> </xsl:message> </xsl:if> <xsl:choose> <xsl:when test="$suppress-numbers = '0' and @linenumbering = 'numbered' and $use.extensions != '0' and $linenumbering.extension != '0'"> <xsl:variable name="rtf"> <xsl:call-template name="apply-highlighting"/> </xsl:variable> <pre> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="number.rtf.lines"> <xsl:with-param name="rtf" select="$rtf"/> </xsl:call-template> </pre> </xsl:when> <xsl:otherwise> <pre> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="apply-highlighting"/> </pre> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="literallayout"> <xsl:param name="suppress-numbers" select="'0'"/> <xsl:variable name="rtf"> <xsl:apply-templates/> </xsl:variable> <xsl:if test="$shade.verbatim != 0 and @class='monospaced'"> <xsl:message> <xsl:text>The shade.verbatim parameter is deprecated. </xsl:text> <xsl:text>Use CSS instead,</xsl:text> </xsl:message> <xsl:message> <xsl:text>for example: pre.</xsl:text> <xsl:value-of select="local-name(.)"/> <xsl:text> { background-color: #E0E0E0; }</xsl:text> </xsl:message> </xsl:if> <xsl:choose> <xsl:when test="$suppress-numbers = '0' and @linenumbering = 'numbered' and $use.extensions != '0' and $linenumbering.extension != '0'"> <xsl:choose> <xsl:when test="@class='monospaced'"> <pre> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="number.rtf.lines"> <xsl:with-param name="rtf" select="$rtf"/> </xsl:call-template> </pre> </xsl:when> <xsl:otherwise> <div> <xsl:apply-templates select="." mode="class.attribute"/> <p> <xsl:call-template name="number.rtf.lines"> <xsl:with-param name="rtf" select="$rtf"/> </xsl:call-template> </p> </div> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:choose> <xsl:when test="@class='monospaced'"> <pre> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:copy-of select="$rtf"/> </pre> </xsl:when> <xsl:otherwise> <div> <xsl:apply-templates select="." mode="class.attribute"/> <p> <xsl:call-template name="make-verbatim"> <xsl:with-param name="rtf" select="$rtf"/> </xsl:call-template> </p> </div> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="address"> <xsl:param name="suppress-numbers" select="'0'"/> <xsl:variable name="rtf"> <xsl:apply-templates/> </xsl:variable> <xsl:choose> <xsl:when test="$suppress-numbers = '0' and @linenumbering = 'numbered' and $use.extensions != '0' and $linenumbering.extension != '0'"> <div> <xsl:apply-templates select="." mode="class.attribute"/> <p> <xsl:call-template name="number.rtf.lines"> <xsl:with-param name="rtf" select="$rtf"/> </xsl:call-template> </p> </div> </xsl:when> <xsl:otherwise> <div> <xsl:apply-templates select="." mode="class.attribute"/> <p> <xsl:call-template name="make-verbatim"> <xsl:with-param name="rtf" select="$rtf"/> </xsl:call-template> </p> </div> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="number.rtf.lines"> <xsl:param name="rtf" select="''"/> <xsl:param name="pi.context" select="."/> <!-- Save the global values --> <xsl:variable name="global.linenumbering.everyNth" select="$linenumbering.everyNth"/> <xsl:variable name="global.linenumbering.separator" select="$linenumbering.separator"/> <xsl:variable name="global.linenumbering.width" select="$linenumbering.width"/> <!-- Extract the <?dbhtml linenumbering.*?> PI values --> <xsl:variable name="pi.linenumbering.everyNth"> <xsl:call-template name="pi.dbhtml_linenumbering.everyNth"> <xsl:with-param name="node" select="$pi.context"/> </xsl:call-template> </xsl:variable> <xsl:variable name="pi.linenumbering.separator"> <xsl:call-template name="pi.dbhtml_linenumbering.separator"> <xsl:with-param name="node" select="$pi.context"/> </xsl:call-template> </xsl:variable> <xsl:variable name="pi.linenumbering.width"> <xsl:call-template name="pi.dbhtml_linenumbering.width"> <xsl:with-param name="node" select="$pi.context"/> </xsl:call-template> </xsl:variable> <!-- Construct the 'in-context' values --> <xsl:variable name="linenumbering.everyNth"> <xsl:choose> <xsl:when test="$pi.linenumbering.everyNth != ''"> <xsl:value-of select="$pi.linenumbering.everyNth"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$global.linenumbering.everyNth"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="linenumbering.separator"> <xsl:choose> <xsl:when test="$pi.linenumbering.separator != ''"> <xsl:value-of select="$pi.linenumbering.separator"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$global.linenumbering.separator"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="linenumbering.width"> <xsl:choose> <xsl:when test="$pi.linenumbering.width != ''"> <xsl:value-of select="$pi.linenumbering.width"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$global.linenumbering.width"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="linenumbering.startinglinenumber"> <xsl:choose> <xsl:when test="$pi.context/@startinglinenumber"> <xsl:value-of select="$pi.context/@startinglinenumber"/> </xsl:when> <xsl:when test="$pi.context/@continuation='continues'"> <xsl:variable name="lastLine"> <xsl:choose> <xsl:when test="$pi.context/self::programlisting"> <xsl:call-template name="lastLineNumber"> <xsl:with-param name="listings" select="preceding::programlisting[@linenumbering='numbered']"/> </xsl:call-template> </xsl:when> <xsl:when test="$pi.context/self::screen"> <xsl:call-template name="lastLineNumber"> <xsl:with-param name="listings" select="preceding::screen[@linenumbering='numbered']"/> </xsl:call-template> </xsl:when> <xsl:when test="$pi.context/self::literallayout"> <xsl:call-template name="lastLineNumber"> <xsl:with-param name="listings" select="preceding::literallayout[@linenumbering='numbered']"/> </xsl:call-template> </xsl:when> <xsl:when test="$pi.context/self::address"> <xsl:call-template name="lastLineNumber"> <xsl:with-param name="listings" select="preceding::address[@linenumbering='numbered']"/> </xsl:call-template> </xsl:when> <xsl:when test="$pi.context/self::synopsis"> <xsl:call-template name="lastLineNumber"> <xsl:with-param name="listings" select="preceding::synopsis[@linenumbering='numbered']"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:message> <xsl:text>Unexpected verbatim environment: </xsl:text> <xsl:value-of select="local-name($pi.context)"/> </xsl:message> <xsl:value-of select="0"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:value-of select="$lastLine + 1"/> </xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <xsl:when test="function-available('sverb:numberLines')"> <xsl:copy-of select="sverb:numberLines($rtf)"/> </xsl:when> <xsl:when test="function-available('xverb:numberLines')"> <xsl:copy-of select="xverb:numberLines($rtf)"/> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>No numberLines function available.</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="make-verbatim"> <xsl:param name="rtf"/> <!-- I want to make this RTF verbatim. There are two possibilities: either I have access to the exsl:node-set extension function and I can "do it right" or I have to rely on CSS. --> <xsl:choose> <xsl:when test="function-available('exsl:node-set')"> <xsl:apply-templates select="exsl:node-set($rtf)" mode="make.verbatim.mode"/> </xsl:when> <xsl:otherwise> <span style="white-space: pre;"> <xsl:copy-of select="$rtf"/> </span> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- ======================================================================== --> <xsl:template name="lastLineNumber"> <xsl:param name="listings"/> <xsl:param name="number" select="0"/> <xsl:variable name="lines"> <xsl:call-template name="countLines"> <xsl:with-param name="listing" select="string($listings[1])"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="not($listings)"> <xsl:value-of select="$number"/> </xsl:when> <xsl:when test="$listings[1]/@startinglinenumber"> <xsl:value-of select="$number + $listings[1]/@startinglinenumber + $lines - 1"/> </xsl:when> <xsl:when test="$listings[1]/@continuation='continues'"> <xsl:call-template name="lastLineNumber"> <xsl:with-param name="listings" select="listings[position() > 1]"/> <xsl:with-param name="number" select="$number + $lines"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$lines"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="countLines"> <xsl:param name="listing"/> <xsl:param name="count" select="1"/> <xsl:choose> <xsl:when test="contains($listing, ' ')"> <xsl:call-template name="countLines"> <xsl:with-param name="listing" select="substring-after($listing, ' ')"/> <xsl:with-param name="count" select="$count + 1"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$count"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/refentry.xsl��������������������������������������������������������0000664�0001750�0001750�00000022577�11175136613�022035� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> <!-- ******************************************************************** $Id: refentry.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:template match="reference"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:if test="$generate.id.attributes != 0"> <xsl:attribute name="id"> <xsl:call-template name="object.id"/> </xsl:attribute> </xsl:if> <xsl:call-template name="reference.titlepage"/> <xsl:variable name="toc.params"> <xsl:call-template name="find.path.params"> <xsl:with-param name="table" select="normalize-space($generate.toc)"/> </xsl:call-template> </xsl:variable> <xsl:if test="not(partintro) and contains($toc.params, 'toc')"> <xsl:call-template name="division.toc"/> </xsl:if> <xsl:apply-templates/> </div> </xsl:template> <xsl:template match="reference" mode="division.number"> <xsl:number from="book" count="reference" format="I."/> </xsl:template> <xsl:template match="reference/docinfo"></xsl:template> <xsl:template match="reference/referenceinfo"></xsl:template> <xsl:template match="reference/title"></xsl:template> <xsl:template match="reference/subtitle"></xsl:template> <xsl:template match="reference/titleabbrev"></xsl:template> <!-- ==================================================================== --> <xsl:template name="refentry.title"> <xsl:param name="node" select="."/> <xsl:variable name="refmeta" select="$node//refmeta"/> <xsl:variable name="refentrytitle" select="$refmeta//refentrytitle"/> <xsl:variable name="refnamediv" select="$node//refnamediv"/> <xsl:variable name="refname" select="$refnamediv//refname"/> <xsl:variable name="refdesc" select="$refnamediv//refdescriptor"/> <xsl:variable name="title"> <xsl:choose> <xsl:when test="$refentrytitle"> <xsl:apply-templates select="$refentrytitle[1]" mode="title"/> </xsl:when> <xsl:when test="$refdesc"> <xsl:apply-templates select="$refdesc[1]" mode="title"/> </xsl:when> <xsl:when test="$refname"> <xsl:apply-templates select="$refname[1]" mode="title"/> </xsl:when> <xsl:otherwise></xsl:otherwise> </xsl:choose> </xsl:variable> <h1 class="title"> <xsl:copy-of select="$title"/> </h1> </xsl:template> <xsl:template match="refentry"> <xsl:call-template name="id.warning"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:if test="$refentry.separator != 0 and preceding-sibling::refentry"> <div class="refentry.separator"> <hr/> </div> </xsl:if> <xsl:call-template name="anchor"> <xsl:with-param name="conditional" select="0"/> </xsl:call-template> <xsl:call-template name="refentry.titlepage"/> <xsl:apply-templates/> <xsl:call-template name="process.footnotes"/> </div> </xsl:template> <xsl:template match="refentry/docinfo|refentry/refentryinfo"></xsl:template> <xsl:template match="refentry/info"></xsl:template> <xsl:template match="refentrytitle|refname|refdescriptor" mode="title"> <xsl:apply-templates/> </xsl:template> <xsl:template match="refmeta"> </xsl:template> <xsl:template match="manvolnum"> <xsl:if test="$refentry.xref.manvolnum != 0"> <xsl:text>(</xsl:text> <xsl:apply-templates/> <xsl:text>)</xsl:text> </xsl:if> </xsl:template> <xsl:template match="refmiscinfo"> </xsl:template> <xsl:template match="refentrytitle"> <xsl:call-template name="inline.charseq"/> </xsl:template> <xsl:template match="refnamediv"> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="anchor"/> <xsl:choose> <xsl:when test="preceding-sibling::refnamediv"> <!-- no title on secondary refnamedivs! --> </xsl:when> <xsl:when test="$refentry.generate.name != 0"> <h2> <xsl:call-template name="gentext"> <xsl:with-param name="key" select="'RefName'"/> </xsl:call-template> </h2> </xsl:when> <xsl:when test="$refentry.generate.title != 0"> <h2> <xsl:choose> <xsl:when test="../refmeta/refentrytitle"> <xsl:apply-templates select="../refmeta/refentrytitle"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="refname[1]"/> </xsl:otherwise> </xsl:choose> </h2> </xsl:when> </xsl:choose> <p> <xsl:apply-templates/> </p> </div> </xsl:template> <xsl:template match="refname"> <xsl:if test="not(preceding-sibling::refdescriptor)"> <xsl:apply-templates/> <xsl:if test="following-sibling::refname"> <xsl:text>, </xsl:text> </xsl:if> </xsl:if> </xsl:template> <xsl:template match="refpurpose"> <xsl:if test="node()"> <xsl:text> </xsl:text> <xsl:call-template name="dingbat"> <xsl:with-param name="dingbat">em-dash</xsl:with-param> </xsl:call-template> <xsl:text> </xsl:text> <xsl:apply-templates/> </xsl:if> </xsl:template> <xsl:template match="refdescriptor"> <xsl:apply-templates/> </xsl:template> <xsl:template match="refclass"> <xsl:if test="$refclass.suppress = 0"> <p> <b> <xsl:if test="@role"> <xsl:value-of select="@role"/> <xsl:text>: </xsl:text> </xsl:if> <xsl:apply-templates/> </b> </p> </xsl:if> </xsl:template> <xsl:template match="refsynopsisdiv"> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="anchor"/> <h2> <xsl:choose> <xsl:when test="refsynopsisdiv/title|title"> <xsl:apply-templates select="(refsynopsisdiv/title|title)[1]" mode="titlepage.mode"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="gentext"> <xsl:with-param name="key" select="'RefSynopsisDiv'"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </h2> <xsl:apply-templates/> </div> </xsl:template> <xsl:template match="refsynopsisdivinfo"></xsl:template> <xsl:template match="refsynopsisdiv/title"> </xsl:template> <xsl:template match="refsynopsisdiv/title" mode="titlepage.mode"> <xsl:apply-templates/> </xsl:template> <xsl:template match="refsection|refsect1|refsect2|refsect3"> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="dir"> <xsl:with-param name="inherit" select="1"/> </xsl:call-template> <xsl:call-template name="language.attribute"/> <xsl:call-template name="anchor"> <xsl:with-param name="conditional" select="0"/> </xsl:call-template> <!-- pick up info title --> <xsl:apply-templates select="(title|info/title)[1]"/> <xsl:apply-templates select="node()[not(self::title) and not(self::info)]"/> </div> </xsl:template> <xsl:template match="refsection/title|refsection/info/title"> <!-- the ID is output in the block.object call for refsect1 --> <xsl:variable name="level" select="count(ancestor-or-self::refsection)"/> <xsl:variable name="refsynopsisdiv"> <xsl:text>0</xsl:text> <xsl:if test="ancestor::refsynopsisdiv">1</xsl:if> </xsl:variable> <xsl:variable name="hlevel"> <xsl:choose> <xsl:when test="$level+$refsynopsisdiv > 5">6</xsl:when> <xsl:otherwise> <xsl:value-of select="$level+1+$refsynopsisdiv"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:element name="h{$hlevel}"> <xsl:apply-templates/> </xsl:element> </xsl:template> <xsl:template match="refsect1/title|refsect1/info/title"> <!-- the ID is output in the block.object call for refsect1 --> <h2> <xsl:apply-templates/> </h2> </xsl:template> <xsl:template match="refsect2/title|refsect2/info/title"> <!-- the ID is output in the block.object call for refsect2 --> <h3> <xsl:apply-templates/> </h3> </xsl:template> <xsl:template match="refsect3/title|refsect3/info/title"> <!-- the ID is output in the block.object call for refsect3 --> <h4> <xsl:apply-templates/> </h4> </xsl:template> <xsl:template match="refsectioninfo|refsection/info"></xsl:template> <xsl:template match="refsect1info|refsect1/info"></xsl:template> <xsl:template match="refsect2info|refsect2/info"></xsl:template> <xsl:template match="refsect3info|refsect3/info"></xsl:template> <!-- ==================================================================== --> </xsl:stylesheet> ���������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/onechunk.xsl��������������������������������������������������������0000664�0001750�0001750�00000002660�11175136613�022000� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:doc="http://nwalsh.com/xsl/documentation/1.0" version="1.0" exclude-result-prefixes="doc"> <!-- ******************************************************************** $Id: onechunk.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:import href="chunk.xsl"/> <!-- Ok, using the onechunk parameter makes this all work again. --> <!-- It does have the disadvantage that it only works for documents that have --> <!-- a root element that is considered a chunk by the chunk.xsl stylesheet. --> <!-- Ideally, onechunk would let anything be a chunk. But not today. --> <xsl:param name="onechunk" select="1"/> <xsl:param name="suppress.navigation">1</xsl:param> <xsl:template name="href.target.uri"> <xsl:param name="object" select="."/> <xsl:text>#</xsl:text> <xsl:call-template name="object.id"> <xsl:with-param name="object" select="$object"/> </xsl:call-template> </xsl:template> </xsl:stylesheet> ��������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/task.xsl������������������������������������������������������������0000664�0001750�0001750�00000004461�11175136613�021131� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <!-- ******************************************************************** $Id: task.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:template match="task"> <xsl:variable name="param.placement" select="substring-after(normalize-space($formal.title.placement), concat(local-name(.), ' '))"/> <xsl:variable name="placement"> <xsl:choose> <xsl:when test="contains($param.placement, ' ')"> <xsl:value-of select="substring-before($param.placement, ' ')"/> </xsl:when> <xsl:when test="$param.placement = ''">before</xsl:when> <xsl:otherwise> <xsl:value-of select="$param.placement"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="preamble" select="*[not(self::title or self::titleabbrev)]"/> <div> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:call-template name="anchor"/> <xsl:if test="title and $placement = 'before'"> <xsl:call-template name="formal.object.heading"/> </xsl:if> <xsl:apply-templates select="$preamble"/> <xsl:if test="title and $placement != 'before'"> <xsl:call-template name="formal.object.heading"/> </xsl:if> </div> </xsl:template> <xsl:template match="task/title"> <!-- nop --> </xsl:template> <xsl:template match="tasksummary"> <xsl:call-template name="semiformal.object"/> </xsl:template> <xsl:template match="tasksummary/title"/> <xsl:template match="taskprerequisites"> <xsl:call-template name="semiformal.object"/> </xsl:template> <xsl:template match="taskprerequisites/title"/> <xsl:template match="taskrelated"> <xsl:call-template name="semiformal.object"/> </xsl:template> <xsl:template match="taskrelated/title"/> </xsl:stylesheet> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/maketoc.xsl���������������������������������������������������������0000664�0001750�0001750�00000005273�11175136613�021614� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:doc="http://nwalsh.com/xsl/documentation/1.0" version="1.0" exclude-result-prefixes="doc"> <!-- ******************************************************************** $Id: maketoc.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <xsl:import href="docbook.xsl"/> <xsl:import href="chunk.xsl"/> <xsl:output method="xml" indent="no" encoding='utf-8'/> <xsl:param name="toc.list.type" select="'tocentry'"/> <!-- refentry in autotoc.xsl does not use subtoc, so must handle it explicitly here. --> <xsl:template match="refentry" mode="toc"> <xsl:param name="toc-context" select="."/> <xsl:call-template name="subtoc"> <xsl:with-param name="toc-context" select="$toc-context"/> </xsl:call-template> </xsl:template> <xsl:template name="subtoc"> <xsl:param name="nodes" select="NOT-AN-ELEMENT"/> <xsl:variable name="filename"> <xsl:apply-templates select="." mode="chunk-filename"/> </xsl:variable> <xsl:variable name="chunk"> <xsl:call-template name="chunk"/> </xsl:variable> <xsl:if test="$chunk != 0"> <xsl:call-template name="indent-spaces"/> <xsl:variable name="id"> <xsl:call-template name="object.id"/> </xsl:variable> <tocentry linkend="{$id}"> <xsl:processing-instruction name="dbhtml"> <xsl:text>filename="</xsl:text> <xsl:value-of select="$filename"/> <xsl:text>"</xsl:text> </xsl:processing-instruction> <xsl:text> </xsl:text> <xsl:apply-templates mode="toc" select="$nodes"/> <xsl:call-template name="indent-spaces"/> </tocentry> <xsl:text> </xsl:text> </xsl:if> </xsl:template> <xsl:template name="indent-spaces"> <xsl:param name="node" select="."/> <xsl:text> </xsl:text> <xsl:if test="$node/parent::*"> <xsl:call-template name="indent-spaces"> <xsl:with-param name="node" select="$node/parent::*"/> </xsl:call-template> </xsl:if> </xsl:template> <!-- ==================================================================== --> <xsl:template match="/" priority="-1"> <xsl:text> </xsl:text> <toc role="chunk-toc"> <xsl:text> </xsl:text> <xsl:apply-templates select="/" mode="toc"/> </toc> <xsl:text> </xsl:text> </xsl:template> </xsl:stylesheet> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������boxbackup/docs/xsl-generic/html/autoidx.xsl���������������������������������������������������������0000664�0001750�0001750�00000061775�11175136613�021657� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0"?> <!DOCTYPE xsl:stylesheet [ <!ENTITY % common.entities SYSTEM "../common/entities.ent"> %common.entities; ]> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" extension-element-prefixes="exslt" exclude-result-prefixes="exslt" version="1.0"> <!-- ******************************************************************** $Id: autoidx.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <!-- ==================================================================== --> <!-- The "basic" method derived from Jeni Tennison's work. --> <!-- The "kosek" method contributed by Jirka Kosek. --> <!-- The "kimber" method contributed by Eliot Kimber of Innodata Isogen. --> <xsl:variable name="kimber.imported" select="0"/> <xsl:variable name="kosek.imported" select="0"/> <xsl:key name="letter" match="indexterm" use="translate(substring(&primary;, 1, 1),&lowercase;,&uppercase;)"/> <xsl:key name="primary" match="indexterm" use="&primary;"/> <xsl:key name="secondary" match="indexterm" use="concat(&primary;, &sep;, &secondary;)"/> <xsl:key name="tertiary" match="indexterm" use="concat(&primary;, &sep;, &secondary;, &sep;, &tertiary;)"/> <xsl:key name="endofrange" match="indexterm[@class='endofrange']" use="@startref"/> <xsl:key name="primary-section" match="indexterm[not(secondary) and not(see)]" use="concat(&primary;, &sep;, §ion.id;)"/> <xsl:key name="secondary-section" match="indexterm[not(tertiary) and not(see)]" use="concat(&primary;, &sep;, &secondary;, &sep;, §ion.id;)"/> <xsl:key name="tertiary-section" match="indexterm[not(see)]" use="concat(&primary;, &sep;, &secondary;, &sep;, &tertiary;, &sep;, §ion.id;)"/> <xsl:key name="see-also" match="indexterm[seealso]" use="concat(&primary;, &sep;, &secondary;, &sep;, &tertiary;, &sep;, seealso)"/> <xsl:key name="see" match="indexterm[see]" use="concat(&primary;, &sep;, &secondary;, &sep;, &tertiary;, &sep;, see)"/> <xsl:key name="sections" match="*[@id or @xml:id]" use="@id|@xml:id"/> <xsl:template name="generate-index"> <xsl:param name="scope" select="(ancestor::book|/)[last()]"/> <xsl:choose> <xsl:when test="$index.method = 'kosek'"> <xsl:call-template name="generate-kosek-index"> <xsl:with-param name="scope" select="$scope"/> </xsl:call-template> </xsl:when> <xsl:when test="$index.method = 'kimber'"> <xsl:call-template name="generate-kimber-index"> <xsl:with-param name="scope" select="$scope"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="generate-basic-index"> <xsl:with-param name="scope" select="$scope"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="generate-basic-index"> <xsl:param name="scope" select="NOTANODE"/> <xsl:variable name="role"> <xsl:if test="$index.on.role != 0"> <xsl:value-of select="@role"/> </xsl:if> </xsl:variable> <xsl:variable name="type"> <xsl:if test="$index.on.type != 0"> <xsl:value-of select="@type"/> </xsl:if> </xsl:variable> <xsl:variable name="terms" select="//indexterm [count(.|key('letter', translate(substring(&primary;, 1, 1), &lowercase;, &uppercase;)) [&scope;][1]) = 1 and not(@class = 'endofrange')]"/> <xsl:variable name="alphabetical" select="$terms[contains(concat(&lowercase;, &uppercase;), substring(&primary;, 1, 1))]"/> <xsl:variable name="others" select="$terms[not(contains(concat(&lowercase;, &uppercase;), substring(&primary;, 1, 1)))]"/> <div class="index"> <xsl:if test="$others"> <div class="indexdiv"> <h3> <xsl:call-template name="gentext"> <xsl:with-param name="key" select="'index symbols'"/> </xsl:call-template> </h3> <dl> <xsl:apply-templates select="$others[count(.|key('primary', &primary;)[&scope;][1]) = 1]" mode="index-symbol-div"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(&primary;, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </dl> </div> </xsl:if> <xsl:apply-templates select="$alphabetical[count(.|key('letter', translate(substring(&primary;, 1, 1), &lowercase;,&uppercase;))[&scope;][1]) = 1]" mode="index-div-basic"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(&primary;, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </div> </xsl:template> <!-- This template not used if html/autoidx-kosek.xsl is imported --> <xsl:template name="generate-kosek-index"> <xsl:param name="scope" select="NOTANODE"/> <xsl:variable name="vendor" select="system-property('xsl:vendor')"/> <xsl:if test="contains($vendor, 'libxslt')"> <xsl:message terminate="yes"> <xsl:text>ERROR: the 'kosek' index method does not </xsl:text> <xsl:text>work with the xsltproc XSLT processor.</xsl:text> </xsl:message> </xsl:if> <xsl:if test="not(function-available('exslt:node-set') or function-available('exslt:nodeSet'))"> <xsl:message terminate="yes"> <xsl:text>ERROR: the 'kosek' index method requires the </xsl:text> <xsl:text>exslt:node-set() function. Use a processor that </xsl:text> <xsl:text>has it, or use a different index method.</xsl:text> </xsl:message> </xsl:if> <xsl:if test="$kosek.imported = 0"> <xsl:message terminate="yes"> <xsl:text>ERROR: the 'kosek' index method requires the </xsl:text> <xsl:text>kosek index extensions be imported: </xsl:text> <xsl:text> xsl:import href="html/autoidx-kosek.xsl"</xsl:text> </xsl:message> </xsl:if> </xsl:template> <!-- This template not used if html/autoidx-kimber.xsl is imported --> <xsl:template name="generate-kimber-index"> <xsl:param name="scope" select="NOTANODE"/> <xsl:variable name="vendor" select="system-property('xsl:vendor')"/> <xsl:if test="not(contains($vendor, 'SAXON '))"> <xsl:message terminate="yes"> <xsl:text>ERROR: the 'kimber' index method requires the </xsl:text> <xsl:text>Saxon version 6 or 8 XSLT processor.</xsl:text> </xsl:message> </xsl:if> <xsl:if test="$kimber.imported = 0"> <xsl:message terminate="yes"> <xsl:text>ERROR: the 'kimber' index method requires the </xsl:text> <xsl:text>kimber index extensions be imported: </xsl:text> <xsl:text> xsl:import href="html/autoidx-kimber.xsl"</xsl:text> </xsl:message> </xsl:if> </xsl:template> <xsl:template match="indexterm" mode="index-div-basic"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:variable name="key" select="translate(substring(&primary;, 1, 1), &lowercase;,&uppercase;)"/> <xsl:if test="key('letter', $key)[&scope;] [count(.|key('primary', &primary;)[&scope;][1]) = 1]"> <div class="indexdiv"> <xsl:if test="contains(concat(&lowercase;, &uppercase;), $key)"> <h3> <xsl:value-of select="translate($key, &lowercase;, &uppercase;)"/> </h3> </xsl:if> <dl> <xsl:apply-templates select="key('letter', $key)[&scope;] [count(.|key('primary', &primary;) [&scope;][1])=1]" mode="index-primary"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(&primary;, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </dl> </div> </xsl:if> </xsl:template> <xsl:template match="indexterm" mode="index-symbol-div"> <xsl:param name="scope" select="/"/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:variable name="key" select="translate(substring(&primary;, 1, 1), &lowercase;,&uppercase;)"/> <xsl:apply-templates select="key('letter', $key) [&scope;][count(.|key('primary', &primary;)[1]) = 1]" mode="index-primary"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(&primary;, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </xsl:template> <xsl:template match="indexterm" mode="index-primary"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:variable name="key" select="&primary;"/> <xsl:variable name="refs" select="key('primary', $key)[&scope;]"/> <dt> <xsl:value-of select="primary"/> <xsl:for-each select="$refs[generate-id() = generate-id(key('primary-section', concat($key, &sep;, §ion.id;))[&scope;][1])]"> <xsl:apply-templates select="." mode="reference"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> </xsl:apply-templates> </xsl:for-each> <xsl:if test="$refs[not(secondary)]/*[self::see]"> <xsl:apply-templates select="$refs[generate-id() = generate-id(key('see', concat(&primary;, &sep;, &sep;, &sep;, see))[&scope;][1])]" mode="index-see"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(see, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </xsl:if> </dt> <xsl:if test="$refs/secondary or $refs[not(secondary)]/*[self::seealso]"> <dd> <dl> <xsl:apply-templates select="$refs[generate-id() = generate-id(key('see-also', concat(&primary;, &sep;, &sep;, &sep;, seealso))[&scope;][1])]" mode="index-seealso"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(seealso, &lowercase;, &uppercase;)"/> </xsl:apply-templates> <xsl:apply-templates select="$refs[secondary and count(.|key('secondary', concat($key, &sep;, &secondary;))[&scope;][1]) = 1]" mode="index-secondary"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(&secondary;, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </dl> </dd> </xsl:if> </xsl:template> <xsl:template match="indexterm" mode="index-secondary"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:variable name="key" select="concat(&primary;, &sep;, &secondary;)"/> <xsl:variable name="refs" select="key('secondary', $key)[&scope;]"/> <dt> <xsl:value-of select="secondary"/> <xsl:for-each select="$refs[generate-id() = generate-id(key('secondary-section', concat($key, &sep;, §ion.id;))[&scope;][1])]"> <xsl:apply-templates select="." mode="reference"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> </xsl:apply-templates> </xsl:for-each> <xsl:if test="$refs[not(tertiary)]/*[self::see]"> <xsl:apply-templates select="$refs[generate-id() = generate-id(key('see', concat(&primary;, &sep;, &secondary;, &sep;, &sep;, see))[&scope;][1])]" mode="index-see"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(see, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </xsl:if> </dt> <xsl:if test="$refs/tertiary or $refs[not(tertiary)]/*[self::seealso]"> <dd> <dl> <xsl:apply-templates select="$refs[generate-id() = generate-id(key('see-also', concat(&primary;, &sep;, &secondary;, &sep;, &sep;, seealso))[&scope;][1])]" mode="index-seealso"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(seealso, &lowercase;, &uppercase;)"/> </xsl:apply-templates> <xsl:apply-templates select="$refs[tertiary and count(.|key('tertiary', concat($key, &sep;, &tertiary;))[&scope;][1]) = 1]" mode="index-tertiary"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(&tertiary;, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </dl> </dd> </xsl:if> </xsl:template> <xsl:template match="indexterm" mode="index-tertiary"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:variable name="key" select="concat(&primary;, &sep;, &secondary;, &sep;, &tertiary;)"/> <xsl:variable name="refs" select="key('tertiary', $key)[&scope;]"/> <dt> <xsl:value-of select="tertiary"/> <xsl:for-each select="$refs[generate-id() = generate-id(key('tertiary-section', concat($key, &sep;, §ion.id;))[&scope;][1])]"> <xsl:apply-templates select="." mode="reference"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> </xsl:apply-templates> </xsl:for-each> <xsl:if test="$refs/see"> <xsl:apply-templates select="$refs[generate-id() = generate-id(key('see', concat(&primary;, &sep;, &secondary;, &sep;, &tertiary;, &sep;, see))[&scope;][1])]" mode="index-see"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(see, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </xsl:if> </dt> <xsl:if test="$refs/seealso"> <dd> <dl> <xsl:apply-templates select="$refs[generate-id() = generate-id(key('see-also', concat(&primary;, &sep;, &secondary;, &sep;, &tertiary;, &sep;, seealso))[&scope;][1])]" mode="index-seealso"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:sort select="translate(seealso, &lowercase;, &uppercase;)"/> </xsl:apply-templates> </dl> </dd> </xsl:if> </xsl:template> <xsl:template match="indexterm" mode="reference"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:param name="position"/> <xsl:variable name="term.separator"> <xsl:call-template name="index.separator"> <xsl:with-param name="key" select="'index.term.separator'"/> </xsl:call-template> </xsl:variable> <xsl:variable name="number.separator"> <xsl:call-template name="index.separator"> <xsl:with-param name="key" select="'index.number.separator'"/> </xsl:call-template> </xsl:variable> <xsl:variable name="range.separator"> <xsl:call-template name="index.separator"> <xsl:with-param name="key" select="'index.range.separator'"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$position = 1"> <xsl:value-of select="$term.separator"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$number.separator"/> </xsl:otherwise> </xsl:choose> <xsl:choose> <xsl:when test="@zone and string(@zone)"> <xsl:call-template name="reference"> <xsl:with-param name="zones" select="normalize-space(@zone)"/> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:variable name="title"> <xsl:choose> <xsl:when test="§ion;/titleabbrev and $index.prefer.titleabbrev != 0"> <xsl:apply-templates select="§ion;" mode="titleabbrev.markup"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="§ion;" mode="title.markup"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="§ion;"/> <xsl:with-param name="context" select="//index[&scope;][1]"/> </xsl:call-template> </xsl:attribute> <xsl:value-of select="$title"/> <!-- text only --> </a> <xsl:variable name="id" select="(@id|@xml:id)[1]"/> <xsl:if test="key('endofrange', $id)[&scope;]"> <xsl:apply-templates select="key('endofrange', $id)[&scope;][last()]" mode="reference"> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> <xsl:with-param name="separator" select="$range.separator"/> </xsl:apply-templates> </xsl:if> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="reference"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:param name="zones"/> <xsl:choose> <xsl:when test="contains($zones, ' ')"> <xsl:variable name="zone" select="substring-before($zones, ' ')"/> <xsl:variable name="target" select="key('sections', $zone)"/> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target[1]"/> <xsl:with-param name="context" select="//index[&scope;][1]"/> </xsl:call-template> </xsl:attribute> <xsl:apply-templates select="$target[1]" mode="index-title-content"/> </a> <xsl:text>, </xsl:text> <xsl:call-template name="reference"> <xsl:with-param name="zones" select="substring-after($zones, ' ')"/> <xsl:with-param name="position" select="position()"/> <xsl:with-param name="scope" select="$scope"/> <xsl:with-param name="role" select="$role"/> <xsl:with-param name="type" select="$type"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:variable name="zone" select="$zones"/> <xsl:variable name="target" select="key('sections', $zone)"/> <a> <xsl:apply-templates select="." mode="class.attribute"/> <xsl:attribute name="href"> <xsl:call-template name="href.target"> <xsl:with-param name="object" select="$target[1]"/> <xsl:with-param name="context" select="//index[&scope;][1]"/> </xsl:call-template> </xsl:attribute> <xsl:apply-templates select="$target[1]" mode="index-title-content"/> </a> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="indexterm" mode="index-see"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:text> (</xsl:text> <xsl:call-template name="gentext"> <xsl:with-param name="key" select="'see'"/> </xsl:call-template> <xsl:text> </xsl:text> <xsl:value-of select="see"/> <xsl:text>)</xsl:text> </xsl:template> <xsl:template match="indexterm" mode="index-seealso"> <xsl:param name="scope" select="."/> <xsl:param name="role" select="''"/> <xsl:param name="type" select="''"/> <xsl:for-each select="seealso"> <xsl:sort select="translate(., &lowercase;, &uppercase;)"/> <dt> <xsl:text>(</xsl:text> <xsl:call-template name="gentext"> <xsl:with-param name="key" select="'seealso'"/> </xsl:call-template> <xsl:text> </xsl:text> <xsl:value-of select="."/> <xsl:text>)</xsl:text> </dt> </xsl:for-each> </xsl:template> <xsl:template match="*" mode="index-title-content"> <xsl:variable name="title"> <xsl:apply-templates select="§ion;" mode="title.markup"/> </xsl:variable> <xsl:value-of select="$title"/> </xsl:template> <xsl:template name="index.separator"> <xsl:param name="key" select="''"/> <xsl:param name="lang"> <xsl:call-template name="l10n.language"/> </xsl:param> <xsl:choose> <xsl:when test="$key = 'index.term.separator'"> <xsl:choose> <!-- Use the override if not blank --> <xsl:when test="$index.term.separator != ''"> <xsl:copy-of select="$index.term.separator"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="gentext.template"> <xsl:with-param name="lang" select="$lang"/> <xsl:with-param name="context">index</xsl:with-param> <xsl:with-param name="name">term-separator</xsl:with-param> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="$key = 'index.number.separator'"> <xsl:choose> <!-- Use the override if not blank --> <xsl:when test="$index.number.separator != ''"> <xsl:copy-of select="$index.number.separator"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="gentext.template"> <xsl:with-param name="lang" select="$lang"/> <xsl:with-param name="context">index</xsl:with-param> <xsl:with-param name="name">number-separator</xsl:with-param> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:when test="$key = 'index.range.separator'"> <xsl:choose> <!-- Use the override if not blank --> <xsl:when test="$index.range.separator != ''"> <xsl:copy-of select="$index.range.separator"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="gentext.template"> <xsl:with-param name="lang" select="$lang"/> <xsl:with-param name="context">index</xsl:with-param> <xsl:with-param name="name">range-separator</xsl:with-param> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:when> </xsl:choose> </xsl:template> </xsl:stylesheet> ���boxbackup/docs/xsl-generic/html/ebnf.xsl������������������������������������������������������������0000664�0001750�0001750�00000023713�11175136613�021102� 0����������������������������������������������������������������������������������������������������ustar �siretart������������������������siretart���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:doc="http://nwalsh.com/xsl/documentation/1.0" exclude-result-prefixes="doc" version='1.0'> <!-- ******************************************************************** $Id: ebnf.xsl 6910 2007-06-28 23:23:30Z xmldoc $ ******************************************************************** This file is part of the XSL DocBook Stylesheet distribution. See ../README or http://docbook.sf.net/release/xsl/current/ for copyright and other information. ******************************************************************** --> <doc:reference xmlns=""> <referenceinfo> <releaseinfo role="meta"> $Id: ebnf.xsl 6910 2007-06-28 23:23:30Z xmldoc $ </releaseinfo> <author><surname>Walsh</surname> <firstname>Norman</firstname></author> <copyright><year>1999</year><year>2000</year> <holder>Norman Walsh</holder> </copyright> </referenceinfo> <title>HTML EBNF Reference
Introduction This is technical reference documentation for the DocBook XSL Stylesheets; it documents (some of) the parameters, templates, and other elements of the stylesheets. This reference describes the templates and parameters relevant to formatting EBNF markup. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets, and for anyone who's interested in how it works. Although I am trying to be thorough, this documentation is known to be incomplete. Don't forget to read the source, too :-)
1 EBNF for
EBNF productions
[ ]   Error: no ID for productionrecap linkend: . Warning: multiple "IDs" for productionrecap linkend: . |
production Non-terminals with no content must point to production elements in the current document. Invalid xpointer for empty nt: ??? /*   */
constraintdef : :  ]

boxbackup/docs/xsl-generic/html/chunk-code.xsl0000664000175000017500000005701711175136613022214 0ustar siretartsiretart bk ar pr ch ap pt rn re co s bi go ix si chunk-filename-error- Note namesp. cut stripped namespace before processing Note namesp. cut processing stripped document Unable to strip the namespace from DB5 document, cannot proceed. ID ' ' not found in document. boxbackup/docs/xsl-generic/html/biblio.xsl0000664000175000017500000011437711175136613021437 0ustar siretartsiretart

No bibliography entry: found in

Error: no bibliography entry: found in

No bibliography entry: found in

Error: no bibliography entry: found in

[ ] [ ] [ ] [ ] [ ] copyright ,
boxbackup/docs/xsl-generic/html/callout.xsl0000664000175000017500000001613011175136613021626 0ustar siretartsiretart No insertCallouts function is available.
Error: coref link is broken: Error: coref doesn't point to a co: {$conum} Don't know how to generate Unicode callouts when $callout.unicode.start.character is ( ) ( )
boxbackup/docs/xsl-generic/html/titlepage.xsl0000664000175000017500000010166511175136613022151 0ustar siretartsiretart








 

copyright

,















: ,





3 2 RevHistory
,  



boxbackup/docs/xsl-generic/html/xref.xsl0000664000175000017500000013361511175136613021137 0ustar siretartsiretart http://docbook.org/xlink/role/olink Endterm points to nonexistent ID: ??? ERROR: xref linking to has no generated link text. ??? XRef to nonexistent id: ??? Endterm points to nonexistent ID: ??? suppress anchor removing removing Don't know what gentext to create for xref to: " ", (" ") ??? [ ] No bibliography entry: found in [ ] Endterm points to nonexistent ID: ??? Link element has no content and no Endterm. Nothing to show in the link to ??? Olink debug: root element of target.database ' ' is ' '. Error: unresolved olink: targetdoc/targetptr = ' / '. Warning: olink linkmode pointer is wrong. # ? & boxbackup/docs/xsl-generic/html/synop.xsl0000664000175000017500000013606511175136613021345 0ustar siretartsiretart ]>



( )  

( )

    
    
  


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

( void) ; ... ) ; , ) ; ( ) padding-bottom: 1em
 
( void) ;   ... ) ;     , ) ;   , ) ; ( ) java Unrecognized language on :
    
    
    
       extends
      
      
        
    
implements
    
throws  {
}
,   , , ,    ;     void  0 ,
 
   ( )
    throws 
;
    
    
    
      : 
      
      
        
    
implements
    
throws  {
}
,   , , ,    ;     void  ,    ( )
    throws 
;
    
    interface 
    
    
      : 
      
      
        
    
implements
    
throws  {
}
,   , , ,    ;     void  ,    ( )
    raises( )
;
    
    package 
    
    ;
    
@ISA = ( );
,   , , ,    ;     void  , sub { ... };
boxbackup/docs/xsl-generic/html/autoidx-ng.xsl0000664000175000017500000000145511175136613022246 0ustar siretartsiretart kosek boxbackup/docs/xsl-generic/html/chunk-common.xsl0000664000175000017500000022511611175136613022567 0ustar siretartsiretart Computing chunks... Fast chunking requires exsl:node-set(). Using "slow" chunking. Error is not a chunk! -toc

-toc -toc 0 1 0


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

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 # 1 0 Olink error: cannot compute relative sitemap path because $current.docid ' ' not found in target database. Olink warning: cannot compute relative sitemap path without $current.docid parameter ../ xxx / /
boxbackup/docs/xsl-generic/html/sections.xsl0000664000175000017500000005426111175136613022021 0ustar siretartsiretart
1 2 3 4 5
6 clear: both 1 1 2 3 4 5 6
boxbackup/docs/xsl-generic/html/block.xsl0000664000175000017500000002750411175136613021264 0ustar siretartsiretart

 
   
  --

--

nbsp , ,

boxbackup/docs/xsl-generic/html/docbook.xsl0000664000175000017500000004366311175136613021616 0ustar siretartsiretart Element in namespace ' ' encountered in , but no template matches. < > </ > white black #0000FF #840084 #0000FF <xsl:copy-of select="$title"/> Note namesp. cut stripped namespace before processing Note namesp. cut processing stripped document Unable to strip the namespace from DB5 document, cannot proceed. ID ' ' not found in document. 0 boxbackup/docs/xsl-generic/html/lists.xsl0000664000175000017500000010352411175136613021325 0ustar siretartsiretart
circle disc square
  • list-style-type:
  • 1 a i A I Unexpected numeration:

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

    :

    ??? # ???
    boxbackup/docs/xsl-generic/html/pi.xsl0000664000175000017500000013363111175136613020601 0ustar siretartsiretart HTML Processing Instruction Reference $Id: pi.xsl 7250 2007-08-18 10:19:00Z xmldoc $ Introduction This is generated reference documentation for all user-specifiable processing instructions (PIs) in the DocBook XSL stylesheets for HTML output. You add these PIs at particular points in a document to cause specific “exceptions†to formatting/output behavior. To make global changes in formatting/output behavior across an entire document, it’s better to do it by setting an appropriate stylesheet parameter (if there is one). Sets background color for an image Use the dbhtml background-color PI before or after an image (graphic, inlinegraphic, imagedata, or videodata element) as a sibling to the element, to set a background color for the image. dbhtml background-color="color" background-color="color" An HTML color value Background color Sets background color on a table row or table cell Use the dbhtml bgcolor PI as child of a table row or cell to set a background color for that table row or cell. dbhtml bgcolor="color" bgcolor="color" An HTML color value Cell background color Specifies cellpadding in table or qandaset output Use the dbhtml cellpadding PI as a child of a table or qandaset to specify the value for the HTML cellpadding attribute in the output HTML table. dbhtml cellpadding="number" cellpadding="number" Specifies the cellpadding html.cellpadding Cell spacing and cell padding, Q and A formatting Specifies cellspacing in table or qandaset output Use the dbhtml cellspacing PI as a child of a table or qandaset to specify the value for the HTML cellspacing attribute in the output HTML table. dbhtml cellspacing="number" cellspacing="number" Specifies the cellspacing html.cellspacing Cell spacing and cell padding, Q and A formatting Set value of the class attribute for a table row Use the dbhtml class PI as a child of a row to specify a class attribute and value in the HTML output for that row. dbhtml class="name" class="name" Specifies the class name Table styles in HTML output Specifies a directory name in which to write files When chunking output, use the dbhtml dir PI as a child of a chunk source to cause the output of that chunk to be written to the specified directory; also, use it as a child of a mediaobject to specify a directory into which any long-description files for that mediaobject will be written. dbhtml dir="path" dir="path" Specifies the pathname for the directory base.dir dbhtml dir processing instruction Specifies a filename for a chunk When chunking output, use the dbhtml filename PI as a child of a chunk source to specify a filename for the output file for that chunk. dbhtml filename="filename" filename="path" Specifies the filename for the file use.id.as.filename dbhtml filenames Specifies presentation style for a funcsynopsis Use the dbhtml funcsynopsis-style PI as a child of a funcprototype or anywhere within a funcprototype control the presentation style for the funcsynopsis in output. dbhtml funcsynopsis-style="kr"|"ansi" funcsynopsis-style="kr" Displays the funcprototype in K&R style funcsynopsis-style="ansi" Displays the funcprototype in ANSI style funcsynopsis.style Specifies a path to the location of an image file Use the dbhtml img.src.path PI before or after an image (graphic, inlinegraphic, imagedata, or videodata element) as a sibling to the element, to specify a path to the location of the image; in HTML output, the value specified for the img.src.path attribute is prepended to the filename. dbhtml img.src.path="path" img.src.path="path" Specifies the pathname to prepend to the name of the image file img.src.path Using fileref Specifies the label width for a qandaset Use the dbhtml label-width PI as a child of a qandaset to specify the width of labels. dbhtml label-width="width" label-width="width" Specifies the label width (including units) Q and A formatting Specifies interval for lines numbers in verbatims Use the dbhtml linenumbering.everyNth PI as a child of a “verbatim†element – programlisting, screen, synopsis — to specify the interval at which lines are numbered. dbhtml linenumbering.everyNth="N" linenumbering.everyNth="N" Specifies numbering interval; a number is output before every Nth line linenumbering.everyNth Line numbering Specifies separator text for line numbers in verbatims Use the dbhtml linenumbering.separator PI as a child of a “verbatim†element – programlisting, screen, synopsis — to specify the separator text output between the line numbers and content. dbhtml linenumbering.separator="text" linenumbering.separator="text" Specifies the text (zero or more characters) linenumbering.separator Line numbering Specifies width for line numbers in verbatims Use the dbhtml linenumbering.width PI as a child of a “verbatim†element – programlisting, screen, synopsis — to specify the width set aside for line numbers. dbhtml linenumbering.width="width" linenumbering.width="width" Specifies the width (inluding units) linenumbering.width Line numbering Specifies presentation style for a variablelist or segmentedlist Use the dbhtml list-presentation PI as a child of a variablelist or segmentedlist to control the presentation style for the list (to cause it, for example, to be displayed as a table). dbhtml list-presentation="list"|"table" list-presentation="list" Displays the list as a list list-presentation="table" Displays the list as a table variablelist.as.table segmentedlist.as.table Variable list formatting in HTML Specifies the width of a variablelist or simplelist Use the dbhtml list-width PI as a child of a variablelist or a simplelist presented as a table, to specify the output width. dbhtml list-width="width" list-width="width" Specifies the output width (including units) Variable list formatting in HTML Specifies the height for a table row Use the dbhtml row-height PI as a child of a row to specify the height of the row. dbhtml row-height="height" row-height="height" Specifies the label height (including units) Row height (obsolete) Sets the starting number on an ordered list This PI is obsolete. The intent of this PI was to provide a means for setting a specific starting number for an ordered list. Instead of this PI, set a value for the override attribute on the first listitem in the list; that will have the same effect as what this PI was intended for. dbhtml start="character" start="character" Specifies the character to use as the starting number; use 0-9, a-z, A-Z, or lowercase or uppercase Roman numerals List starting number Specifies summary for table, variablelist, segmentedlist, or qandaset output Use the dbhtml table-summary PI as a child of a table, variablelist, segmentedlist, or qandaset to specify the text for the HTML summary attribute in the output HTML table. dbhtml table-summary="text" table-summary="text" Specifies the summary text (zero or more characters) Variable list formatting in HTML, Table summary text Specifies the width for a table Use the dbhtml table-width PI as a child of a table to specify the width of the table in output. dbhtml table-width="width" table-width="width" Specifies the table width (including units or as a percentage) default.table.width Table width Sets character formatting for terms in a variablelist Use the dbhtml term-presentation PI as a child of a variablelist to set character formatting for the term output of the list. dbhtml term-presentation="bold"|"italic"|"bold-italic" term-presentation="bold" Specifies that terms are displayed in bold term-presentation="italic" Specifies that terms are displayed in italic term-presentation="bold-italic" Specifies that terms are displayed in bold-italic Variable list formatting in HTML Specifies separator text among terms in a varlistentry Use the dbhtml term-separator PI as a child of a variablelist to specify the separator text among term instances. dbhtml term-separator="text" term-separator="text" Specifies the text (zero or more characters) variablelist.term.separator Variable list formatting in HTML Specifies the term width for a variablelist Use the dbhtml term-width PI as a child of a variablelist to specify the width for term output. dbhtml term-width="width" term-width="width" Specifies the term width (including units) Variable list formatting in HTML Specifies whether a TOC should be generated for a qandaset Use the dbhtml toc PI as a child of a qandaset to specify whether a table of contents (TOC) is generated for the qandaset. dbhtml toc="0"|"1" toc="0" If zero, no TOC is generated toc="1" If 1 (or any non-zero value), a TOC is generated Q and A list of questions, Q and A formatting Generates a hyperlinked list of commands Use the dbcmdlist PI as the child of any element (for example, refsynopsisdiv) containing multiple cmdsynopsis instances; a hyperlinked navigational “command list†will be generated at the top of output for that element, enabling users to quickly jump to each command synopsis. dbcmdlist [No parameters] No cmdsynopsis elements matched dbcmdlist PI, perhaps it's nested too deep?
    Generates a hyperlinked list of functions Use the dbfunclist PI as the child of any element (for example, refsynopsisdiv) containing multiple funcsynopsis instances; a hyperlinked navigational “function list†will be generated at the top of output for that element, enabling users to quickly jump to to each function synopsis. dbfunclist [No parameters] No funcsynopsis elements matched dbfunclist PI, perhaps it's nested too deep?
    Copies an external well-formed HTML/XML file into current doc Use the dbhtml-include href PI anywhere in a document to cause the contents of the file referenced by the href pseudo-attribute to be copied/inserted “as is†into your HTML output at the point in document order where the PI occurs in the source. The referenced file may contain plain text (as long as it is “wrapped†in an html element — see the note below) or markup in any arbitrary vocabulary, including HTML — but it must conform to XML well-formedness constraints (because the feature in XSLT 1.0 for opening external files, the document() function, can only handle files that meet XML well-formedness constraints). Among other things, XML well-formedness constraints require a document to have a single root element. So if the content you want to include is plain text or is markup that does not have a single root element, wrap the content in an html element. The stylesheets will strip out that surrounding html “wrapper†when they find it, leaving just the content you want to insert. dbhtml-include href="URI" href="URI" Specifies the URI for the file to include; the URI can be, for example, a remote http: URI, or a local filesystem file: URI textinsert.extension Inserting external HTML code, External code files href ERROR: dbhtml-include processing instruction href has no content. ERROR: dbhtml-include processing instruction has missing or empty href value. filename
    #
    #
    / / Sets topic name and topic id for context-sensitive HTML Help Use the dbhh PI as a child of components that should be used as targets for context-sensitive help requests. dbhh topicname="name" topicid="id" topicname="name" Specifies a unique string constant that identifies a help topic topicid="id" Specifies a unique integer value for the topicname string Context-sensitive help
    boxbackup/docs/xsl-generic/html/profile-chunk-code.xsl0000664000175000017500000005573711175136613023661 0ustar siretartsiretart bk ar pr ch ap pt rn re co s bi go ix si chunk-filename-error- Note: namesp. cut : stripped namespace before processingNote: namesp. cut : processing stripped document ID ' ' not found in document. boxbackup/docs/xsl-generic/html/info.xsl0000664000175000017500000000300311175136613021111 0ustar siretartsiretart boxbackup/docs/xsl-generic/html/division.xsl0000664000175000017500000001655211175136613022017 0ustar siretartsiretart

    title

    boxbackup/docs/xsl-generic/html/annotations.xsl0000664000175000017500000001303111175136613022515 0ustar siretartsiretart Note: namesp. cut : stripped namespace before processingNote: namesp. cut : processing stripped document ID ' ' not found in document. 0 boxbackup/docs/xsl-generic/html/glossary.xsl0000664000175000017500000003670411175136613022037 0ustar siretartsiretart %common.entities; ]> &setup-language-variable;
    &setup-language-variable;
    &setup-language-variable;

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

    Warning: glosssee @otherterm reference not found: .

    Warning: glossseealso @otherterm reference not found: . , &setup-language-variable; Warning: processing automatic glossary without a glossary.collection file. Warning: processing automatic glossary but unable to open glossary.collection file ' '
    &setup-language-variable;
    boxbackup/docs/xsl-generic/html/profile-onechunk.xsl0000664000175000017500000000270011175136613023431 0ustar siretartsiretart 1 # boxbackup/docs/xsl-generic/html/highlight.xsl0000664000175000017500000000336111175136613022134 0ustar siretartsiretart boxbackup/docs/xsl-generic/html/html-rtf.xsl0000664000175000017500000002637111175136613021730 0ustar siretartsiretart

    boxbackup/docs/xsl-generic/html/changebars.xsl0000664000175000017500000000704411175136613022264 0ustar siretartsiretart
    Revisionflag on unexpected element: (Assuming block)
    boxbackup/docs/xsl-generic/html/autotoc.xsl0000664000175000017500000005740011175136613021646 0ustar siretartsiretart dt li dd

    TableofContents

    1 2 3 4 5 1 2 3 2 3 4 5 6 2 3 4 1 0

    ListofTables ListofFigures ListofEquations ListofExamples ListofProcedures ListofUnknown

    boxbackup/docs/xsl-generic/manpages/0000775000175000017500000000000011652362373020265 5ustar siretartsiretartboxbackup/docs/xsl-generic/manpages/html-synop.xsl0000664000175000017500000013643211175136613023134 0ustar siretartsiretart

    . . ( )  

    ( )

    .sp .nf
        
        
      
    .fi

    .

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

    ( void) ; ... ) ; , ) ; ( ) padding-bottom: 1em
     
    ( void) ;   ... ) ;     , ) ;   , ) ; ( ) java Unrecognized language on : . .sp .nf
        
        
        
           extends
          
          
            
    .
    
    	    
          
        
        
          implements
          
          
            
    .
    
    	    
          
        
        
          throws
          
        
         {
        
    .
    
        
        }
      
    .fi
    ,   , , ,    ;     =  void  0 , .      ( ) .     throws  ; .sp .nf
        
        
        
          : 
          
          
            
    .
    
    	    
          
        
        
           implements
          
          
            
    .
    
    	    
          
        
        
           throws
          
        
         {
        
    .
    
        
        }
      
    .fi
    ,   , , ,    ;     =  void  ,    ( ) .     throws  ; .sp .nf
        
        interface 
        
        
          : 
          
          
            
    .
    
    	    
          
        
        
           implements
          
          
            
    .
    
    	    
          
        
        
           throws
          
        
         {
        
    .
    
        
        }
      
    .fi
    ,   , , ,    ;     =  void  ,    ( ) .     raises( ) ; .sp .nf
        
        package 
        
        ;
        
    .
    
    
        
          @ISA = (
          
          );
          
    .
    
        
    
        
      
    .fi
    ,   , , ,    ;     =  void  , sub { ... };
    boxbackup/docs/xsl-generic/manpages/param.xsl0000664000175000017500000001734311175136613022121 0ustar siretartsiretart 1 0 @*[local-name() = 'block'] = 'Miscellaneous Technical' or (@*[local-name() = 'block'] = 'C1 Controls And Latin-1 Supplement (Latin-1 Supplement)' and @*[local-name() = 'class'] = 'symbols' ) or (@*[local-name() = 'block'] = 'General Punctuation' and (@*[local-name() = 'class'] = 'spaces' or @*[local-name() = 'class'] = 'dashes' or @*[local-name() = 'class'] = 'quotes' or @*[local-name() = 'class'] = 'bullets' ) ) or @*[local-name() = 'name'] = 'HORIZONTAL ELLIPSIS' or @*[local-name() = 'name'] = 'WORD JOINER' or @*[local-name() = 'name'] = 'SERVICE MARK' or @*[local-name() = 'name'] = 'TRADE MARK SIGN' or @*[local-name() = 'name'] = 'ZERO WIDTH NO-BREAK SPACE' 1 1 1 BI B B B 0 0 0 0 4 0 1 man/ UTF-8 MAN.MANIFEST 0 ======================================================================== ---- 0 30 0 30 0 20 0 (($info[//date])[last()]/date)[1]| (($info[//pubdate])[last()]/pubdate)[1] refmeta/refmiscinfo[1]/node() 0 (($info[//title])[last()]/title)[1]| ../title/node() refmeta/refmiscinfo[1]/node() 0 (($info[//productname])[last()]/productname)[1]| (($info[//corpname])[last()]/corpname)[1]| (($info[//corpcredit])[last()]/corpcredit)[1]| (($info[//corpauthor])[last()]/corpauthor)[1]| (($info[//orgname])[last()]/orgname)[1]| (($info[//publishername])[last()]/publishername)[1] 0 0 (($info[//productnumber])[last()]/productnumber)[1]| (($info[//edition])[last()]/edition)[1]| (($info[//releaseinfo])[last()]/releaseinfo)[1] 0 boxbackup/docs/xsl-generic/manpages/inline.xsl0000664000175000017500000001464111175136613022275 0ustar siretartsiretart ( ) © ® boxbackup/docs/xsl-generic/manpages/table.xsl0000664000175000017500000006733411175136613022115 0ustar siretartsiretart : allbox center expand
    .PP . *[nested▀table] .sp -1n .TS H tab( ) ; .TH .T& .TE .sp
    T{ T} Warn tbl convert Extracted a nested table [\fInested▀table\fR]* . ^ c r n l t ^ s .br ftn. # [ ]
    boxbackup/docs/xsl-generic/manpages/refentry.xsl0000664000175000017500000002410711175136613022653 0ustar siretartsiretart .br .SH " " , - .SH " " .SH " " .SS " " .RS .RE .RS .RE .ti (\n(SNu * 5u / 3u) (\n(SNu) .RS (\n(SNu) .RE boxbackup/docs/xsl-generic/manpages/other.xsl0000664000175000017500000007203611175136613022142 0ustar siretartsiretart \ \e . \. - \- ' \'   \ .\" Title: .\" Author: .\" Generator: DocBook v <http://docbook.sf.net/> .\" Date: .\" Manual: .\" Source: .\" .TH " " " " " " " " " " .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" store initial "default indentation value" .nr zq \n(IN .\" adjust default indentation .nr IN .\" adjust indentation of SS headings .nr SN \n(IN .\" enable line breaks after slashes .cflags 4 / Note: Note: (soelim stub) Note: (manifest file) boxbackup/docs/xsl-generic/manpages/synop.xsl0000664000175000017500000002642111175136613022166 0ustar siretartsiretart | ( ) .sp -1n .HP ( ) .br▒ .ad l .hy 0 .HP .ad .hy .ad l .hy 0 .ad .hy .HP . " ( " void); ...); , ); "░" "░" ( ) boxbackup/docs/xsl-generic/manpages/utility.xsl0000664000175000017500000004665311175136613022532 0ustar siretartsiretart \fB \fR \fI \fR \% .sp .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .sp .RS .RE .\" boxbackup/docs/xsl-generic/manpages/endnotes.xsl0000664000175000017500000005747111175136613022646 0ustar siretartsiretart Warn endnote Bad: [ ] in source Note endnote Has: / Note endnote Fix: / para/ \% \&[ ] .SH " " .IP " . " .RS \% .RE boxbackup/docs/xsl-generic/manpages/block.xsl0000664000175000017500000002512111175136613022104 0ustar siretartsiretart .PP . .sp .RS 4n .PP .RE .sp Yes .sp .RS .ft .nf .fi .ft .nf .fi .RE .sp before .PP .sp .RS .RE [IMAGE] [ ] boxbackup/docs/xsl-generic/manpages/docbook.xsl0000664000175000017500000003350111175136613022433 0ustar siretartsiretart Note namesp. cut stripped namespace before processing Note namesp. cut processing stripped document MAN.MANIFEST Erro no refentry No refentry elements found in " ... " . boxbackup/docs/xsl-generic/manpages/lists.xsl0000664000175000017500000003225211175136613022153 0ustar siretartsiretart \n(zqu .sp .PP .PP .br .RS .RE .sp .sp .RS \h'- 0 ' \h'+ 0 ' .RE .sp .RS \h'- 0 ' \h'+ 0 ' .RE .PP .sp .RE .PP .IP "" , .IP "" .PP .\" line length increase to cope w/ tbl weirdness .ll +(\n(LLu * 62u / 100u) .TS l . .TE .\" line length decrease back to previous value .ll -(\n(LLu * 62u / 100u) .sp T{ T} boxbackup/docs/xsl-generic/manpages/info.xsl0000664000175000017500000006414011175136613021751 0ustar siretartsiretart \n(zqu < > .SH " Author Authors " .PP .br .PP .PP .PP .sp -1n .IP "" . <\& \&> , .br , .br .br .sp -1n .IP "" . .sp -1n .IP "" . .sp -1n .IP "" . . .sp -1n .IP "" .PP .br .SH " Copyright " .br .sp boxbackup/docs/xsl-generic/manpages/charmap.groff.xsl0000664000175000017500000045313711175136613023543 0ustar siretartsiretart boxbackup/docs/xsl-generic/manpages/profile-docbook.xsl0000664000175000017500000003324011175136613024071 0ustar siretartsiretart Note: namesp. cut : stripped namespace before processingNote: namesp. cut : processing stripped document MAN.MANIFEST Erro no refentry No refentry elements found in " ... " . boxbackup/docs/xsl-generic/common/0000775000175000017500000000000011652362373017762 5ustar siretartsiretartboxbackup/docs/xsl-generic/common/el.xml0000664000175000017500000013153111175136613021104 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/bg.xml0000664000175000017500000010350411175136613021073 0ustar siretartsiretart Цифри и знаци Ра Б б Ð’ в Г г Д д Е е Ж ж З з И и Й й К к Л л М м Рн О о П п Р Ñ€ С Ñ Ð¢ Ñ‚ У у Ф Ñ„ Ð¥ Ñ… Ц ц Ч ч Ш ш Щ щ Ъ ÑŠ Ь ÑŒ Ю ÑŽ Я Ñ Ð­ Ñ Ð« Ñ‹ A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z boxbackup/docs/xsl-generic/common/or.xml0000664000175000017500000013364611175136613021135 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/l10n.dtd0000664000175000017500000000240611175136613021227 0ustar siretartsiretart boxbackup/docs/xsl-generic/common/autoidx-kosek.xsl0000664000175000017500000001227711175136613023306 0ustar siretartsiretart ]> ERROR: the 'kosek' index method does not work with the xsltproc XSLT processor. 1 No " " localization of index grouping letters exists . ; using "en". 0 No " " localization of index grouping letters exists . ; using "en". boxbackup/docs/xsl-generic/common/ta.xml0000664000175000017500000013337211175136613021115 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/autoidx-kimber.xsl0000664000175000017500000000314411175136613023434 0ustar siretartsiretart ]> ERROR: the 'kimber' index method requires the Saxon version 6 or 8 XSLT processor. 1 boxbackup/docs/xsl-generic/common/xh.xml0000664000175000017500000012623211175136613021125 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/de.xml0000664000175000017500000007402611175136613021101 0ustar siretartsiretart Symbole A a Ä ä B b C c D d E e F f G g H h I i J j K k L l M m N n O o Ö ö P p Q q R r S s T t U u Ü ü V v W w X x Y y Z z boxbackup/docs/xsl-generic/common/common.xsl0000664000175000017500000021401511175136613022001 0ustar siretartsiretart Common » Base Template Reference $Id: common.xsl 7056 2007-07-17 13:56:09Z xmldoc $ Introduction This is technical reference documentation for the “base†set of common templates in the DocBook XSL Stylesheets. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. Tests if a given node is a component-level element This template returns '1' if the specified node is a component (Chapter, Appendix, etc.), and '0' otherwise. node The node which is to be tested. This template returns '1' if the specified node is a component (Chapter, Appendix, etc.), and '0' otherwise. 1 0 Tests if a given node is a section-level element This template returns '1' if the specified node is a section (Section, Sect1, Sect2, etc.), and '0' otherwise. node The node which is to be tested. This template returns '1' if the specified node is a section (Section, Sect1, Sect2, etc.), and '0' otherwise. 1 0 Returns the hierarchical level of a section This template calculates the hierarchical level of a section. The element sect1 is at level 1, sect2 is at level 2, etc. Recursive sections are calculated down to the fifth level. node The section node for which the level should be calculated. Defaults to the context node. The section level, 1, 2, etc. 1 2 3 4 5 6 5 4 3 2 1 2 3 4 5 5 5 4 3 2 1 1 Returns the hierarchical level of a QandASet This template calculates the hierarchical level of a QandASet. The level, 1, 2, etc. 1 1 1 2 3 5 4 3 2 1 1 question answer qandadiv qandaset [FAMILY Given] , , [ ] { } [ ] ... | 4pi Selects and processes an appropriate media object from a list This template takes a list of media objects (usually the children of a mediaobject or inlinemediaobject) and processes the "right" object. This template relies on a template named "select.mediaobject.index" to determine which object in the list is appropriate. If no acceptable object is located, nothing happens. olist The node list of potential objects to examine. Calls <xsl:apply-templates> on the selected object. Selects the position of the appropriate media object from a list This template takes a list of media objects (usually the children of a mediaobject or inlinemediaobject) and determines the "right" object. It returns the position of that object to be used by the calling template. If the parameter use.role.for.mediaobject is nonzero, then it first checks for an object with a role attribute of the appropriate value. It takes the first of those. Otherwise, it takes the first acceptable object through a recursive pass through the list. This template relies on a template named "is.acceptable.mediaobject" to determine if a given object is an acceptable graphic. The semantics of media objects is that the first acceptable graphic should be used. If no acceptable object is located, no index is returned. olist The node list of potential objects to examine. count The position in the list currently being considered by the recursive process. Returns the position in the original list of the selected object. 1 1 0 0 1 0 1 Returns '1' if the specified media object is recognized This template examines a media object and returns '1' if the object is recognized as a graphic. object The media object to consider. 0 or 1 0 1 1 1 0 . . Warn users about references to non-unique IDs If passed an ID in linkend, check.id.unique prints a warning message to the user if either the ID does not exist or the ID is not unique. Error: no ID for constraint linkend: . Warning: multiple "IDs" for constraint linkend: . Warn users about incorrectly typed references If passed an ID in linkend, check.idref.targets makes sure that the element pointed to by the link is one of the elements listed in element-list and warns the user otherwise. Error: linkend ( ) points to " " not (one of): Unexpected context in procedure.step.numeration: 1 2 loweralpha lowerroman upperalpha upperroman arabic arabic circle square disc Print a set of years with collapsed ranges This template prints a list of year elements with consecutive years printed as a range. In other words: 1992 1993 1994]]> is printed 1992-1994, whereas: 1992 1994]]> is printed 1992, 1994. This template assumes that all the year elements contain only decimal year numbers, that the elements are sorted in increasing numerical order, that there are no duplicates, and that all the years are expressed in full century+year (1999 not 99) notation. years The initial set of year elements. print.ranges If non-zero, multi-year ranges are collapsed. If zero, all years are printed discretely. single.year.ranges If non-zero, two consecutive years will be printed as a range, otherwise, they will be printed discretely. In other words, a single year range is 1991-1992 but discretely it's 1991, 1992. This template returns the formatted list of years. , , - , , , - , Search in a table for the "best" match for the node This template searches in a table for the value that most-closely (in the typical best-match sense of XSLT) matches the current (element) node location. / Converts a string to all uppercase letters Given a string, this template does a language-aware conversion of that string to all uppercase letters, based on the values of the lowercase.alpha and uppercase.alpha gentext keys for the current locale. It affects only those characters found in the values of lowercase.alpha and uppercase.alpha. All other characters are left unchanged. string The string to convert to uppercase. Converts a string to all lowercase letters Given a string, this template does a language-aware conversion of that string to all lowercase letters, based on the values of the uppercase.alpha and lowercase.alpha gentext keys for the current locale. It affects only those characters found in the values of uppercase.alpha and lowercase.alpha. All other characters are left unchanged. string The string to convert to lowercase. Returns localized choice separator This template enables auto-generation of an appropriate localized "choice" separator (for example, "and" or "or") before the final item in an inline list (though it could also be useful for generating choice separators for non-inline lists). It currently works by evaluating a processing instruction (PI) of the form <?dbchoice choice="foo"?> : if the value of the choice pseudo-attribute is "and" or "or", returns a localized "and" or "or" otherwise returns the literal value of the choice pseudo-attribute The latter is provided only as a temporary workaround because the locale files do not currently have translations for the word or. So if you want to generate a a logical "or" separator in French (for example), you currently need to do this: <?dbchoice choice="ou"?> The dbchoice processing instruction is an unfortunate hack; support for it may disappear in the future (particularly if and when a more appropriate means for marking up "choice" lists becomes available in DocBook). Evaluates an info profile This template evaluates an "info profile" matching the XPath expression given by the profile parameter. It relies on the XSLT evaluate() extension function. The value of the profile parameter can include the literal string $info. If found in the value of the profile parameter, the literal string $info string is replaced with the value of the info parameter, which should be a set of *info nodes; the expression is then evaluated using the XSLT evaluate() extension function. profile A string representing an XPath expression info A set of *info nodes Returns a node (the result of evaluating the profile parameter) Error: The "info profiling" mechanism currently requires an XSLT engine that supports the evaluate() XSLT extension function. Your XSLT engine does not support it. boxbackup/docs/xsl-generic/common/sk.xml0000664000175000017500000012423211175136613021121 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/ar.xml0000664000175000017500000012705311175136613021112 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/sq.xml0000664000175000017500000012444711175136613021137 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/es.xml0000664000175000017500000007541311175136613021121 0ustar siretartsiretart Símbolos A a á à B b C c CH ch D d E e É é F f G g H h I i à í J j K k L l LL ll M m N n Ñ ñ O o Ó ó P p Q q R r S s T t U u Ú ú V v W w X x Y y Z z boxbackup/docs/xsl-generic/common/nn.xml0000664000175000017500000012452711175136613021126 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/ro.xml0000664000175000017500000012502611175136613021126 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/he.xml0000664000175000017500000012675711175136613021116 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/mn.xml0000664000175000017500000010230211175136613021110 0ustar siretartsiretart ТÑмдÑгтүүд A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z Ра Б б Ð’ в Г г Д д Е е Ð Ñ‘ Ж ж З з И и Й й К к Л л М м Рн О о Ó¨ Ó© П п Р Ñ€ С Ñ Ð¢ Ñ‚ У у Ò® Ò¯ Ф Ñ„ Ð¥ Ñ… Ц ц Ч ч Ш ш Щ щ Ъ ÑŠ Ы Ñ‹ Ь ÑŒ Э Ñ Ð® ÑŽ Я Ñ boxbackup/docs/xsl-generic/common/bs.xml0000664000175000017500000007360411175136613021116 0ustar siretartsiretart Simboli A a B b C c Ć ć ÄŒ Ä D d Ä Ä‘ E e F f G g H h I i J j K k L l M m N n O o P p R r S s Å  Å¡ T t U u V v Z z Ž ž boxbackup/docs/xsl-generic/common/uk.xml0000664000175000017500000012767011175136613021134 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/common.xml0000664000175000017500000004336611175136613022004 0ustar siretartsiretart Common » Base Template Reference $Id: common.xsl 7056 2007-07-17 13:56:09Z xmldoc $ Introduction This is technical reference documentation for the “base†set of common templates in the DocBook XSL Stylesheets. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. is.component Tests if a given node is a component-level element <xsl:template name="is.component"> <xsl:param name="node" select="."/> ... </xsl:template> <para>This template returns '1' if the specified node is a component (Chapter, Appendix, etc.), and '0' otherwise.</para> </refsect1><refsect1><title>Parameters node The node which is to be tested. Returns This template returns '1' if the specified node is a component (Chapter, Appendix, etc.), and '0' otherwise. is.section Tests if a given node is a section-level element <xsl:template name="is.section"> <xsl:param name="node" select="."/> ... </xsl:template> <para>This template returns '1' if the specified node is a section (Section, Sect1, Sect2, etc.), and '0' otherwise.</para> </refsect1><refsect1><title>Parameters node The node which is to be tested. Returns This template returns '1' if the specified node is a section (Section, Sect1, Sect2, etc.), and '0' otherwise. section.level Returns the hierarchical level of a section <xsl:template name="section.level"> <xsl:param name="node" select="."/> ... </xsl:template> <para>This template calculates the hierarchical level of a section. The element <tag>sect1</tag> is at level 1, <tag>sect2</tag> is at level 2, etc.</para> <para>Recursive sections are calculated down to the fifth level.</para> </refsect1><refsect1><title>Parameters node The section node for which the level should be calculated. Defaults to the context node. Returns The section level, 1, 2, etc. qanda.section.level Returns the hierarchical level of a QandASet <xsl:template name="qanda.section.level"/> <para>This template calculates the hierarchical level of a QandASet. </para> </refsect1><refsect1><title>Returns The level, 1, 2, etc. select.mediaobject Selects and processes an appropriate media object from a list <xsl:template name="select.mediaobject"> <xsl:param name="olist" select="imageobject|imageobjectco |videoobject|audioobject|textobject"/> ... </xsl:template> <para>This template takes a list of media objects (usually the children of a mediaobject or inlinemediaobject) and processes the "right" object.</para> <para>This template relies on a template named "select.mediaobject.index" to determine which object in the list is appropriate.</para> <para>If no acceptable object is located, nothing happens.</para> </refsect1><refsect1><title>Parameters olist The node list of potential objects to examine. Returns Calls <xsl:apply-templates> on the selected object. select.mediaobject.index Selects the position of the appropriate media object from a list <xsl:template name="select.mediaobject.index"> <xsl:param name="olist" select="imageobject|imageobjectco |videoobject|audioobject|textobject"/> <xsl:param name="count">1</xsl:param> ... </xsl:template> <para>This template takes a list of media objects (usually the children of a mediaobject or inlinemediaobject) and determines the "right" object. It returns the position of that object to be used by the calling template.</para> <para>If the parameter <parameter>use.role.for.mediaobject</parameter> is nonzero, then it first checks for an object with a role attribute of the appropriate value. It takes the first of those. Otherwise, it takes the first acceptable object through a recursive pass through the list.</para> <para>This template relies on a template named "is.acceptable.mediaobject" to determine if a given object is an acceptable graphic. The semantics of media objects is that the first acceptable graphic should be used. </para> <para>If no acceptable object is located, no index is returned.</para> </refsect1><refsect1><title>Parameters olist The node list of potential objects to examine. count The position in the list currently being considered by the recursive process. Returns Returns the position in the original list of the selected object. is.acceptable.mediaobject Returns '1' if the specified media object is recognized <xsl:template name="is.acceptable.mediaobject"> <xsl:param name="object"/> ... </xsl:template> <para>This template examines a media object and returns '1' if the object is recognized as a graphic.</para> </refsect1><refsect1><title>Parameters object The media object to consider. Returns 0 or 1 check.id.unique Warn users about references to non-unique IDs <xsl:template name="check.id.unique"> <xsl:param name="linkend"/> ... </xsl:template> <para>If passed an ID in <varname>linkend</varname>, <function>check.id.unique</function> prints a warning message to the user if either the ID does not exist or the ID is not unique.</para> </refsect1></refentry> <refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.check.idref.targets"> <refnamediv> <refname>check.idref.targets</refname> <refpurpose>Warn users about incorrectly typed references</refpurpose> </refnamediv> <refsynopsisdiv> <synopsis><xsl:template name="check.idref.targets"> <xsl:param name="linkend"/> <xsl:param name="element-list"/> ... </xsl:template></synopsis> </refsynopsisdiv> <refsect1><title/> <para>If passed an ID in <varname>linkend</varname>, <function>check.idref.targets</function> makes sure that the element pointed to by the link is one of the elements listed in <varname>element-list</varname> and warns the user otherwise.</para> </refsect1></refentry> <refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.copyright.years"> <refnamediv> <refname>copyright.years</refname> <refpurpose>Print a set of years with collapsed ranges</refpurpose> </refnamediv> <refsynopsisdiv> <synopsis><xsl:template name="copyright.years"> <xsl:param name="years"/> <xsl:param name="print.ranges" select="1"/> <xsl:param name="single.year.ranges" select="0"/> <xsl:param name="firstyear" select="0"/> <xsl:param name="nextyear" select="0"/> ... </xsl:template></synopsis> </refsynopsisdiv> <refsect1><title/> <para>This template prints a list of year elements with consecutive years printed as a range. In other words:</para> <screen><year>1992</year> <year>1993</year> <year>1994</year></screen> <para>is printed <quote>1992-1994</quote>, whereas:</para> <screen><year>1992</year> <year>1994</year></screen> <para>is printed <quote>1992, 1994</quote>.</para> <para>This template assumes that all the year elements contain only decimal year numbers, that the elements are sorted in increasing numerical order, that there are no duplicates, and that all the years are expressed in full <quote>century+year</quote> (<quote>1999</quote> not <quote>99</quote>) notation.</para> </refsect1><refsect1><title>Parameters years The initial set of year elements. print.ranges If non-zero, multi-year ranges are collapsed. If zero, all years are printed discretely. single.year.ranges If non-zero, two consecutive years will be printed as a range, otherwise, they will be printed discretely. In other words, a single year range is 1991-1992 but discretely it's 1991, 1992. Returns This template returns the formatted list of years. find.path.params Search in a table for the "best" match for the node <xsl:template name="find.path.params"> <xsl:param name="node" select="."/> <xsl:param name="table" select="''"/> <xsl:param name="location"> <xsl:call-template name="xpath.location"> <xsl:with-param name="node" select="$node"/> </xsl:call-template> </xsl:param> ... </xsl:template> <para>This template searches in a table for the value that most-closely (in the typical best-match sense of XSLT) matches the current (element) node location.</para> </refsect1></refentry> <refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.string.upper"> <refnamediv> <refname>string.upper</refname> <refpurpose>Converts a string to all uppercase letters</refpurpose> </refnamediv> <refsynopsisdiv> <synopsis><xsl:template name="string.upper"> <xsl:param name="string" select="''"/> ... </xsl:template></synopsis> </refsynopsisdiv> <refsect1><title/> <para>Given a string, this template does a language-aware conversion of that string to all uppercase letters, based on the values of the <literal>lowercase.alpha</literal> and <literal>uppercase.alpha</literal> gentext keys for the current locale. It affects only those characters found in the values of <literal>lowercase.alpha</literal> and <literal>uppercase.alpha</literal>. All other characters are left unchanged.</para> </refsect1><refsect1><title>Parameters string The string to convert to uppercase. string.lower Converts a string to all lowercase letters <xsl:template name="string.lower"> <xsl:param name="string" select="''"/> ... </xsl:template> <para>Given a string, this template does a language-aware conversion of that string to all lowercase letters, based on the values of the <literal>uppercase.alpha</literal> and <literal>lowercase.alpha</literal> gentext keys for the current locale. It affects only those characters found in the values of <literal>uppercase.alpha</literal> and <literal>lowercase.alpha</literal>. All other characters are left unchanged.</para> </refsect1><refsect1><title>Parameters string The string to convert to lowercase. select.choice.separator Returns localized choice separator <xsl:template name="select.choice.separator"/> <para>This template enables auto-generation of an appropriate localized "choice" separator (for example, "and" or "or") before the final item in an inline list (though it could also be useful for generating choice separators for non-inline lists).</para> <para>It currently works by evaluating a processing instruction (PI) of the form <?dbchoice choice="foo"?> : <itemizedlist> <listitem> <simpara>if the value of the <tag>choice</tag> pseudo-attribute is "and" or "or", returns a localized "and" or "or"</simpara> </listitem> <listitem> <simpara>otherwise returns the literal value of the <tag>choice</tag> pseudo-attribute</simpara> </listitem> </itemizedlist> The latter is provided only as a temporary workaround because the locale files do not currently have translations for the word <wordasword>or</wordasword>. So if you want to generate a a logical "or" separator in French (for example), you currently need to do this: <literallayout><?dbchoice choice="ou"?></literallayout> </para> <warning> <para>The <tag>dbchoice</tag> processing instruction is an unfortunate hack; support for it may disappear in the future (particularly if and when a more appropriate means for marking up "choice" lists becomes available in DocBook).</para> </warning> </refsect1></refentry> <refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.evaluate.info.profile"> <refnamediv> <refname>evaluate.info.profile</refname> <refpurpose>Evaluates an info profile</refpurpose> </refnamediv> <refsynopsisdiv> <synopsis><xsl:template name="evaluate.info.profile"> <xsl:param name="profile"/> <xsl:param name="info"/> ... </xsl:template></synopsis> </refsynopsisdiv> <refsect1><title/> <para>This template evaluates an "info profile" matching the XPath expression given by the <parameter>profile</parameter> parameter. It relies on the XSLT <function>evaluate()</function> extension function.</para> <para>The value of the <parameter>profile</parameter> parameter can include the literal string <literal>$info</literal>. If found in the value of the <parameter>profile</parameter> parameter, the literal string <literal>$info</literal> string is replaced with the value of the <parameter>info</parameter> parameter, which should be a set of <replaceable>*info</replaceable> nodes; the expression is then evaluated using the XSLT <function>evaluate()</function> extension function.</para> </refsect1><refsect1><title>Parameters profile A string representing an XPath expression info A set of *info nodes Returns Returns a node (the result of evaluating the profile parameter) boxbackup/docs/xsl-generic/common/bn.xml0000664000175000017500000013425711175136613021113 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/da.xml0000664000175000017500000007371611175136613021102 0ustar siretartsiretart A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z Æ æ Ø ø Ã… Ã¥ boxbackup/docs/xsl-generic/common/table.xsl0000664000175000017500000004344611175136613021610 0ustar siretartsiretart 0: 0: : 0 Determine the column number in which a given entry occurs If an entry has a colname or namest attribute, this template will determine the number of the column in which the entry should occur. For other entrys, nothing is returned. entry The entry-element which is to be tested. This template returns the column number if it can be determined, or 0 (the empty string) 1 1 1 1 : boxbackup/docs/xsl-generic/common/cs.xml0000664000175000017500000006737311175136613021125 0ustar siretartsiretart Symboly A a à á B b C c ÄŒ Ä D d ÄŽ Ä E e É é Äš Ä› Ë ë F f G g H h Ch ch cH CH I i à í J j K k L l M m N n Ň ň O o Ó ó Ö ö P p Q q R r Ř Å™ S s Å  Å¡ T t Ť Å¥ U u Ú ú Å® ů Ü ü V v W w X x Y y à ý Z z Ž ž boxbackup/docs/xsl-generic/common/refentry.xsl0000664000175000017500000014750611175136613022361 0ustar siretartsiretart Common » Refentry Metadata Template Reference $Id: refentry.xsl 7056 2007-07-17 13:56:09Z xmldoc $ Introduction This is technical reference documentation for the “refentry metadata†templates in the DocBook XSL Stylesheets. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. Currently, only the manpages stylesheets make use of these templates. They are, however, potentially useful elsewhere. Gathers metadata from a refentry and its ancestors Reference documentation for particular commands, functions, etc., is sometimes viewed in isolation from its greater "context". For example, users view Unix man pages as, well, individual pages, not as part of a "book" of some kind. Therefore, it is sometimes necessary to embed "context" information in output for each refentry. However, one problem is that different users mark up that context information in different ways. Often (usually), the context information is not actually part of the content of the refentry itself, but instead part of the content of a parent or ancestor element to the the refentry. And even then, DocBook provides a variety of elements that users might potentially use to mark up the same kind of information. One user might use the productnumber element to mark up version information about a particular product, while another might use the releaseinfo element. Taking all that in mind, the get.refentry.metadata template tries to gather metadata from a refentry element and its ancestor elements in an intelligent and user-configurable way. The basic mechanism used in the XPath expressions throughout this stylesheet is to select the relevant metadata from the *info element that is closest to the actual refentry â€“ either on the refentry itself, or on its nearest ancestor. The get.refentry.metadata template is actually just sort of a "driver" template; it calls other templates that do the actual data collection, then returns the data as a set. refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing user preferences (from global stylesheet parameters) Returns a node set with the following elements. The descriptions are verbatim from the man(7) man page. title the title of the man page (e.g., MAN) section the section number the man page should be placed in (e.g., 7) date the date of the last revision source the source of the command manual the title of the manual (e.g., Linux Programmer's Manual) <xsl:call-template name="get.refentry.title"> <xsl:with-param name="refname" select="$refname"/> </xsl:call-template>
    Gets title metadata for a refentry The man(7) man page describes this as "the title of the man page (e.g., MAN). This differs from refname in that, if the refentry has a refentrytitle, we use that as the title; otherwise, we just use first refname in the first refnamediv in the source. refname The first refname in the refentry Returns a title node. Gets section metadata for a refentry The man(7) man page describes this as "the section number the man page should be placed in (e.g., 7)". If we do not find a manvolnum specified in the source, and we find that the refentry is for a function, we use the section number 3 ["Library calls (functions within program libraries)"]; otherwise, we default to using 1 ["Executable programs or shell commands"]. refname The first refname in the refentry quiet If non-zero, no "missing" message is emitted Returns a string representing a section number. Note meta manvol no refentry/refmeta/manvolnum Note meta manvol see http://docbook.sf.net/el/manvolnum Note meta manvol Setting man section to 3 3 1 Gets date metadata for a refentry The man(7) man page describes this as "the date of the last revision". If we cannot find a date in the source, we generate one. refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns a date node. Note meta date no date; using generated date Note meta date see http://docbook.sf.net/el/date Gets source metadata for a refentry The man(7) man page describes this as "the source of the command", and provides the following examples: For binaries, use something like: GNU, NET-2, SLS Distribution, MCC Distribution. For system calls, use the version of the kernel that you are currently looking at: Linux 0.99.11. For library calls, use the source of the function: GNU, BSD 4.3, Linux DLL 4.4.1. The solbook(5) man page describes something very much like what man(7) calls "source", except that solbook(5) names it "software" and describes it like this:
    This is the name of the software product that the topic discussed on the reference page belongs to. For example UNIX commands are part of the SunOS x.x release.
    In practice, there are many pages that simply have a version number in the "source" field. So, it looks like what we have is a two-part field, Name Version, where: Name product name (e.g., BSD) or org. name (e.g., GNU) Version version name Each part is optional. If the Name is a product name, then the Version is probably the version of the product. Or there may be no Name, in which case, if there is a Version, it is probably the version of the item itself, not the product it is part of. Or, if the Name is an organization name, then there probably will be no Version.
    refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns a source node.
    Warn meta source no valid fallback for source; leaving empty Warn meta source no source fallback specified; leaving empty Gets source-name metadata for a refentry A "source name" is one part of a (potentially) two-part Name Version source field. For more details, see the documentation for the get.refentry.source template. refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Depending on what output method is used for the current stylesheet, either returns a text node or possibly an element node, containing "source name" data. source source productname source productname source productname source productname source productname Note meta source no *info/productname or alternative Note meta source see http://docbook.sf.net/el/productname Note meta source no refentry/refmeta/refmiscinfo@class=source Note meta source see http://docbook.sf.net/el/refmiscinfo Gets version metadata for a refentry A "version" is one part of a (potentially) two-part Name Version source field. For more details, see the documentation for the get.refentry.source template. refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Depending on what output method is used for the current stylesheet, either returns a text node or possibly an element node, containing "version" data. version version productnumber version productnumber Note meta version no *info/productnumber or alternative Note meta version see http://docbook.sf.net/el/productnumber Note meta version no refentry/refmeta/refmiscinfo@class=version Note meta version see http://docbook.sf.net/el/refmiscinfo Gets source metadata for a refentry The man(7) man page describes this as "the title of the manual (e.g., Linux Programmer's Manual)". Here are some examples from existing man pages: dpkg utilities (dpkg-name) User Contributed Perl Documentation (GET) GNU Development Tools (ld) Emperor Norton Utilities (ddate) Debian GNU/Linux manual (faked) GIMP Manual Pages (gimp) KDOC Documentation System (qt2kdoc) The solbook(5) man page describes something very much like what man(7) calls "manual", except that solbook(5) names it "sectdesc" and describes it like this:
    This is the section title of the reference page; for example User Commands.
    refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns a manual node.
    manual manual Note meta manual no titled ancestor of refentry Note meta manual no refentry/refmeta/refmiscinfo@class=manual Note meta manual see http://docbook.sf.net/el/refmiscinfo Warn meta manual no valid fallback for manual; leaving empty Warn meta manual no manual fallback specified; leaving empty Gets user preferences for refentry metadata gathering The DocBook XSL stylesheets include several user-configurable global stylesheet parameters for controlling refentry metadata gathering. Those parameters are not read directly by the other refentry metadata-gathering templates. Instead, they are read only by the get.refentry.metadata.prefs template, which assembles them into a structure that is then passed to the other refentry metadata-gathering templates. So the, get.refentry.metadata.prefs template is the only interface to collecting stylesheet parameters for controlling refentry metadata gathering. There are no local parameters for this template; however, it does rely on a number of global parameters. Returns a manual node. Sets content of a refentry metadata item The set.refentry.metadata template is called each time a suitable source element is found for a certain metadata field. refname The first refname in the refentry info A single *info node that contains the selected source element. contents A node containing the selected source element. context A string describing the metadata context in which the set.refentry.metadata template was called: either "date", "source", "version", or "manual". Returns formatted contents of a selected source element. Note Note no refentry/refmeta/refmiscinfo@class= Note
    boxbackup/docs/xsl-generic/common/tr.xml0000664000175000017500000007312311175136613021133 0ustar siretartsiretart Semboller A a B b C c Ç ç D d E e F f G g Äž ÄŸ H h I ı İ i J j K k L l M m N n O o Ö ö P p R r S s Åž ÅŸ T t U u Ü ü V v Y y Z z boxbackup/docs/xsl-generic/common/vi.xml0000664000175000017500000012551411175136613021126 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/ca.xml0000664000175000017500000012542111175136613021070 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/stripns.xsl0000664000175000017500000003013211175136613022207 0ustar siretartsiretart info objectinfo blockinfo WARNING: cannot add @xml:base to node set root element. Relative paths may not work. / Stripping namespace from DocBook 5 document. Processing stripped document. boxbackup/docs/xsl-generic/common/fa.xml0000664000175000017500000013142011175136613021067 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/ru.xml0000664000175000017500000010146311175136613021133 0ustar siretartsiretart A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z Ра Б б Ð’ в Г г Д д Е е Ð Ñ‘ Ж ж З з И и Й й К к Л л М м Рн О о П п Р Ñ€ С Ñ Ð¢ Ñ‚ У у Ф Ñ„ Ð¥ Ñ… Ц ц Ч ч Ш ш Щ щ Ъ ÑŠ Ы Ñ‹ Ь ÑŒ Э Ñ Ð® ÑŽ Я Ñ boxbackup/docs/xsl-generic/common/zh_tw.xml0000664000175000017500000012452511175136613021644 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/et.xml0000664000175000017500000012503211175136613021113 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/titles.xsl0000664000175000017500000006356411175136613022030 0ustar siretartsiretart Provides access to element titles Processing an element in the title.markup mode produces the title of the element. This does not include the label. Request for title of element with no title: (id=" ") (xml:id=" ") ???TITLE??? REFENTRY WITHOUT TITLE??? ERROR: glossdiv missing its required title Note Important Caution Warning Tip Question Answer Question XRef to nonexistent id: ??? Endterm points to nonexistent ID: ??? boxbackup/docs/xsl-generic/common/zh_cn.xml0000664000175000017500000007251611175136613021614 0ustar siretartsiretart 其它 A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z boxbackup/docs/xsl-generic/common/la.xml0000664000175000017500000012427111175136613021103 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/utility.xml0000664000175000017500000002305211175136613022205 0ustar siretartsiretart Common » Utility Template Reference $Id: utility.xsl 7101 2007-07-20 15:32:12Z xmldoc $ Introduction This is technical reference documentation for the miscellaneous utility templates in the DocBook XSL Stylesheets. These templates are defined in a separate file from the set of “common†templates because some of the common templates reference DocBook XSL stylesheet parameters, requiring the entire set of parameters to be imported/included in any stylesheet that imports/includes the common templates. The utility templates don’t import or include any DocBook XSL stylesheet parameters, so the utility templates can be used without importing the whole set of parameters. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. log.message Logs/emits formatted notes and warnings <xsl:template name="log.message"> <xsl:param name="level"/> <xsl:param name="source"/> <xsl:param name="context-desc"/> <xsl:param name="context-desc-field-length">12</xsl:param> <xsl:param name="context-desc-padded"> <xsl:if test="not($context-desc = '')"> <xsl:call-template name="pad-string"> <xsl:with-param name="leftRight">right</xsl:with-param> <xsl:with-param name="padVar" select="substring($context-desc, 1, $context-desc-field-length)"/> <xsl:with-param name="length" select="$context-desc-field-length"/> </xsl:call-template> </xsl:if> </xsl:param> <xsl:param name="message"/> <xsl:param name="message-field-length" select="45"/> <xsl:param name="message-padded"> <xsl:variable name="spaces-for-blank-level"> <!-- * if the level field is blank, we'll need to pad out --> <!-- * the message field with spaces to compensate --> <xsl:choose> <xsl:when test="$level = ''"> <xsl:value-of select="4 + 2"/> <!-- * 4 = hard-coded length of comment text ("Note" or "Warn") --> <!-- * + 2 = length of colon-plus-space separator ": " --> </xsl:when> <xsl:otherwise> <xsl:value-of select="0"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="spaces-for-blank-context-desc"> <!-- * if the context-description field is blank, we'll need --> <!-- * to pad out the message field with spaces to compensate --> <xsl:choose> <xsl:when test="$context-desc = ''"> <xsl:value-of select="$context-desc-field-length + 2"/> <!-- * + 2 = length of colon-plus-space separator ": " --> </xsl:when> <xsl:otherwise> <xsl:value-of select="0"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="extra-spaces" select="$spaces-for-blank-level + $spaces-for-blank-context-desc"/> <xsl:call-template name="pad-string"> <xsl:with-param name="leftRight">right</xsl:with-param> <xsl:with-param name="padVar" select="substring($message, 1, ($message-field-length + $extra-spaces))"/> <xsl:with-param name="length" select="$message-field-length + $extra-spaces"/> </xsl:call-template> </xsl:param> ... </xsl:template> <para>The <function>log.message</function> template is a utility template for logging/emitting formatted messages – that is, notes and warnings, along with a given log “level†and an identifier for the “source†that the message relates to.</para> </refsect1><refsect1><title>Parameters level Text to log/emit in the message-level field to indicate the message level (Note or Warning) source Text to log/emit in the source field to identify the “source†to which the notification/warning relates. This can be any arbitrary string, but because the message lacks line and column numbers to identify the exact part of the source document to which it relates, the intention is that the value you pass into the source parameter should give the user some way to identify the portion of their source document on which to take potentially take action in response to the log message (for example, to edit, change, or add content). So the source value should be, for example, an ID, book/chapter/article title, title of some formal object, or even a string giving an XPath expression. context-desc Text to log/emit in the context-description field to describe the context for the message. context-desc-field-length Specifies length of the context-description field (in characters); default is 12 If the text specified by the context-desc parameter is longer than the number of characters specified in context-desc-field-length, it is truncated to context-desc-field-length (12 characters by default). If the specified text is shorter than context-desc-field-length, it is right-padded out to context-desc-field-length (12 by default). If no value has been specified for the context-desc parameter, the field is left empty and the text of the log message begins with the value of the message parameter. message Text to log/emit in the actual message field message-field-length Specifies length of the message field (in characters); default is 45 Returns Outputs a message (generally, to standard error). get.doc.title Gets a title from the current document <xsl:template name="get.doc.title"/> <para>The <function>get.doc.title</function> template is a utility template for returning the first title found in the current document.</para> </refsect1><refsect1><title>Returns Returns a string containing some identifying title for the current document . pad-string Right-pads or left-pads a string out to a certain length <xsl:template name="pad-string"> <xsl:param name="padChar" select="' '"/> <xsl:param name="leftRight">left</xsl:param> <xsl:param name="padVar"/> <xsl:param name="length"/> ... </xsl:template> <para>This function takes string <parameter>padVar</parameter> and pads it out in the direction <parameter>rightLeft</parameter> to the string-length <parameter>length</parameter>, using string <parameter>padChar</parameter> (a space character by default) as the padding string (note that <parameter>padChar</parameter> can be a string; it is not limited to just being a single character).</para> <note> <para>This function began as a copy of Nate Austin's <function>prepend-pad</function> function in the <link xlink:href="http://www.dpawson.co.uk/xsl/sect2/padding.html">Padding Content</link> section of Dave Pawson's <link xlink:href="http://www.dpawson.co.uk/xsl/index.html">XSLT FAQ</link>.</para> </note> </refsect1><refsect1><title>Returns Returns a (padded) string. boxbackup/docs/xsl-generic/common/pa.xml0000664000175000017500000013011411175136613021100 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/charmap.xsl0000664000175000017500000002247311175136613022131 0ustar siretartsiretart Common » Character-Map Template Reference $Id: charmap.xsl 7266 2007-08-22 11:58:42Z xmldoc $ Introduction This is technical reference documentation for the character-map templates in the DocBook XSL Stylesheets. These templates are defined in a separate file from the set of “common†templates because some of the common templates reference DocBook XSL stylesheet parameters, requiring the entire set of parameters to be imported/included in any stylesheet that imports/includes the common templates. The character-map templates don’t import or include any DocBook XSL stylesheet parameters, so the character-map templates can be used without importing the whole set of parameters. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. Applies an XSLT character map This template applies an XSLT character map; that is, it causes certain individual characters to be substituted with strings of one or more characters. It is useful mainly for replacing multiple “special†characters or symbols in the same target content. It uses the value of map.contents to do substitution on content, and then returns the modified contents. This template is a very slightly modified version of Jeni Tennison’s replace_strings template in the multiple string replacements section of Dave Pawson’s XSLT FAQ. The apply-string-subst-map template is essentially the same template as the apply-character-map template; the only difference is that in the map that apply-string-subst-map expects, oldstring and newstring attributes are used instead of character and string attributes. content The content on which to perform the character-map substitution. map.contents A node set of elements, with each element having the following attributes: character, a character to be replaced string, a string with which to replace character Reads in all or part of an XSLT character map The XSLT 2.0 specification describes character maps and explains how they may be used to allow a specific character appearing in a text or attribute node in a final result tree to be substituted by a specified string of characters during serialization. The read-character-map template provides a means for reading and using character maps with XSLT 1.0-based tools. This template reads the character-map contents from uri (in full or in part, depending on the value of the use.subset parameter), then passes those contents to the apply-character-map template, along with content, the data on which to perform the character substitution. Using the character map “in part†means that it uses only those output-character elements that match the XPath expression given in the value of the subset.profile parameter. The current implementation of that capability here relies on the evaluate extension XSLT function. use.subset Specifies whether to use a subset of the character map instead of the whole map; boolean 0 or 1 subset.profile XPath expression that specifies what subset of the character map to use uri URI for a character map Error: To process character-map subsets, you must use an XSLT engine that supports the evaluate() XSLT extension function. Your XSLT engine does not support it. boxbackup/docs/xsl-generic/common/pt.xml0000664000175000017500000012504411175136613021131 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/hu.xml0000664000175000017500000012510711175136613021122 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/labels.xsl0000664000175000017500000007345111175136613021762 0ustar siretartsiretart Provides access to element labels Processing an element in the label.markup mode produces the element label. Trailing punctuation is not added to the label. . Request for label of unexpected element: label.markup: this can't happen! 1 a i A I Unexpected numeration: 0 Returns true if $section should be labelled Returns true if the specified section should be labelled. By default, this template returns zero unless the section level is less than or equal to the value of the $section.autolabel.max.depth parameter, in which case it returns $section.autolabel. Custom stylesheets may override it to get more selective behavior. 1 1 Unexpected .autolabel value: ; using default. Returns format for autolabel parameters Returns format passed as parameter if non zero. Supported format are 'arabic' or '1', 'loweralpha' or 'a', 'lowerroman' or 'i', 'upperlapha' or 'A', 'upperroman' or 'I', 'arabicindic' or '١'. If its not one of these then returns the default format. boxbackup/docs/xsl-generic/common/pt_br.xml0000664000175000017500000012515311175136613021615 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/gentext.xsl0000664000175000017500000007267411175136613022204 0ustar siretartsiretart .formal object.xref.markup: empty xref template for linkend=" " and @xrefstyle=" " Xref is only supported to listitems in an orderedlist: ??? %n 1 Attempt to use %d in gentext with no referrer! % % labelnumber labelname label quotedtitle title nopage pagenumber pageabbrev Page page nodocname docnamelong docname %n %t %t %p boxbackup/docs/xsl-generic/common/olink.xsl0000664000175000017500000013177211175136613021635 0ustar siretartsiretart Olinks not processed: must specify a $target.database.document parameter when using olinks with targetdoc and targetptr attributes. Olink error: could not open target database ' '. Olink debug: cases for targetdoc=' ' and targetptr=' ' in language ' '. Olink debug: CaseA matched. Olink debug: CaseA NOT matched Olink debug: CaseB matched. Olink debug: CaseB NOT matched Olink debug: CaseC matched. Olink debug: CaseC NOT matched. Olink debug: CaseD matched. Olink debug: CaseD NOT matched Olink debug: CaseE matched. Olink debug: CaseE NOT matched. Olink debug: CaseF matched. Olink debug: CaseF NOT matched. Olink debug: CaseB key is the final selection: Olink debug: CaseA key is the final selection: Olink debug: CaseC key is the final selection: Olink debug: CaseD key is the final selection: Olink debug: CaseF key is the final selection: Olink debug: CaseE key is the final selection: Olink debug: No case matched for lang ' '. 1 0 Olink error: cannot compute relative sitemap path because $current.docid ' ' not found in target database. Olink warning: cannot compute relative sitemap path without $current.docid parameter xrefstyle is ' '. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' ' in context 'xref-number-and-title '. Using template without @style. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' ' in context 'xref-number '. Using template without @style. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' ' in context 'xref '. Using template without @style. Olink error: no gentext template exists for xrefstyle ' ' for element ' ' in language ' '. Trying '%t'. Olink debug: xrefstyle template is ' '. Olink error: no generated text for targetdoc/targetptr/lang = ' '. ???? Olink error: no generated text for targetdoc/targetptr/lang = ' '. ???? Olink error: cannot locate targetdoc in sitemap / ../ boxbackup/docs/xsl-generic/common/utility.xsl0000664000175000017500000002777711175136613022235 0ustar siretartsiretart Common » Utility Template Reference $Id: utility.xsl 7101 2007-07-20 15:32:12Z xmldoc $ Introduction This is technical reference documentation for the miscellaneous utility templates in the DocBook XSL Stylesheets. These templates are defined in a separate file from the set of “common†templates because some of the common templates reference DocBook XSL stylesheet parameters, requiring the entire set of parameters to be imported/included in any stylesheet that imports/includes the common templates. The utility templates don’t import or include any DocBook XSL stylesheet parameters, so the utility templates can be used without importing the whole set of parameters. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. Logs/emits formatted notes and warnings The log.message template is a utility template for logging/emitting formatted messages â€“ that is, notes and warnings, along with a given log “level†and an identifier for the “source†that the message relates to. level Text to log/emit in the message-level field to indicate the message level (Note or Warning) source Text to log/emit in the source field to identify the “source†to which the notification/warning relates. This can be any arbitrary string, but because the message lacks line and column numbers to identify the exact part of the source document to which it relates, the intention is that the value you pass into the source parameter should give the user some way to identify the portion of their source document on which to take potentially take action in response to the log message (for example, to edit, change, or add content). So the source value should be, for example, an ID, book/chapter/article title, title of some formal object, or even a string giving an XPath expression. context-desc Text to log/emit in the context-description field to describe the context for the message. context-desc-field-length Specifies length of the context-description field (in characters); default is 12 If the text specified by the context-desc parameter is longer than the number of characters specified in context-desc-field-length, it is truncated to context-desc-field-length (12 characters by default). If the specified text is shorter than context-desc-field-length, it is right-padded out to context-desc-field-length (12 by default). If no value has been specified for the context-desc parameter, the field is left empty and the text of the log message begins with the value of the message parameter. message Text to log/emit in the actual message field message-field-length Specifies length of the message field (in characters); default is 45 Outputs a message (generally, to standard error). 12 right right : : Gets a title from the current document The get.doc.title template is a utility template for returning the first title found in the current document. Returns a string containing some identifying title for the current document . Right-pads or left-pads a string out to a certain length This function takes string padVar and pads it out in the direction rightLeft to the string-length length, using string padChar (a space character by default) as the padding string (note that padChar can be a string; it is not limited to just being a single character). This function began as a copy of Nate Austin's prepend-pad function in the Padding Content section of Dave Pawson's XSLT FAQ. Returns a (padded) string. left boxbackup/docs/xsl-generic/common/fi.xml0000664000175000017500000007331711175136613021111 0ustar siretartsiretart Symbole A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s Å  Å¡ T t U u V v W w X x Y y Z z Ž ž Ã… Ã¥ Ä ä Ö ö boxbackup/docs/xsl-generic/common/sl.xml0000664000175000017500000012462511175136613021130 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/sr.xml0000664000175000017500000010155511175136613021133 0ustar siretartsiretart Симболи Ра Б б Ð’ в Г г Д д Ђ Ñ’ Е е Ж ж З з И и Ј ј К к Л л Љ Ñ™ М м Рн Њ Ñš О о П п Р Ñ€ С Ñ Ð¢ Ñ‚ Ћ Ñ› У у Ф Ñ„ Ð¥ Ñ… Ц ц Ч ч Ð ÑŸ Ш ш A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q Q R r S s T t U u V v W w X x Y y Z z boxbackup/docs/xsl-generic/common/ja.xml0000664000175000017500000012425511175136613021103 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/lv.xml0000664000175000017500000012334611175136613021132 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/l10n.xsl0000664000175000017500000003640311175136613021266 0ustar siretartsiretart _ No localization exists for " " or " ". Using default " ". No " " localization of " " exists . ; using "en". bullet No " " localization of dingbat exists; using "en". startquote endquote nestedstartquote nestedendquote No " " localization exists. No context named " " exists in the " " localization. No template for " " (or any of its leaves) exists in the context named " " in the " " localization. 1 0 boxbackup/docs/xsl-generic/common/pl.xml0000664000175000017500000012502611175136613021121 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/insertfile.xsl0000664000175000017500000000756211175136613022664 0ustar siretartsiretart boxbackup/docs/xsl-generic/common/lt.xml0000664000175000017500000007471711175136613021137 0ustar siretartsiretart Simboliai A a Ä„ Ä… B b C c ÄŒ Ä D d E e Ę Ä™ Ä– Ä— F f G g H h I i Ä® į Y y J j K k L l M m N n O o P p R r S s Å  Å¡ T t U u Ų ų Ū Å« V v Z z Ž ž Q q W w X x boxbackup/docs/xsl-generic/common/pi.xsl0000664000175000017500000003322611175136613021124 0ustar siretartsiretart Common Processing Instruction Reference $Id: pi.xsl 7107 2007-07-22 10:22:06Z xmldoc $ Introduction This is generated reference documentation for all user-specifiable processing instructions (PIs) in the “common†part of the DocBook XSL stylesheets. You add these PIs at particular points in a document to cause specific “exceptions†to formatting/output behavior. To make global changes in formatting/output behavior across an entire document, it’s better to do it by setting an appropriate stylesheet parameter (if there is one). Generates a localized choice separator Use the dbchoice choice PI to generate an appropriate localized “choice†separator (for example, and or or) before the final item in an inline simplelist This PI is a less-than-ideal hack; support for it may disappear in the future (particularly if and when a more appropriate means for marking up "choice" lists becomes available in DocBook). dbchoice choice="and"|"or"|string" choice="and" generates a localized and separator choice="or" generates a localized or separator choice="string" generates a literal string separator choice Inserts a date timestamp Use the dbtimestamp PI at any point in a source document to cause a date timestamp (a formatted string representing the current date and time) to be inserted in output of the document. dbtimestamp format="formatstring" [padding="0"|"1"] format="formatstring" Specifies format in which the date and time are output For details of the content of the format string, see Date and time. padding="0"|"1" Specifies padding behavior; if non-zero, padding is is added format padding 1 Timestamp processing requires XSLT processor with EXSLT date support. Generates delimiters around embedded TeX equations in output Use the dbtex delims PI as a child of a textobject containing embedded TeX markup, to cause that markup to be surrounded by $ delimiter characters in output. dbtex delims="no"|"yes" dbtex delims="no"|"yes" Specifies whether delimiters are output tex.math.delims DBTeXMath 0 0 0 0 0 Timestamp processing requires an XSLT processor with support for the EXSLT node-set() function. boxbackup/docs/xsl-generic/common/it.xml0000664000175000017500000012445511175136613021127 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/cy.xml0000664000175000017500000012454511175136613021126 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ Ch ch D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ Dd dd E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ Ff ff G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ Ng ng H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ Ll ll M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Ph ph Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ Rh rh S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ Th th U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/targets.xsl0000664000175000017500000002045211175136613022162 0ustar siretartsiretart Collects information for potential cross reference targets Processing the root element in the collect.targets mode produces a set of target database elements that can be used by the olink mechanism to resolve external cross references. The collection process is controlled by the collect.xref.targets parameter, which can be yes to collect targets and process the document for output, only to only collect the targets, and no (default) to not collect the targets and only process the document. A targets.filename parameter must be specified to receive the output if collect.xref.targets is set to yes so as to redirect the target data to a file separate from the document output. Must specify a $targets.filename parameter when $collect.xref.targets is set to 'yes'. The xref targets were not collected.
    boxbackup/docs/xsl-generic/common/nl.xml0000664000175000017500000012562511175136613021124 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/id.xml0000664000175000017500000012467211175136613021110 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/hi.xml0000664000175000017500000013142611175136613021107 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/kn.xml0000664000175000017500000013147711175136613021125 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/af.xml0000664000175000017500000012620111175136613021070 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/ga.xml0000664000175000017500000012451511175136613021077 0ustar siretartsiretart Siombailí A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/gu.xml0000664000175000017500000013174411175136613021125 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/charmap.xml0000664000175000017500000001502311175136613022114 0ustar siretartsiretart Common » Character-Map Template Reference $Id: charmap.xsl 7266 2007-08-22 11:58:42Z xmldoc $ Introduction This is technical reference documentation for the character-map templates in the DocBook XSL Stylesheets. These templates are defined in a separate file from the set of “common†templates because some of the common templates reference DocBook XSL stylesheet parameters, requiring the entire set of parameters to be imported/included in any stylesheet that imports/includes the common templates. The character-map templates don’t import or include any DocBook XSL stylesheet parameters, so the character-map templates can be used without importing the whole set of parameters. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. apply-character-map Applies an XSLT character map <xsl:template name="apply-character-map"> <xsl:param name="content"/> <xsl:param name="map.contents"/> ... </xsl:template> <para>This template applies an <link xlink:href="http://www.w3.org/TR/xslt20/#character-maps">XSLT character map</link>; that is, it causes certain individual characters to be substituted with strings of one or more characters. It is useful mainly for replacing multiple “special†characters or symbols in the same target content. It uses the value of <parameter>map.contents</parameter> to do substitution on <parameter>content</parameter>, and then returns the modified contents.</para> <note> <para>This template is a very slightly modified version of Jeni Tennison’s <function>replace_strings</function> template in the <link xlink:href="http://www.dpawson.co.uk/xsl/sect2/StringReplace.html#d9351e13">multiple string replacements</link> section of Dave Pawson’s <link xlink:href="http://www.dpawson.co.uk/xsl/index.html">XSLT FAQ</link>.</para> <para>The <function>apply-string-subst-map</function> template is essentially the same template as the <function>apply-character-map</function> template; the only difference is that in the map that <function>apply-string-subst-map</function> expects, <tag class="attribute">oldstring</tag> and <tag class="attribute">newstring</tag> attributes are used instead of <tag class="attribute">character</tag> and <tag class="attribute">string</tag> attributes.</para> </note> </refsect1><refsect1><title>Parameters content The content on which to perform the character-map substitution. map.contents A node set of elements, with each element having the following attributes: character, a character to be replaced string, a string with which to replace character read-character-map Reads in all or part of an XSLT character map <xsl:template name="read-character-map"> <xsl:param name="use.subset"/> <xsl:param name="subset.profile"/> <xsl:param name="uri"/> ... </xsl:template> <para>The XSLT 2.0 specification describes <link xlink:href="http://www.w3.org/TR/xslt20/#character-maps">character maps</link> and explains how they may be used to allow a specific character appearing in a text or attribute node in a final result tree to be substituted by a specified string of characters during serialization. The <function>read-character-map</function> template provides a means for reading and using character maps with XSLT 1.0-based tools.</para> <para>This template reads the character-map contents from <parameter>uri</parameter> (in full or in part, depending on the value of the <parameter>use.subset</parameter> parameter), then passes those contents to the <function>apply-character-map</function> template, along with <parameter>content</parameter>, the data on which to perform the character substitution.</para> <para>Using the character map “in part†means that it uses only those <tag>output-character</tag> elements that match the XPath expression given in the value of the <parameter>subset.profile</parameter> parameter. The current implementation of that capability here relies on the <function>evaluate</function> extension XSLT function.</para> </refsect1><refsect1><title>Parameters use.subset Specifies whether to use a subset of the character map instead of the whole map; boolean 0 or 1 subset.profile XPath expression that specifies what subset of the character map to use uri URI for a character map boxbackup/docs/xsl-generic/common/eo.xml0000664000175000017500000012263711175136613021116 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/no.xml0000664000175000017500000012440411175136613021121 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/tl.xml0000664000175000017500000012462411175136613021130 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/l10n.xml0000664000175000017500000000431711175136613021257 0ustar siretartsiretart ]> ⁡ &am; &ar; &az; &bg; &bn; &bs; &ca; &cs; &cy; &da; &de; ⪙ &en; &eo; &es; &et; &eu; &fa; &fi; &fr; &ga; &gu; &he; &hi; &hr; &hu; &id; ⁢ &ja; &kn; &ko; &la; &lit; &lv; &mn; &nl; &nn; &no; ∨ &pa; &pl; &pt; &pt_br; &ro; &ru; &sk; &sl; &sq; &sr; &sr_Latn; &sv; &ta; &th; &tl; &tr; &uk; &vi; &xh; &zh_cn; &zh_tw; boxbackup/docs/xsl-generic/common/hr.xml0000664000175000017500000012455711175136613021127 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/am.xml0000664000175000017500000012750411175136613021106 0ustar siretartsiretart áˆáˆáŠ­á‰¶á‰½ A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/ko.xml0000664000175000017500000012455111175136613021121 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/subtitles.xsl0000664000175000017500000001406611175136613022533 0ustar siretartsiretart Provides access to element subtitles Processing an element in the subtitle.markup mode produces the subtitle of the element. Request for subtitle of unexpected element: ???SUBTITLE??? boxbackup/docs/xsl-generic/common/az.xml0000664000175000017500000007350611175136613021125 0ustar siretartsiretart İşarÉ™lÉ™r A a B b C c Ç ç D d E e e e Æ É™ G g Äž ÄŸ H h X x I ı İ i J j K k Q q L l M m N n O o Ö ö P p R r S s Åž ÅŸ T t U u Ü ü V v Y y Z z boxbackup/docs/xsl-generic/common/sv.xml0000664000175000017500000007367411175136613021151 0ustar siretartsiretart A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z Ã… Ã¥ Ä ä Ö ö boxbackup/docs/xsl-generic/common/fr.xml0000664000175000017500000007554011175136613021122 0ustar siretartsiretart Symboles A a à À â Â Æ æ B b C c ç D d E e ê Ê é É è È ë Ë € F f G g H h I i ÃŽ î à ï J j K k L l M m N n O o Ö ö Å’ Å“ P p Q q R r S s T t U u Ù ù Û û Ü ü V v W w X x Y y Z z boxbackup/docs/xsl-generic/common/entities.ent0000664000175000017500000001532711175136613022322 0ustar siretartsiretart normalize.sort.input normalize.sort.output '> boxbackup/docs/xsl-generic/common/targetdatabase.dtd0000664000175000017500000000231711175136613023431 0ustar siretartsiretart boxbackup/docs/xsl-generic/common/th.xml0000664000175000017500000013226411175136613021123 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/sr_Latn.xml0000664000175000017500000007376311175136613022122 0ustar siretartsiretart Simboli A a B b C c ÄŒ Ä Ä† ć D d DŽ Dž dž Ä Ä‘ E e F f G g H h I i J j K k L l LJ Lj lj M m N n NJ Nj nj O o P p Q Q R r S s Å  Å¡ T t U u V v W w X x Y y Z z Ž ž boxbackup/docs/xsl-generic/common/eu.xml0000664000175000017500000012571411175136613021123 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/en.xml0000664000175000017500000012236311175136613021111 0ustar siretartsiretart Symbols A a À à à á  â à ã Ä ä Ã… Ã¥ Ä€ Ä Ä‚ ă Ä„ Ä… Ç ÇŽ Çž ÇŸ Ç  Ç¡ Ǻ Ç» È€ È È‚ ȃ Ȧ ȧ Ḁ Ḡẚ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ B b Æ€ Æ É“ Æ‚ ƃ Ḃ ḃ Ḅ ḅ Ḇ ḇ C c Ç ç Ć ć Ĉ ĉ ÄŠ Ä‹ ÄŒ Ä Æ‡ ƈ É• Ḉ ḉ D d ÄŽ Ä Ä Ä‘ ÆŠ É— Æ‹ ÆŒ Ç… Dz È¡ É– Ḋ ḋ Ḍ ḠḎ ḠḠḑ Ḓ ḓ E e È è É é Ê ê Ë ë Ä’ Ä“ Ä” Ä• Ä– Ä— Ę Ä™ Äš Ä› È„ È… Ȇ ȇ Ȩ È© Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḠẸ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ỠỂ ể Ễ á»… Ệ ệ F f Æ‘ Æ’ Ḟ ḟ G g Äœ Ä Äž ÄŸ Ä  Ä¡ Ä¢ Ä£ Æ“ É  Ǥ Ç¥ Ǧ ǧ Ç´ ǵ Ḡ ḡ H h Ĥ Ä¥ Ħ ħ Èž ÈŸ ɦ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ẖ I i ÃŒ ì à í ÃŽ î à ï Ĩ Ä© Ī Ä« Ĭ Ä­ Ä® į İ Æ— ɨ Ç Ç Èˆ ȉ ÈŠ È‹ Ḭ ḭ Ḯ ḯ Ỉ ỉ Ị ị J j Ä´ ĵ ǰ Ê K k Ķ Ä· Ƙ Æ™ Ǩ Ç© Ḱ ḱ Ḳ ḳ Ḵ ḵ L l Ĺ ĺ Ä» ļ Ľ ľ Ä¿ Å€ Å Å‚ Æš Lj È´ É« ɬ É­ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ M m ɱ Ḿ ḿ á¹€ ṠṂ ṃ N n Ñ ñ Ń Å„ Å… ņ Ň ň Æ É² Æž È  Ç‹ Ǹ ǹ ȵ ɳ Ṅ á¹… Ṇ ṇ Ṉ ṉ Ṋ ṋ O o Ã’ ò Ó ó Ô ô Õ õ Ö ö Ø ø ÅŒ Å ÅŽ Å Å Å‘ ÆŸ Æ  Æ¡ Ç‘ Ç’ Ǫ Ç« Ǭ Ç­ Ǿ Ç¿ ÈŒ È ÈŽ È Èª È« Ȭ È­ È® ȯ Ȱ ȱ Ṍ ṠṎ ṠṠṑ á¹’ ṓ Ọ ỠỎ ỠỠố á»’ ồ á»” ổ á»– á»— Ộ á»™ Ớ á»› Ờ ỠỞ ở á»  ỡ Ợ ợ P p Ƥ Æ¥ á¹” ṕ á¹– á¹— Q q Ê  R r Å” Å• Å– Å— Ř Å™ È È‘ È’ È“ ɼ ɽ ɾ Ṙ á¹™ Ṛ á¹› Ṝ ṠṞ ṟ S s Åš Å› Åœ Å Åž ÅŸ Å  Å¡ Ș È™ Ê‚ á¹  ṡ á¹¢ á¹£ Ṥ á¹¥ Ṧ á¹§ Ṩ ṩ T t Å¢ Å£ Ť Å¥ Ŧ ŧ Æ« Ƭ Æ­ Æ® ʈ Èš È› ȶ Ṫ ṫ Ṭ á¹­ á¹® ṯ á¹° á¹± ẗ U u Ù ù Ú ú Û û Ü ü Ũ Å© Ū Å« Ŭ Å­ Å® ů Ű ű Ų ų Ư ư Ç“ Ç” Ç• Ç– Ç— ǘ Ç™ Çš Ç› Çœ È” È• È– È— á¹² á¹³ á¹´ á¹µ á¹¶ á¹· Ṹ á¹¹ Ṻ á¹» Ụ ụ Ủ á»§ Ứ ứ Ừ ừ Ử á»­ á»® ữ á»° á»± V v Ʋ Ê‹ á¹¼ á¹½ á¹¾ ṿ W w Å´ ŵ Ẁ ẠẂ ẃ Ẅ ẅ Ẇ ẇ Ẉ ẉ ẘ X x Ẋ ẋ Ẍ ẠY y à ý ÿ Ÿ Ŷ Å· Ƴ Æ´ Ȳ ȳ Ẏ Ạẙ Ỳ ỳ á»´ ỵ á»¶ á»· Ỹ ỹ Z z Ź ź Å» ż Ž ž Ƶ ƶ Ȥ È¥ Ê Ê‘ Ạẑ Ẓ ẓ Ẕ ẕ boxbackup/docs/xsl-generic/common/refentry.xml0000664000175000017500000005365011175136613022347 0ustar siretartsiretart Common » Refentry Metadata Template Reference $Id: refentry.xsl 7056 2007-07-17 13:56:09Z xmldoc $ Introduction This is technical reference documentation for the “refentry metadata†templates in the DocBook XSL Stylesheets. This is not intended to be user documentation. It is provided for developers writing customization layers for the stylesheets. Currently, only the manpages stylesheets make use of these templates. They are, however, potentially useful elsewhere. get.refentry.metadata Gathers metadata from a refentry and its ancestors <xsl:template name="get.refentry.metadata"> <xsl:param name="refname"/> <xsl:param name="info"/> <xsl:param name="prefs"/> ... </xsl:template> <para>Reference documentation for particular commands, functions, etc., is sometimes viewed in isolation from its greater "context". For example, users view Unix man pages as, well, individual pages, not as part of a "book" of some kind. Therefore, it is sometimes necessary to embed "context" information in output for each <tag>refentry</tag>.</para> <para>However, one problem is that different users mark up that context information in different ways. Often (usually), the context information is not actually part of the content of the <tag>refentry</tag> itself, but instead part of the content of a parent or ancestor element to the the <tag>refentry</tag>. And even then, DocBook provides a variety of elements that users might potentially use to mark up the same kind of information. One user might use the <tag>productnumber</tag> element to mark up version information about a particular product, while another might use the <tag>releaseinfo</tag> element.</para> <para>Taking all that in mind, the <function>get.refentry.metadata</function> template tries to gather metadata from a <tag>refentry</tag> element and its ancestor elements in an intelligent and user-configurable way. The basic mechanism used in the XPath expressions throughout this stylesheet is to select the relevant metadata from the *info element that is closest to the actual <tag>refentry</tag> – either on the <tag>refentry</tag> itself, or on its nearest ancestor.</para> <note> <para>The <function>get.refentry.metadata</function> template is actually just sort of a "driver" template; it calls other templates that do the actual data collection, then returns the data as a set.</para> </note> </refsect1><refsect1><title>Parameters refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing user preferences (from global stylesheet parameters) Returns Returns a node set with the following elements. The descriptions are verbatim from the man(7) man page. title the title of the man page (e.g., MAN) section the section number the man page should be placed in (e.g., 7) date the date of the last revision source the source of the command manual the title of the manual (e.g., Linux Programmer's Manual) get.refentry.title Gets title metadata for a refentry <xsl:template name="get.refentry.title"> <xsl:param name="refname"/> ... </xsl:template> <para>The <literal>man(7)</literal> man page describes this as "the title of the man page (e.g., <literal>MAN</literal>). This differs from <tag>refname</tag> in that, if the <tag>refentry</tag> has a <tag>refentrytitle</tag>, we use that as the <tag>title</tag>; otherwise, we just use first <tag>refname</tag> in the first <tag>refnamediv</tag> in the source.</para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry Returns Returns a title node. get.refentry.section Gets section metadata for a refentry <xsl:template name="get.refentry.section"> <xsl:param name="refname"/> <xsl:param name="quiet" select="0"/> ... </xsl:template> <para>The <literal>man(7)</literal> man page describes this as "the section number the man page should be placed in (e.g., <literal>7</literal>)". If we do not find a <tag>manvolnum</tag> specified in the source, and we find that the <tag>refentry</tag> is for a function, we use the section number <literal>3</literal> ["Library calls (functions within program libraries)"]; otherwise, we default to using <literal>1</literal> ["Executable programs or shell commands"].</para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry quiet If non-zero, no "missing" message is emitted Returns Returns a string representing a section number. get.refentry.date Gets date metadata for a refentry <xsl:template name="get.refentry.date"> <xsl:param name="refname"/> <xsl:param name="info"/> <xsl:param name="prefs"/> ... </xsl:template> <para>The <literal>man(7)</literal> man page describes this as "the date of the last revision". If we cannot find a date in the source, we generate one.</para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns Returns a date node. get.refentry.source Gets source metadata for a refentry <xsl:template name="get.refentry.source"> <xsl:param name="refname"/> <xsl:param name="info"/> <xsl:param name="prefs"/> ... </xsl:template> <para>The <literal>man(7)</literal> man page describes this as "the source of the command", and provides the following examples: <itemizedlist> <listitem> <para>For binaries, use something like: GNU, NET-2, SLS Distribution, MCC Distribution.</para> </listitem> <listitem> <para>For system calls, use the version of the kernel that you are currently looking at: Linux 0.99.11.</para> </listitem> <listitem> <para>For library calls, use the source of the function: GNU, BSD 4.3, Linux DLL 4.4.1.</para> </listitem> </itemizedlist> </para> <para>The <literal>solbook(5)</literal> man page describes something very much like what <literal>man(7)</literal> calls "source", except that <literal>solbook(5)</literal> names it "software" and describes it like this: <blockquote> <para>This is the name of the software product that the topic discussed on the reference page belongs to. For example UNIX commands are part of the <literal>SunOS x.x</literal> release.</para> </blockquote> </para> <para>In practice, there are many pages that simply have a version number in the "source" field. So, it looks like what we have is a two-part field, <replaceable>Name</replaceable> <replaceable>Version</replaceable>, where: <variablelist> <varlistentry> <term>Name</term> <listitem> <para>product name (e.g., BSD) or org. name (e.g., GNU)</para> </listitem> </varlistentry> <varlistentry> <term>Version</term> <listitem> <para>version name</para> </listitem> </varlistentry> </variablelist> Each part is optional. If the <replaceable>Name</replaceable> is a product name, then the <replaceable>Version</replaceable> is probably the version of the product. Or there may be no <replaceable>Name</replaceable>, in which case, if there is a <replaceable>Version</replaceable>, it is probably the version of the item itself, not the product it is part of. Or, if the <replaceable>Name</replaceable> is an organization name, then there probably will be no <replaceable>Version</replaceable>. </para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns Returns a source node. get.refentry.source.name Gets source-name metadata for a refentry <xsl:template name="get.refentry.source.name"> <xsl:param name="refname"/> <xsl:param name="info"/> <xsl:param name="prefs"/> ... </xsl:template> <para>A "source name" is one part of a (potentially) two-part <replaceable>Name</replaceable> <replaceable>Version</replaceable> source field. For more details, see the documentation for the <function>get.refentry.source</function> template.</para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns Depending on what output method is used for the current stylesheet, either returns a text node or possibly an element node, containing "source name" data. get.refentry.version Gets version metadata for a refentry <xsl:template name="get.refentry.version"> <xsl:param name="refname"/> <xsl:param name="info"/> <xsl:param name="prefs"/> ... </xsl:template> <para>A "version" is one part of a (potentially) two-part <replaceable>Name</replaceable> <replaceable>Version</replaceable> source field. For more details, see the documentation for the <function>get.refentry.source</function> template.</para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns Depending on what output method is used for the current stylesheet, either returns a text node or possibly an element node, containing "version" data. get.refentry.manual Gets source metadata for a refentry <xsl:template name="get.refentry.manual"> <xsl:param name="refname"/> <xsl:param name="info"/> <xsl:param name="prefs"/> ... </xsl:template> <para>The <literal>man(7)</literal> man page describes this as "the title of the manual (e.g., <citetitle>Linux Programmer's Manual</citetitle>)". Here are some examples from existing man pages: <itemizedlist> <listitem> <para><citetitle>dpkg utilities</citetitle> (<command>dpkg-name</command>)</para> </listitem> <listitem> <para><citetitle>User Contributed Perl Documentation</citetitle> (<command>GET</command>)</para> </listitem> <listitem> <para><citetitle>GNU Development Tools</citetitle> (<command>ld</command>)</para> </listitem> <listitem> <para><citetitle>Emperor Norton Utilities</citetitle> (<command>ddate</command>)</para> </listitem> <listitem> <para><citetitle>Debian GNU/Linux manual</citetitle> (<command>faked</command>)</para> </listitem> <listitem> <para><citetitle>GIMP Manual Pages</citetitle> (<command>gimp</command>)</para> </listitem> <listitem> <para><citetitle>KDOC Documentation System</citetitle> (<command>qt2kdoc</command>)</para> </listitem> </itemizedlist> </para> <para>The <literal>solbook(5)</literal> man page describes something very much like what <literal>man(7)</literal> calls "manual", except that <literal>solbook(5)</literal> names it "sectdesc" and describes it like this: <blockquote> <para>This is the section title of the reference page; for example <literal>User Commands</literal>.</para> </blockquote> </para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry info A set of info nodes (from a refentry element and its ancestors) prefs A node containing users preferences (from global stylesheet parameters) Returns Returns a manual node. get.refentry.metadata.prefs Gets user preferences for refentry metadata gathering <xsl:template name="get.refentry.metadata.prefs"/> <para>The DocBook XSL stylesheets include several user-configurable global stylesheet parameters for controlling <tag>refentry</tag> metadata gathering. Those parameters are not read directly by the other <tag>refentry</tag> metadata-gathering templates. Instead, they are read only by the <function>get.refentry.metadata.prefs</function> template, which assembles them into a structure that is then passed to the other <tag>refentry</tag> metadata-gathering templates.</para> <para>So the, <function>get.refentry.metadata.prefs</function> template is the only interface to collecting stylesheet parameters for controlling <tag>refentry</tag> metadata gathering.</para> </refsect1><refsect1><title>Parameters There are no local parameters for this template; however, it does rely on a number of global parameters. Returns Returns a manual node. set.refentry.metadata Sets content of a refentry metadata item <xsl:template name="set.refentry.metadata"> <xsl:param name="refname"/> <xsl:param name="info"/> <xsl:param name="contents"/> <xsl:param name="context"/> <xsl:param name="preferred"/> ... </xsl:template> <para>The <function>set.refentry.metadata</function> template is called each time a suitable source element is found for a certain metadata field.</para> </refsect1><refsect1><title>Parameters refname The first refname in the refentry info A single *info node that contains the selected source element. contents A node containing the selected source element. context A string describing the metadata context in which the set.refentry.metadata template was called: either "date", "source", "version", or "manual". Returns Returns formatted contents of a selected source element. boxbackup/docs/xsl-generic/VERSION0000664000175000017500000001032511175136613017537 0ustar siretartsiretart docbook-xsl 1.72.0 6549 $Revision: 7388 $ $URL: https://docbook.svn.sourceforge.net/svnroot/docbook/trunk/xsl/VERSION $ DocBook XSL Stylesheets 1.73.2 Minor bugfixes http://sourceforge.net/projects/docbook/ http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.tar.gz?download http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.zip?download http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.bz2?download http://sourceforge.net/project/shownotes.php?release_id={SFRELID} http://docbook.svn.sourceforge.net/viewvc/docbook/ http://lists.oasis-open.org/archives/docbook-apps/ This is a bug-fix update to the 1.73.1 release. You must specify the sf-relid as a parameter. : : : boxbackup/docs/images/0000775000175000017500000000000011652362373015517 5ustar siretartsiretartboxbackup/docs/images/bblogo-alpha.xcf0000664000175000017500000026743411163413441020556 0ustar siretartsiretartgimp xcf fileKABBQJgamma0.55000001192092896gimp-image-grid(style intersections) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) Õƒœ·}¾åF¹RVÈ[ÂdßmúKABackground copyÿ     HKA¡ƒxƒ„ƒKAÝ 6 N-fF~úƒHƒPƒXƒ`ƒhƒpÿæÿ)æÿþãææÿþáß'ßöæÝd{…UŽîãÿ ÿûæßI7;&;õæÛ007rëÝáÿÿøæÝ2"++;I#IôæÛ7+;"rØÒÛâÿÿøæÝ;+1\´Õ#ÕôæÛ728"kÃÃÔáÿÿøæÝ;28fÒï#ïôæÛ;+1"d»»Òáÿÿ÷æÝ;18Iެ´"´ôæÛ;+1"d»»ÒßÿÿøæÝD1@?I\\!fôæÛ;+1"d»»ÒßÿÿøæÝD8?IIPP VôæÛ;+1"d»»ÒßÿÿõæÝD8IPVV\\f fôæÛ;+1"d»»Òßÿÿ÷æÝ@@PV\fgg môæÛ;+1"d»»ÒßÿÿöæßI?P\fggmmxmôæÛ;+1"d»»ÒßÿÿøèØI?V\fggmx…xmôæÛ;+1"d»»ÒßÿÿúèÇ@?P\\fgx… •…xümggffôæÛ;+1"d»»Òßÿÿêë»8?IIPfŽ  ¨¨ ˜sgmx……• •…ëxmggsŽ˜  æÛ;+1"d»»Òßÿÿøî¬18@k˜±±´»ú¨Žgx…• •ø…xmgs˜¨±±ó´æÛ;+1"d»»Òßÿÿùî "8Ž»±±û´»ÃÃÇÇÃû gx…••ú…xmŽ ±±ð´»»ÃæÛ;+1"d»»Òßÿÿóî…+¬Ï´±´ÃÇÏÔØØøÔÒϬgx…••û…ms ±±í´»ÃÇËÏÔæÛ;+1"d»»Òßÿÿñãë…¨ßû»ÇÒØÝáâãã÷âßÛØ±gx…••…üm…¨±±ì»ÃËÒØÛßáæÛ;+1"d»»ÒßÿÿõÛÿÔÇÃËØßâãÿÿÚãßÝ gx…••…xm…¬±±´»ËÔÛßâãÿÿæÛ;+1"d»»ÒßÿÿøæáØÏÒØáÿ ÿãìÔsmx……xgs¬±±»ÃÏØáããÿÿôæÛ;+1"d»»Òßÿ ÿúâÝÛÝáÿ ÿïè¬\mxxgf¨±±»ÇÔÝâÿÿôæÛ;+1"d»»Òßÿ ÿúãâáâãÿ ÿðæÝf\ggVޱ±´ÇÔÝãÿÿôæÛ;+1"d»»Òßÿÿòë˜I\Pk¬±´ÃÒÝãÿÿôæÛ;+1"d»»ÒßÿÿóëÃII?…±±»ÎÝãÿÿôæÛ;+1"d»»Òßÿ ÿ ÿôæá\8I¨±´ÇØáÿÿóæÛ;+1"d»»Òßÿ ÿ ÿõæèr'd»±»ÏßÿÿôæÛ;+1"d»»Òßÿÿú0""; ÿöîŽ{̫̯áÿÿó'æÛ;+1"d»»ÒßÿÿøD"181"N ÿ÷î "˜Ã»ËÛÿÿò+'æÛ;+1"d»»Òßÿÿø"2@P12"þâÿÿ÷ë¬'¨ÇÃÏßÿÿò"1æÛ;+1"d»»Òßÿÿø"18@81"þßÿÿ÷ë»0»ËÇÔáÿÿò'8æÛ;+1"d»»Òßÿÿý"188ý2"ýÛãÿÿ÷ë»0»ÎËØáÿÿò'8æÛ;+1"d»»Òßÿÿø'2881+0ýØâÿÿ÷ë¬0±ÒËØâÿÿò"1æÛ;+1"d»»Òßÿÿød"21+"kýÔâÿÿ÷ë´d±ÒÎØãÿÿòI"æÛ;+1"d»»Òßÿÿú\'"'dýØâÿÿ÷èÇ ´ÒËØãÿÿóUæÛ;+1"d»»Òßÿ ÿüÎÝãÿÿæøŽr{ÎËØâÿ ÿôæÛ;+1"d»»Òßÿ ÿýØáÿÿöæß;'7»ËÔáÿ ÿôæÛ;+1"d»»Òßÿ ÿýßãÿÿöë´'+"…ËÒßÿ ÿóæÛ;+1"d»»Òßÿ ÿ÷ãßÛØØÝáãÿÿöî{"1+I»ÎÛÿ ÿôæÛ;+1"d»»Òßÿ ÿãüâããÿÿôèØ;1{8"{ÎØáÿ ÿôæÛ;+1"d»»ÒßÿÿóîŽ"I»r'0 ØÝãÿ ÿôæÛ;+1"d»»ÒßÿÿýãèÿÿòëÎ;+I¨{1+;´ßáÿ ÿôæÛ;+1"d»»ÒßÿÿúèÇ îãÿ ÿèòd'88?@81'D»ããÿÿôæÛ;+1"d»»ÒßÿÿøèÃ0 îÿãÿ ÿúèë{'188ù1'; æèÿÿôæÛ7+2"d»»Òßÿÿ÷èÔ0"˜èæãÿÿëû{"2;88õ;12''rÏîèæÿÿôæÛ0""\»»Òßÿÿ÷æß0dÃæÿÿøëÎd"++22+÷'"2{ÃææÿÿôæÝN;;2r»»Òßÿÿöæá\;;7N ßÿÿü±\;D D;7ûN…ÃãÿÿöæÝË»±±ÃÒáÿÿõæÝÎûÃÔÝáãÿÿ÷ßáãßØË»´±±ö´»ÃÇÔÝáããÿÿ÷ÝÏû»ÇÔáÿ ÿöÝÏûÃÎØßãÿÿûáØÏû »øÃÇÏØÝáãÿÿ÷ߨÏËÎÒÛãÿ ÿöáØÏÎÎÒØßãÿÿúãßÛÔÎË ËùÎÒØÝáãÿÿøãáÝÛÝÝâÿ ÿýãáÝÝüßâãÿÿûãßÝÝÛ ÛÝûßáããÿÿþãÿ ÿþãÿÿþãÿÿÿèÿ)èÿþåèèÿþãâ'âöèàxŒ•kïåÿ ÿûèâbPT&TõèÞJJP1„ìàãÿÿøèàM@JJTb#bôèÞPJT@„Ü×ÞäÿÿøèàTJRq¿Ü#ÜôèÞPMZ@ËËÙãÿÿøèàTMZ‰×ò#òôèÞTJR@xÄÄ×ãÿÿ÷èàTRZn·¿"¿ôèÞTJR@xÄÄ×âÿÿøèà\R`dnƒƒ!‰ôèÞTJR@xÄÄ×âÿÿøèà\Zdnnuu |ôèÞTJR@xÄÄ×âÿÿõèà\Znu||ƒƒ‰ ‰ôèÞTJR@xÄÄ×âÿÿ÷èà``u|ƒ‰’’ šôèÞTJR@xÄÄ×âÿÿöèâbduƒ‰’’šš¨šôèÞTJR@xÄÄ×âÿÿøéÜbd|ƒ‰’’š¨¸¨šôèÞTJR@xÄÄ×âÿÿúéÎ`duƒƒ‰’¨¸ ͸¨üš’’‰‰ôèÞTJR@xÄÄ×âÿÿêìÄZdnnu‰­­³³­¥•’š¨¸¸Í Í¸ë¨š’’•¥­­èÞTJR@xÄÄ×âÿÿøï·RZ`¥»»¿Äú³’¨¸Í Íø¸¨š’•¥³»»ó¿èÞTJR@xÄÄ×âÿÿùï­@ZÄ»»û¿ÄËËÎÎËû­’¨¸ÍÍú¸¨š­»»ð¿ÄÄËèÞTJR@xÄÄ×âÿÿóï•J·Õ¿»¿ËÎÕÙÜÜøÙ×Õ·’¨¸ÍÍû¸š•­»»í¿ÄËÎÑÕÙèÞTJR@xÄÄ×âÿÿñå앳âËÄÄÎ×Üàãäåå÷äâÞÜ»’¨¸Í͸ü𕳻»ìÄËÑ×ÜÞâãèÞTJR@xÄÄ×âÿÿõÞÿÙÎËÑÜâäåÿÿÚåâà­’¨¸Í͸¨š•·»»¿ÄÑÙÞâäåÿÿèÞTJR@xÄÄ×âÿÿøèãÜÕ×Üãÿ ÿåìÙ•š¨¸¸¨’•·»»ÄËÕÜãååÿÿôèÞTJR@xÄÄ×âÿ ÿúäàÞàãÿ ÿïé·ƒš¨¨’‰³»»ÄÎÙàäÿÿôèÞTJR@xÄÄ×âÿ ÿþåääþåÿ ÿðèà‰ƒ’’|»»¿ÎÙàåÿÿôèÞTJR@xÄÄ×âÿÿòì¥nƒu·»¿Ë×àåÿÿôèÞTJR@xÄÄ×âÿÿóìËnnd•»»ÄÓàåÿÿôèÞTJR@xÄÄ×âÿ ÿú   ÿ ÿôèãqZb³»¿ÎÜäÿÿó èÞTJR@xÄÄ×âÿ ÿý "!!ý ÿ ÿõèé„DxÄ»ÄÕâÿÿò "èÞTJR@xÄÄ×âÿÿõ"J@@8T" ÿ ÿöï8ŒË¿ËÜäÿÿñ #DèÞTJR@xÄÄ×âÿÿó\@RZR@dÿ ÿ÷ï­@¥ËÄÑÞÿÿð JDèÞTJR@xÄÄ×âÿÿò @M`uRM@ äÿÿ÷ì·D³ÎËÕâÿÿð!@RèÞTJR@xÄÄ×âÿÿò0"@RZ`ZR@!âÿÿ÷ìÄJÄÑÎÙãÿÿï !DZèÞTJR@xÄÄ×âÿÿû"@RZZøM@!"Þåÿÿ÷ìÄJÄÓÑÜäÿÿï !DZèÞTJR@xÄÄ×âÿÿñ "DMZZRJJ#%"Üäÿÿ÷ì·J»×ÑÜäÿÿð"@RèÞTJR@xÄÄ×âÿÿñ x@MRJ@%!"Ùäÿÿ÷ì¿x»×ÓÜåÿÿð b@èÞTJR@xÄÄ×âÿÿò qD@Dx"!% Üäÿÿ÷éέ¿×ÑÜåÿÿñ kèÞTJR@xÄÄ×âÿ ÿú  " !!û"Óàåÿÿèø„ŒÓÑÜäÿ ÿò èÞTJR@xÄÄ×âÿ ÿõ"%%"ÜãÿÿöèâTDPÄÑÙãÿ ÿòèÞTJR@xÄÄ×âÿ ÿõ âåÿÿöì¿DJ@•Ñ×âÿ ÿóèÞTJR@xÄÄ×âÿ ÿ÷åâÞÜÜàãåÿÿöïŒ@RJbÄÓÞÿ ÿôèÞTJR@xÄÄ×âÿ ÿåüäååÿÿôéÜTRŒZ@ŒÓÜäÿ ÿôèÞTJR@xÄÄ×âÿÿóï@bÄ„DJ­Üàåÿ ÿôèÞTJR@xÄÄ×âÿÿýåéÿÿòìÓTJn³ŒRJT¿âãÿ ÿôèÞTJR@xÄÄ×âÿÿúéέïåÿ ÿéòxDZZd`ZRD\ÄååÿÿôèÞTJR@xÄÄ×âÿÿøéËJ­ïÿåÿ ÿúéìŒDRZZùRDT­èéÿÿôèÞPJM@xÄÄ×âÿÿ÷éÙJ@¥éèåÿÿìûŒ@MTZZõTRMDD„ÕïéèÿÿôèÞJ@@1qÄÄ×âÿÿ÷èâJ18xËèÿÿøìÓx8@JJMMJ÷D@1MŒËèèÿÿôèàdTTM„ÄÄ×âÿÿöèãqTTPd­âÿÿü»qT\ \TPûd•ËåÿÿöèàÑÄ»»Ë×ãÿÿõèàÓËÄËÙàãåÿÿ÷âãåâÜÑÄ¿»»ö¿ÄËÎÙàãååÿÿ÷àÕËÄÄÎÙãÿ ÿöàÕËÄËÓÜâåÿÿûãÜÕËÄ ÄøËÎÕÜàãåÿÿ÷âÜÕÑÓ×Þåÿ ÿöãÜÕÓÓ×ÜâåÿÿúåâÞÙÓÑ ÑùÓ×ÜàãåÿÿøåãàÞààäÿ ÿýåãààüâäåÿÿûåâààÞ Þàûâãååÿÿþåÿ ÿþåÿÿþåÿÿÿîÿ)îÿþíîîÿþëê'êöî騵»¡Àòíÿ ÿûîê¡•&•õîè~±ñéëÿÿøî鑉‘‘•¡#¡ôîè‘•‰±æâèìÿÿøîé•‘˜¥Õê#êôî葞‰­ÙÙãëÿÿøîé•‘ž¼âø#øôîè•‘˜‰¨ÕÕâëÿÿ÷î镘ž¬ÀÏÕ"Õôîè•‘˜‰¨ÕÕâêÿÿøî阘 ¦¬¹¹!¼ôîè•‘˜‰¨ÕÕâêÿÿøî阞¦¬¬±± µôîè•‘˜‰¨ÕÕâêÿÿõî阞¬±µµ¹¹¼ ¼ôîè•‘˜‰¨ÕÕâêÿÿ÷îé  ±µ¹¼Â Èôîè•‘˜‰¨ÕÕâêÿÿöîꡦ±¹¼ÂÂÈÈÑÈôîè•‘˜‰¨ÕÕâêÿÿøï桦µ¹¼ÂÂÈÑÜÑÈôîè•‘˜‰¨ÕÕâêÿÿúïÜ ¦±¹¹¼ÂÑÜ éÜÑüȼ¼ôîè•‘˜‰¨ÕÕâêÿÿêñÕž¦¬¬±¼ÀÉÉÌÌÉÄÀÂÈÑÜÜé éÜëÑÈÂÂÀÀÄÉÉîè•‘˜‰¨ÕÕâêÿÿøòϘž ­ÄÎÎÕúÌÀÂÑÜé éøÜÑÈÂÀÄÌÎÎóÕîè•‘˜‰¨ÕÕâêÿÿùòɉžÀÕÎÎÕÙÜÙûÉÂÑÜééúÜÑÈÀÉÎÎÕóÙîè•‘˜‰¨ÕÕâêÿÿóò»‘ÏàÕÎÕÙÜàãææøãâàÏÂÑÜééûÜÈÀÉÎÎÕïÙÜÝàãîè•‘˜‰¨ÕÕâêÿÿñíñ»ÌêÙÕÕÜâæéëìíí÷ìêèæÎÂÑÜééÜüÈ»ÌÎÎìÕÙÝâæèêëîè•‘˜‰¨ÕÕâêÿÿõèÿãÜÙÝæêìíÿÿÚíêéÉÂÑÜééÜÑÈ»ÏÎÎÕÕÝãèêìíÿÿîè•‘˜‰¨ÕÕâêÿÿøîëæàâæëÿ ÿíìãÀÈÑÜÜÑÂÀÏÎÎÕÙàæëííÿÿôîè•‘˜‰¨ÕÕâêÿ ÿúìéèéëÿ ÿïïϹÈÑѼÌÎÎÕÜãéìÿÿôîè•‘˜‰¨ÕÕâêÿ ÿúíìëìíÿ ÿðî鼹µÀÎÎÕÜãéíÿÿôîè•‘˜‰¨ÕÕâêÿÿòñĬ¹±­ÏÎÕÙâéíÿÿôîè•‘˜‰¨ÕÕâêÿÿóñÙ¬¬¦»ÎÎÕßéíÿÿôîè•‘˜‰¨ÕÕâêÿ ÿúMÿ5þÿüðÿÿ4ÿ<ÿþ<7 ÿ5ýÿÿÿ5þÿÿ<ÿ6À????? ¥ R)KASelection Mask copy eRKAermÖmâmîKAe®i‚lŽm¾mÂmÆmÊmÌmÎmÐmÒmÔÿ)ÿÿ+ÿÿ ÿ)ÿÿ ÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿÿ)ÿÿýÿxxðþxxÿÿþÿÿþðþðÿÿÿþÿ ÿþxþxÿÿÿüÿÿÿ þxÿ ÿÿÿýðÿÿ þðÿ ÿÿ ýÿÿÿÿþx ÿÿÿ ÿ ÿ ÿÿÿ ÿ ÿþðÿÿÿÿ ÿ ÿÿÿÿþÿÿÿÿÿÿ ÿÿ úá´á´ÿ ÿþðÿÿ ÿÿ ýáÿýÿá ÿÿ ÿÿ ý´ÿÿ þðÿ ÿÿÿÿ ûðxÿÿ ÿÿý´ÿþ<ÿÿ ÿýðÿÿ ÿÿþ¥ÿ ÿþÿÿ ÿÿþ´ÿÿþÿÿ ÿÿýáÿþ´ÿÿÿþÿÿ ÿÿÿÿÿþÿÿ ÿÿ ÿÿÿþxÿÿ ÿÿ ÿý<ÿÿÿÿ ÿÿ ÿÿÿ ÿÿ ÿÿÿ ÿÿ ÿ þðÿ ÿÿÿ ÿÿ ÿÿþðþxÿÿ ÿÿÿÿ ÿÿÿ ÿ ÿÿ þÿ ÿ üxÿÿÿÿ ÿ ÿûÿðÿÿÿÿ ÿÿÿÿÿýÿþÿÿÿ ÿÿÿ ÿÿÿ ÿÿÿ ÿÿþÿ þÿþÿ:ÿÿþÿ:ÿþÿÿ:ÿþÿ9ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿ:ÿúðxðxx4 ÿþxþxÿÿþðþÃÿÿþ<ÿÿþðÿÿÿÿþðÿÿþ<ÿÿÿÿþÿÿ ÿÿÿÃÿ ÿþðÿÿ ýÿÿÿÿÿ ÿÿ ÿýðxÿÿþðÿ ÿÿÿþÿÿ ÿÿÿýÿðÿ ÿÿÿÿÿ ÿþÿÿþÿÿðÿþðÿÿ ÿÿÿÿ ÿÿÿ ÿ ÿÿÿ ÿ ÿÿÿ ÿ ÿÿþðÿÿ þð ÿÿÿ ÿ ÿýðxÿÿÿþÿÿÿÿÿÿÿÿðÿÿÿÿÿÿ þÿÿþðþxÿÿÿ ÿÿÿÿûÿÿÿ ÿþÿÿÿÿþÿÿ ÿÿ ÿÿÿ ÿÿ üÿðÿÿÿÿ ÿÿ üÿáÿþðÿÿÿþZÿ ÿþÿÿÿÿÿÿ ÿÿÿ ýÿZÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿ ÿ ÿÿ ÿ2€þÿ=þÿ=þÿ=þÿ=þÿ=þÿ=þÿ=þÿ=þÿ=þÿ=þÿÿ5üÿÿÿ5ÿþð9ÿ<þÿÿ8ÿ6ÿ5 ÿ5 ÿ5ÿ7ÿ9ÿ:ÿÿ5ÿÿ5ÿýÿÿÿ5üÿÿÿ5 ÿ5 ÿ5 ÿ5 ÿ5 ÿ5ýÿÿÿ5ýÿÿÿ5üÿÿÿ5þÿÿ;ÿ5þÿÿ5þÿþÿ4ÿ=ÿ<ÿ=ÿ>ÿ5þÿüðÿÿ4ÿ<ÿþ<7 ÿ5ýÿÿÿ5þÿÿ<ÿ6À????? ¥ R)KASelection Mask npKAnnøooKAnÌnÔnÜnànänènìnînðnònônö ÿ ÿÀ????? ¥ R)boxbackup/docs/tools/0000775000175000017500000000000011652362373015412 5ustar siretartsiretartboxbackup/docs/tools/generate_except_xml.pl0000664000175000017500000000327211163413441021763 0ustar siretartsiretart#!/usr/bin/perl -w use strict; open (EXCEPT, "< $ARGV[0]") or die "Can't open $ARGV[0]: $!\n"; open (DOCBOOK, "> $ARGV[1]") or die "Can't open $ARGV[1] for writing: $!\n"; print DOCBOOK < Exception codes EOD my $sectionName; my $sectionNum; my $sectionDesc; my $exceptionCode; my $exceptionShortDesc; my $exceptionLongDesc; while() { next if(m/^#/); chomp; if(m/^EXCEPTION TYPE (\w+) (\d+)/) { $sectionName = ucfirst(lc($1)); $sectionNum = $2; if($sectionName ne "Common") { $sectionDesc = "the " . $sectionName; } else { $sectionDesc = "any"; } print DOCBOOK < $sectionName Exceptions ($sectionNum) These are exceptions that can occur in $sectionDesc module of the system. EOD } # The END TYPE line if(m/^END TYPE$/) { print DOCBOOK " \n \n"; } # The actual exceptions if(m/(\(\d+\/\d+\)) - (\w+ \w+)(?: - )?(.*)$/) { $exceptionCode = $1; $exceptionShortDesc = $2; $exceptionLongDesc = $3; print DOCBOOK " \n "; print DOCBOOK $exceptionCode . ": " . $exceptionShortDesc . ""; if($exceptionLongDesc ne "") { print DOCBOOK " -- " . $exceptionLongDesc; } print DOCBOOK "\n \n"; } } print DOCBOOK "\n"; close EXCEPT; close DOCBOOK; boxbackup/docs/api-notes/0000775000175000017500000000000011652362373016151 5ustar siretartsiretartboxbackup/docs/api-notes/lib_backupclient.txt0000664000175000017500000000214511163443550022200 0ustar siretartsiretartTITLE lib/backupclient Classes used on the store and on the server. See documentation in the files for more details. SUBTITLE BackupStoreDirectory The directory listing class, containing a number of entries, representing files. SUBTITLE BackupStoreFile Handles compressing and encrypting files, and decoding files downloaded from the server. SUBTITLE BackupStoreFilename An encrypted filename. SUBTITLE BackupStoreFilenameClear Derived from BackupStoreFilename, but with the ability to encrypt and decrypt filenames. Client side only. SUBTITLE BackupClientFileAttributes Only used on the client -- the server treats attributes as blocks of opaque data. This reads attributes from files on discs, stores them, encrypts them, and applies them to new files. Also has a static function to generate filename attribute hashes given a struct stat and the filename. SUBTITLE BackupClientRestore Routines to restore files from the server onto the client filesystem. SUBTITLE BackupClientCryptoKeys This reads the key material from disc, and sets up the crypto for storing files, attributes and directories. boxbackup/docs/api-notes/windows_porting.txt0000664000175000017500000001511111163443550022137 0ustar siretartsiretartTITLE Notes on porting the backup client to Windows It should be relatively easy to port the backup client (bbackupd) to Windows. However, the server relies on unlink() behaviour of the UNIX filesystem which is different on the Windows platform, so it will not be so easy to port. An installation of perl is required to build the system. The ActiveState port is the easiest to install. SUBTITLE Build environment The build environment generation script, makebuildenv.pl, uses perl to scan all the files and generate makefiles. It's rather orientated towards UNIX systems with gcc. Probably the easiest way to handle this is to detect the Windows platform, set up a few variables appropriately (in BoxPlatform.pm) and then post-process the generated Makefiles to mould them into something more handy for the MS Windows compiler and toolchain. The script itself is a bit messy. It was fine at first, but then the multi-platform thing got in the way. I do intend to rewrite it at some point in the future. Make sure your new version defines PLATFORM_WIN32 on the compile line. All files #include "Box.h" as the first include file. Use this for pre-compiled headers. Edit BoxPlatform.h to include the Windows headers required, and include a PLATFORM_WIN32 section. The easiest start will be to leave this blank, apart from typedefs for the basic types and any "not supported" #defines you can find. Boring bits of the code, such as exceptions and protocol definitions, are autogenerated using perl scripts. This code should be portable without modification. I have tried to avoid the things I know won't work with the MS compiler, so hopefully the code will be fairly clean. However, it might be a little easier to use the MinGW compiler [ http://www.mingw.org/ ] just to be consistent with the UNIX version. But I hope this won't be necessary. You'll need the latest version of OpenSSL. This was slightly difficult to get to compile last time I tried -- especially if you're determined to use the optimised assembler version. The main difficulty was getting a version which would link properly with the options used in my project, the default libraries selected got in the way. SUBTITLE Porting as UNIX emulation Since the daemon uses so few UNIX system calls and with a limited set of options, it seems to make sense to port it by writing emulations of these functions. It's probably nicest to create a lib/win32 directory, and populate this with header files corresponding to the UNIX header files used. These just contain inline functions which map the UNIX calls to Win32 calls. File/socket handles may have to be translated. -1 is used as a failure return value and by the code internally to mark an invalid socket handle. (0 is a valid socket handle) Of course, some bits of code aren't relevant, so will just be #ifdefed out, or replaced. But this should be minimal. (Only perhaps the small bit relating to filesystem structure -- there aren't really mount points as such.) SUBTITLE File tracking The daemon uses the inode number of a file to keep track of files and directories, so when they're renamed they can be moved efficiently on the store. Some unique (per filesystem) number will have to be found and used instead. It uses the Berkeley DB to store these on disc. It's likely another storage system will need to be used. (It just has to map the file's unique number into to a 8 byte struct.) There is a in-memory implementation for platforms which don't support Berkeley DB, but this isn't so good when the daemon has to be restarted as all the tracking is lost. But it's an easy start. SUBTITLE Expected filesystem behaviour File and directories have (at least) two modification times, for contents and attributes. For files, the contents modification time must change when the contents change, and the attributes time when the attributes change (and may change when the contents change too.) For directories, the contents modification time must change when files or directories are deleted or added. If it changes any more frequently than this, then the client will be slightly less efficient -- it will download the store's directory listing whenever this time changes. The attributes modification time is less important, as the actual attributes are compared and only uploaded if different. SUBTITLE Attributes Attributes means file modification times, flags, and filesystem permissions. The BackupClientFileAttribute class will need to be extended. Allocate another "attribute type" for the Win32 attributes, and then serialise it in a compatible way -- put your new attribute type in the header, and then a serialised network byte order structure in the rest. The different size of block is handled for you, and the server never looks inside. Add code so that under UNIX, Win32 attributes are ignored, and UNIX attributes under Win32. It's probably not necessary to worry too much about these for the first version. Not many people seem to use these attributes anyway. SUBTITLE Times The system uses it's own 64 bit time type -- see BoxTime.h. Everything is translated to this from the various different system time types, and calculated and stored internally in this form. SUBTITLE Daemon as a Service The client is derived from the Daemon class, which implements a daemon. The interface is simple, and it shouldn't be hard to write a compatible class which implements a Windows Service instead. Or cheat and run it as a Win32 application. Note that the daemon expects to be able to read every file it wants, and will abort a scan and upload run if it gets an error. The daemon must therefore be run with sufficient privileges. It runs as root under UNIX. SUBTITLE Command Socket The backup daemon accepts commands from bbackupctl through a UNIX domain socket. When a connection is made, the user ID of the connecting process is checked to see if it's the same user ID as the daemon is running under. This may not have any exact analogue under Win32, so another communications scheme may have to be devised. This is only actually necessary if the client is to be run in snapshot mode. It can be safely left unimplemented if snapshot mode is not required, or the prompts for it to sync with the server are implemented some other way. SUBTITLE NTFS streams If you want to back up NTFS streams, then a generic solution should probably be defined, so that the Mac OS X resource forks can be backed up with the same mechanism. SUBTITLE Source code I work on a slightly different version of the source files. A make distribution script adds the license header and removes private sections of code. This means submitted diffs need a slight bit of translation. boxbackup/docs/api-notes/backup/0000775000175000017500000000000011652362373017416 5ustar siretartsiretartboxbackup/docs/api-notes/Win32_Clients.txt0000664000175000017500000000040411163443550021265 0ustar siretartsiretartThe basic client tools now run on Win32 natively. The port was done by nick@omniis.com. * bbackupd * bbackupquery * bbackupctl Have been ported. bbackupd runs as a NT style service. Known limitations: * File attributes and permissions are not backed up. boxbackup/docs/api-notes/win32_build_on_cygwin_using_mingw.txt0000664000175000017500000000712611445743664025531 0ustar siretartsiretartHow to build Box Backup on Win32 using Cygwin and MinGW By Chris Wilson, 2009-03-31 (To read this document online with better formatting, browse to: [http://www.boxbackup.org/trac/wiki/CompileWithMinGW]) == MinGW C++ == Start by installing Cygwin on your Windows machine from [http://www.cygwin.org/cygwin/]. Make sure to select the following packages during installation: * Devel/automake * Devel/autoconf * Devel/gcc-mingw * Devel/gcc-mingw-core * Devel/gcc-mingw-g++ * Devel/make * Devel/mingw-runtime * Lib/libxml2 * Lib/libxslt (for xsltproc) * Mingw/mingw-zlib * Perl/Perl If you already have Cygwin installed, please re-run the installer and ensure that those packages are installed. You may also want to install the debugger, Devel/gdb. == Base Directory == Choose a directory where you will unpack and compile OpenSSL, Zlib and Box Backup. We will call this the ''base directory''. An example might be: C:\Cygwin\Home\YourUsername Make sure you know the full path to this directory. If your user name has spaces in it, which is quite common on Windows, please rename your home directory to one without any spaces, and change your user's home directory in /etc/passwd to match. == OpenSSL == Download OpenSSL from [http://www.openssl.org/source/openssl-1.0.0a.tar.gz] Open a Cygwin shell, go to the base directory, and unpack OpenSSL: tar xzvf openssl-1.0.0a.tar.gz Configure OpenSSL for MinGW compilation, and build and install it: cd openssl-1.0.0a ./Configure --prefix=/usr/i686-pc-mingw32/ mingw make make install == PCRE == This step is only required to support regular expressions in including/excluding files from backups. However, this is a very useful feature. Download PCRE from [http://prdownloads.sourceforge.net/pcre/pcre-8.10.tar.bz2?download] Open a Cygwin shell, go to the base directory, and unpack PCRE: tar xjvf pcre-8.10.tar.bz2 Configure PCRE for MinGW compilation, and build and install it: cd pcre-8.10 export CFLAGS="-mno-cygwin" export CXXFLAGS="-mno-cygwin" ./configure --prefix=/usr/i686-pc-mingw32 make make install == Download Box Backup == Go back to the base directory, and download the latest Box Backup sources: svn co https://www.boxbackup.org/svn/box/trunk/ trunk Note: If you have problems during the configure or make stage, please try to eliminate one potential source of problems by running the following command to convert all line endings to Unix format: find -type f -not \( -wholename .*svn*. \) -exec dos2unix {} \; == Compile Box Backup == Enter the source directory and configure like this: cd trunk ./infrastructure/mingw/configure.sh make == Installation == Create the destination directory, ''C:\Program Files\Box Backup\bbackupd''. Write a configuration file, keys and certificate on a Unix machine, and copy them into the ''Box Backup'' directory, together with the following files from the ''base directory'': * boxbackup\release\bin\bbackupd\bbackupd.exe * boxbackup\release\bin\bbackupquery\bbackupquery.exe * boxbackup\release\bin\bbackupctl\bbackupctl.exe * openssl\out32dll\libeay32.dll * openssl\out32dll\ssleay32.dll * zlib\zlib1.dll Ensure that the user running Box Backup can read from the ''Box Backup'' directory, and write to the ''bbackupd'' directory inside it. Run Box Backup by double-clicking on it, and check that it connects to the server. If the window opens and closes immediately, it's probably due to a problem with the configuration file - check the Windows Event Viewer for details. See also the service installation and upgrade instructions at [https://www.boxbackup.org/trac/wiki/CompilationOnWindows]. boxbackup/docs/api-notes/raidfile/0000775000175000017500000000000011652362373017730 5ustar siretartsiretartboxbackup/docs/api-notes/raidfile/RaidFileRead.txt0000664000175000017500000000052211163443550022735 0ustar siretartsiretartCLASS RaidFileRead Read a raid file. IOStream interface, plus a few extras, including reading directories and checking that files exist. FUNCTION RaidFileRead::Open Open a given raid file -- returns a pointer to a new RaidFileRead object. Note that one of two types could be returned, depending on the representation of the file. boxbackup/docs/api-notes/raidfile/lib_raidfile.txt0000664000175000017500000001000710347400657023072 0ustar siretartsiretartTITLE lib/raidfile Implements RAID type abilities in files in userland, along with simple transaction like semantics for writing and reading files. IOStream interfaces are used to read and write files. Eventually a raid file daemon will be written to "raidify" files in the background. This will allow fast streaming of data to a single disc, followed by splitting into separate raid files later. There is an option to disable raidification for use on systems with only one hard disc, or a hardware RAID array. SUBTITLE Controller The raid disc sets are managed by RaidFileController. This reads the configuration file, /etc/box/raidfile.conf, and then stores the sets of discs for use by the other classes. In the code, files are referenced using an integer set number, and a filename within that set. For example, (0, "dir/subdir/filename.ext"). The RAID file controller turns this into actual physcial filenames transparently. The files are devided into blocks, which should match the fragment/block size of the underlying filesystem for maximum space efficiency. If the directories specified in the configuration files are all equal, then userland RAID is disabled. The files will not be processed into the redundant parts. SUBTITLE Writing Files are written (and modified) using the RaidFileWrite class. This behaves as a seekable IOStream for writing. When all data has been written, the file is committed. This makes it available to be read. Processing to split it into the three files can be delayed. If the data is not commited, the temporary write file will be deleted. RaidFileWrite will optionally refuse to overwrite another file. If it is being overwritten, a read to that file will get the old file until it has been committed. SUBTITLE Reading File are opened and read with RaidFileRead::Open. This returns an IOStream (with some extras) which reads a file. If the file has been turned into the RAID representation, then if an EIO error occurs on any operation, it will switch transparently into recovery mode where the file will be regenerated from the remaining two files using the parity file. (and log an error to syslog) SUBTITLE Layout on disc The directory structure is replicated onto each of the three directories within the disc set. Given a filename x, within the disc set, one of the three discs is chosen as the 0th disc for this file. This is done by hashing the filename. This ensures that an equal load is placed on each disc -- otherwise one disc would only be accessed when a RAID file was being written. When the file is being written, the actual physical file is "x.rfwX". This just a striaght normal file -- nothing is done to the data. When it is commited, it is renamed to "x.rfw". Then, when it is turned into the raid representation, it is split across three discs, each file named "x.rf". When trying to open a file, the first attempt is for "x.rfw". If this exists, it is used as is. If this fails, two of the "x.rf" files are attempted to be opened. If this succeeds, the file is opened in RAID mode. This procedure guarenettes that a file will be opened either as a normal file, or as the correct three RAID file elements, even if a new version is being commited at the same time, or another process is working to turn it into a raid file. SUBTITLE Raid file representation The file is split into three files, stripe 1, stripe 2 and parity. Considering the file as a set of blocks of the size specified in the configuration, odd numbered blocks go in stripe 1, even blocks in stripe 2, and a XOR of the corresponding blocks in parity. Thus only two files are needed to recreate the original. The complexity comes because the size of the file is not stored inside the files at any point. This means if stripe 2 is missing, it is ambiguous as to how big the file is. Size is stored as a 64 bit integer. This is appended to the parity file, unless it can be stored by XORing it into the last 8 bytes of the parity file. This scheme is complex to implement (see the RaidFileRead.cpp!) but minimises the disc spaced wasted. boxbackup/docs/api-notes/raidfile/RaidFileWrite.txt0000664000175000017500000000163311163443550023160 0ustar siretartsiretartCLASS RaidFileWrite Interface to writing raidfiles. See IOStream interface. Some other useful functions are available, see h and cpp files. FUNCTION RaidFileWrite::RaidFileWrite() The constructor takes the disc set number and filename of the file you're interested. FUNCTION RaidFileWrite::Open() Open() opens the file for writing, and takes an "allow overwrite" flag. FUNCTION RaidFileWrite::Commit() Commmit the file, and make it visible to RaidFileRead. If ConvertToRaidNow == true, it will be converted to raid file representation immediately. Setting it to false is not a good idea. Later on, it will tell a daemon to convert it in the background, but for now it simply won't be converted. FUNCTION RaidFileWrite::Discard() Abort the creation/update. Equivalent to just deleting the object without calling Commit(). FUNCTION RaidFileWrite::Delete() Delete a file -- don't need to Open() it first. boxbackup/docs/api-notes/lib_backupstore.txt0000664000175000017500000000154111163443550022055 0ustar siretartsiretartTITLE lib/backupstore Classes which are shared amongst the server side store utilities, bbstored and bbstoreaccounts. Note also depends on lib/backupclient, as a lot of code is shared between the client and server. SUBTITLE BackupStoreAccountDatabase A simple implementation of an account database. This will be replaced with a more suitable implementation. SUBTITLE BackupStoreAccounts An interface to the account database, and knowledge of how to initialise an account on disc. SUBTITLE BackupStoreConfigVerify The same configuration file is used by all the utilities. This is the Configuration verification structure for this file. SUBTITLE BackupStoreInfo The "header" information about an account, specifying current disc usage, space limits, etc. SUBTITLE StoreStructure Functions specifying how the files are laid out on disc in the store. boxbackup/docs/api-notes/common/0000775000175000017500000000000011652362373017441 5ustar siretartsiretartboxbackup/docs/api-notes/common/lib_crypto.txt0000664000175000017500000000153610347400657022353 0ustar siretartsiretartTITLE lib/crypto Provides cryptographic primatives using the OpenSSL implementation. SUBTITLE CipherContexts and CipherDescriptions A CipherContext is the interface to encryption and decryption, providing a generic interface. It is constructed with a decrypt or encrypt flag, and a CipherDescription. The CipherDescription specifies which cipher is to be used, the key, and all other cipher specific paramteres. The CipherContext has support for padding and initialisation vectors. SUBTITLE Random numbers An interface to the OpenSSL psuedo random number generater is provided. SUBTITLE Digests An interface to the MD5 digest is provided. SUBTITLE RollingChecksum The rsync rolling checksum is implemented. The interface is a little tricky to be efficient as the caller must track the position and provide relevant bytes to advance the checksum. boxbackup/docs/api-notes/common/lib_common/0000775000175000017500000000000011652362373021557 5ustar siretartsiretartboxbackup/docs/api-notes/common/lib_common/Configuration.txt0000664000175000017500000000721010347400657025125 0ustar siretartsiretartCLASS Configuration Implements the reading of multi-level configuration files. This is intended to be a generic way of representing configuration implementation. Basic validation is performed on files as they are read, specified by a data structure. test/common has some examples of usage. SUBTITLE Configuration file format The format is simple, a list of Key = Value pairs. For example Key1 = Value1 Key2 = Value2 Optionally, multiple values for the same key are allowed. Lists of configurations can be nested to any level. These are called Sub-configurations: SubConfig { SubKey1 = ValueX SubKey2 = ValueY } SUBTITLE Verification The verification structure specifies what keys are required, what are optional, and what sub-configurations are expected. Default values can be specified. See Configuration.h for the structures. RaidFileController::Initialise has a good example of a simple verification layout. Wildcards can be used for SubConfigurations, by specifying a name of "*". This allows you to use multiple sections to configure any number of items. Each item has a number of flags, which are combined to say whether an item is required, should be an integer or boolean, and rather importantly, whether it's the last item in the list. Verification is limited, so you may wish to do more verification the file. There are unimplemented hooks for custom verification functions to be included in the verification definitions. Should be done at some point. Boolean keys have possible values "true", "yes", "false", "no" (case insensitive). FUNCTION Configuration::LoadAndVerify() Loads the configuration from disc, and verifies it. If there are problems with the verification, it returns some text which can be used to tell the user the problems with the file. These are fairly basic error messages, but do say what should be done to get it to parse properly. This returns a Configuration object, which can then be queries for Keys and Values. Sub-configurations are implemented through an interface which returns a reference to another Configuration object. FUNCTION Configuration::KeyExists() Does a specified key exist? Use this for optional key values only -- specify them in the verification structure if they are required. FUNCTION Configuration::GetKeyValue() Get the value of a key as a string. If ConfigTest_MultiValueAllowed is set in the relevant entry in the verify structure, this string may contain multiple values, separated by a single byte with value 0x01 (use Configuration::MultiValueSeparator in code). Use SplitString() defined in Utils.h to split it into components. This representation was chosen as multi-values are probably rare, and unlikely to justify writing a nicer (but more memory intensive and complex) solution. FUNCTION Configuration::GetKeyValueInt() Get the value of a key as an integer. Make sure the AsInt property is requried in the verification structure. FUNCTION Configuration::GetKeyValueBool() Get the value of a key as a boolean value. Make sure the AsBool property is requried in the verification structure. Default to "false", should this verification not be performed and an unknown value is specified. FUNCTION Configuration::GetKeyNames() Return a list of all the keys in this configuration. FUNCTION Configuration::SubConfigurationExists() Does a specified sub-configuration exist? FUNCTION Configuration::GetSubConfiguration() Return another Configuration object representing the sub section. FUNCTION Configuration::GetSubConfigurationNames() Get a list of all the sub configurations. As there isn't a particularly neat way that configurations can be iterated over, mSubConfigurations is public. (BAD!) boxbackup/docs/api-notes/common/lib_common/FdGetLine.txt0000664000175000017500000000044110347400657024116 0ustar siretartsiretartCLASS FdGetLine See IOStreamGetLine, difference is that it works on basic UNIX file descriptors rather than full blown streams. Follows basic interface, except... FUNCTION FdGetLine::GetLine Returns a string containing the optionally preprocessed line. Do not call if IsEOF if true. boxbackup/docs/api-notes/common/lib_common/ExcludeList.txt0000664000175000017500000000132010347400657024537 0ustar siretartsiretartTITLE ExcludeList A class implementing a list of strings which are to be excluded in some way. Entries can be added as strings to be matched exactly, or as regular expressions. Multiple entries can be added in a single function call in a manner designed to work with the multi-value entries store in a Configuation object. FUNCTION ExcludeList::AddDefiniteEntries() Definite entries are exact strings to match. FUNCTION ExcludeList::AddRegexEntries() Regular expressions as defined by POSIX, and supported by the host platform. Will throw an exception if regular expressions are not supported by the platform. FUNCTION ExcludeList::IsExcluded() Test a string for being excluded by definite or regex entries. boxbackup/docs/api-notes/common/lib_common/IOStreamGetLine.txt0000664000175000017500000000162110347400657025251 0ustar siretartsiretartCLASS IOStreamGetLine This class provides a convenient way to read text from a file, line by line. It also can preprocess the line to remove leading and trailing whitespace and comments. Comments are started by the character # and run to the end of the line. Create an instance by passing a reference to a stream into the constructor. Note the class does internal buffering, so you can only detach it later if the stream supports seeking backwards. FUNCTION IOStreamGetLine::GetLine() Returns true if a line could be retreieved without a read timing out. FUNCTION IOStreamGetLine::IsEOF() Whether the end of the stream has been reached. Do not call GetLine if this is true. FUNCTION IOStreamGetLine::GetLineNumber() Returns the line number. FUNCTION IOStreamGetLine::DetachFile() Detaches the stream from the GetLine class. Will seek backwards to "replace" data it's buffered back in the stream. boxbackup/docs/api-notes/common/lib_common/WaitForEvent.txt0000664000175000017500000000227510347400657024701 0ustar siretartsiretartCLASS WaitForEvent This class implements a way to efficiently wait on one or more system objects, for example sockets and file descriptors. Where kqueue() is available, this is used, otherwise poll(). The poll() implementation is less comprehensive and rather more limited. To add a compatible object, call Add(). To remove it, call Remove(). To wait for an object to become ready, call Wait(), which returns a pointer to the first ready object, or 0 for a timeout. The timeout is set either in the constructor, or using the SetTimout() method. It is specified in milliseconds. The kqueue based implementation will automatically remove objects when they are closed (actually, the OS does this), but the poll implementation requires that Remove() be called. The flags passed to Add() or Remove() are passed to the object, and are ignored by this class. For an object to be able to be added to the list, it should implement FillInKEvent() and FillInPoll() for kqueue and poll implementations respectively. Use the PLATFORM_KQUEUE_NOT_SUPPORTED define to test which is necessary for the platform. It does not have to be derived off any particular class, as it uses templates to be compatible with any class. boxbackup/docs/api-notes/common/lib_common/Guards.txt0000664000175000017500000000025610347400657023546 0ustar siretartsiretartTITLE Guards.h This file contains a couple of classes for using file and memory. When the class goes out of scope, the underlying object is closed or freed, respectively. boxbackup/docs/api-notes/common/lib_common/CollectInBufferStream.txt0000664000175000017500000000137310347400657026504 0ustar siretartsiretartCLASS CollectInBufferStream This class is essentially a buffer. Data is written to it, and stored in memory. Then when all data is written which will be written, it can be read out again. Useful for building streams in memory. FUNCTION CollectInBufferStream::SetForReading() After you've written all the data, call this to set it to read mode. FUNCTION CollectInBufferStream::Reset() Reset the stream to the state where is has no data, and is about to have data written. FUNCTION CollectInBufferStream::GetSize() Get the size of the buffered data -- the entire data. Use the interface function BytesLeftToRead() in most cases. FUNCTION CollectInBufferStream::GetBuffer() Get the buffer, so you can do bad things with it if you're feeling naughty. boxbackup/docs/api-notes/common/lib_common/MainHelper.txt0000664000175000017500000000022510347400657024341 0ustar siretartsiretartTITLE MainHelper.h This header contains a couple of macros which add exception handling and reporting to main() functions for executable programs. boxbackup/docs/api-notes/common/lib_common/Conversion.txt0000664000175000017500000000133310347400657024443 0ustar siretartsiretartTITLE Generic type conversion Conversion.h provides generic type conversion. Within the BoxConvert namespace, it defines the templated function ToType Convert(FromType From) which converts the data type as expected. In general, from and to types have to be specified explicitly. Templates rather than overloaded functions are used, firstly to be absolutely explicit about the conversion required, and secondly because overloaded functions can't have differing return types for the same argument type. The function is specialised for various types, and the generic version uses C++ type conversion. Exceptions may be thrown if the conversion is not possible. These are all of the ConversionException type. boxbackup/docs/api-notes/common/lib_common/xStream.txt0000664000175000017500000000222610347400657023743 0ustar siretartsiretartTITLE Various stream types Some useful streams are implemented in lib/common. SUBTITLE CollectInBufferStream Described in it's own page. SUBTITLE FileStream Implements a stream from a file, allowing both reading and writing. SUBTITLE MemBlockStream Turns a memory block into a readable stream. Can also be constructed using StreamableMemBlock, CollectInBufferStream and MemBlockStream as sources of the buffer. SUBTITLE PartialReadStream Create a readable stream which will read a set number of bytes from another stream, and then declare itself as closed. Useful for extracting a small chunk of another stream to present to a function which expects to consume all of a stream. SUBTITLE ReadGatherStream Create a readable stream out of blocks from many streams -- so various sections of any number of streams are composed into a single stream. To use, register each stream with AddComponent, then use the returned 'handle' with AddBlock to add blocks to the composed stream. Optionally, the object will take ownership of the streams added, and delete them when itself is deleted. See the comments in the function blocks in the cpp file for more info. boxbackup/docs/api-notes/common/lib_common/BoxTime.txt0000664000175000017500000000045310347400657023667 0ustar siretartsiretartTITLE BoxTime Not strictly a class, but time is presented in the project as 64 bit integers as microseconds since the UNIX Epoch. There are a number of utility functions in the BoxTime*.h files, which are useful. To get BoxTime values from a struct stat, use the FileModificationTime.h functions.boxbackup/docs/api-notes/common/lib_common/IOStream.txt0000664000175000017500000000576010347400657024011 0ustar siretartsiretartCLASS IOStream The base class for streams of data. See IOStream.h for specific details on functions, but the interface is described here. Most streams only implement one direction, but some both. SUBTITLE Reading FUNCTION IOStream::Read() Reads data from the stream. Returns 0 if there is no data available within the timeout requested. Unlike the UNIX API, a return of 0 does not mean that there is no more data to read. Use IOStream::StreamDataLeft() to find this out. FUNCTION IOStream::ReadFullBuffer() If you want to read a specific sized block of data from a stream, it is annoying ot use the Read function, because it might return less, so you have to loop. This implements that loop. Note that the timeout is the timeout for each individual read -- it might take a lot longer to complete. You must check the return value, which is whether or not it managed to get all that data. FUNCTION IOStream::BytesLeftToRead() How many bytes are left to read in the stream treated as a whole. May return IOStream::SizeOfStreamUnknown if it is something like a socket which doesn't know how much data is in the stream. FUNCTION IOStream::StreamDataLeft() Returns true if more data can be read. Or more specifically, if more data might be able to be read some time in the future. SUBTITLE Writing FUNCTION IOStream::Write() Write data to a stream. Writes the entire block, or exceptions. FUNCTION IOStream::WriteAllBuffered() Writes any buffered data to the underlying object. Call at the end of a series of writes to make sure the data is actually written. (Buffering is not yet implemented anywhere, but it should be soon.) FUNCTION IOStream::StreamClosed() Is the stream closed, and writing no longer possible? FUNCTION IOStream::CopyStreamTo() A utility function to copy the contents of one stream to another, until the reading stream has no data left. SUBTITLE Other interfaces These are slightly nasty interfaces, because they have to match the UNIX API to some extent. FUNCTION IOStream::Close() Close the stream -- intended to indicate that nothing more will be written. However, also closes reading in most cases. Stream dependent. Probably best just to delete the object and let it sort itself out. FUNCTION IOStream::Seek() Seeks within the stream -- mainly for using files within a stream interface, although some of the utility stream classes support it too. This is actually a bad interface, because it does not specify whether it applies to reading or writing. This matches the interface provided by files with a single file pointer, but does not map well onto other stream types. Should it be changed? Perhaps. But then it means that files would either have to have an inconsitent interface, or the class keep track of two separate file pointers. Which isn't nice. So use carefully, and remember whether you're using a file stream, a reading stream, or a writing stream. FUNCTION IOStream::GetPosition() Get the current file pointer. Subject to same problems as Seek with regard to semantics. boxbackup/docs/api-notes/common/lib_crypto/0000775000175000017500000000000011652362373021607 5ustar siretartsiretartboxbackup/docs/api-notes/common/lib_crypto/CipherContext.txt0000664000175000017500000000163110347400657025126 0ustar siretartsiretartCLASS CipherContext Encryption and decryption using OpenSSL EVP interface. See the cpp file for more documentation in the function headers, and test/crypto for examples. General notes below. SUBTITLE Construction Construct with the encryption direction, and a CipherDescription of the cipher and key required. SUBTITLE Encrypting or decrypting Begin() and Transform() allow piece by piece transformation of a block. TransformBlock() transforms an entire block. SUBTITLE Buffering All transforms expect to have enough space in the buffers for their entire output. Because of block boundaries and padding, it is necessary that the output buffer should be bigger than the input buffer. The amount of space depends on the cipher. InSizeForOutBufferSize() and MaxOutSizeForInBufferSize() perform these space calculations, returning the maximuim in size for a specified out size, and the reverse, respectively. boxbackup/docs/api-notes/common/lib_crypto/RollingChecksum.txt0000664000175000017500000000216310347400657025441 0ustar siretartsiretartCLASS RollingChecksum Implementing the rsync rolling checksum algorithm. Read it's description first: http://samba.anu.edu.au/rsync/tech_report/node3.html SUBTITLE Construction and initial checksum calculation The constructor takes a pointer to a block of data and a size, and calculates the checksum of this block. It can now be "rolled forward" to find the checksum of the block of the same size, one byte forward, with minimal calculation. FUNCTION RollingChecksum::GetChecksum() Returns the checksum for the current block. FUNCTION RollingChecksum::RollForward() This function takes the byte at the start of the current block, and the last byte of the block it's rolling forward to, and moves the checksum on. If the block is char *pBlock = ; with size s, then it should be called with RollForward(pBlock[0], pBlock[s]) and now GetChecksum will return the checksum of the block (pBlock+1) of size s. FUNCTION RollingChecksum::RollForwardSeveral() Similar to RollForward(), but is more efficient for skipping several bytes at once. Takes pointers to the data buffer rather than the actual data values. boxbackup/docs/api-notes/common/lib_server/0000775000175000017500000000000011652362373021575 5ustar siretartsiretartboxbackup/docs/api-notes/common/lib_server/SocketStream.txt0000664000175000017500000000045310347400657024742 0ustar siretartsiretartCLASS SocketStream A implementation of IOStream which wraps a socket connection. It can either be created by attaching to an existing object, or use the Open() function to open a connection to a named host on a specific port (or a local UNIX socket in the filesystem). Follows stream interface. boxbackup/docs/api-notes/common/lib_server/TLSContext.txt0000664000175000017500000000100510347400657024337 0ustar siretartsiretartCLASS TLSContext A wrapper over the OpenSSL context object. Note: you need to call SSLLib::Initialise at the beginning of your program to use these functions. SUBTITLE Construction The constuctor takes the following parameters * Boolean for whether this is acting as a server or a client * The .pem file containing the certificate which will be used * The .pem file containing the private key for this certificate * The .pem file containing the certificates which will certify the other end of the connection. boxbackup/docs/api-notes/common/lib_server/Protocol.txt0000664000175000017500000001115510347400657024140 0ustar siretartsiretartCLASS Protocol Protocol * serialises and deserialises data objects * sends arbitary streams through a bi-directional IOStream object, usually a socket. These data objects are auto-generated by a perl script, along with the logic to implement a simple protocol where one end is a client, and the other a server. The client sends a command object (and optional streams) and the server returns a reply object (and optional streams). The perl script uses a description file which specifies the data inside these objects (which can be extended to include any type which implements the standard serialisation functions), the required responses to the commands, and whether streams are involved. It then implements a server object, which given a stream and a user defined context object, waits for commands, processes them, and sends the results back. All you need to do is implement a DoCommand() function for each command object you define. On the client side, a Query() function is implemented which takes objects as parameters, and returns the right type of reply object (or throws an exception if it doesn't get it.) Short cut Query() functions are also implemented which don't require objects to be created manually. Thus, implementing a server is as simple as deriving a daemon off ServerStream or ServerTLS, and implementing the Connection() function as void TestProtocolServer::Connection(SocketStream &rStream) { TestProtocolServer server(rStream); TestContext context; server.DoServer(context); } and that's it. TestContext is a user defined class which keeps track of all the state of the connection, and is passed to all the DoCommand() functions, which look like this: std::auto_ptr TestProtocolServerSimple::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) { return std::auto_ptr (new TestProtocolServerSimpleReply(mValue+1)); } (taken from test/basicserver) The client code looks like this SocketStream conn; conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock"); TestProtocolClient protocol(conn); // Query { std::auto_ptr reply(protocol.QuerySimple(41)); TEST_THAT(reply->GetValuePlusOne() == 42); } Finally, debug logging can be generated which allows a list of all commands and their parameters to be logged to syslog or a file. SUBTITLE Protocol Description File This file is passed to the lib/server/makeprotocol.pl script, which generates a h and cpp file to implement the protocol. It is in two sections, separated by a 'BEGIN_OBJECTS' on a line of it's own. In the top half, the following statements must be made. Name The name of the protocol, used in naming classes. IdentString The idenfitifaction string sent over the IOStream to confirm it it is talking to another Protocol object speaking the same Protocol. ServerContextClass The user defined context class used for the server, and the header file it is defined in. Additionally, the following optional commands can be made. ClientType ServerType (similarly) Extends the types used in the objects below. Server and client can use different types for the same object type. ImplementLog (Client|Server) (syslog|file) Implement command logging for client or server into syslog or a file. LogTypeToText (Client|Server) For extended types, optionally define how to convert them into printf elements and the code to run to get the argument. Within the evaluate parameter, VAR is replaced by the name of the variable to display. If this is not specified for a given type, OPAQUE is output instead. In the object section, an object is defined by a line followed by lines beginning with whitespace defining the data transmitted in the object. The type may be list, which specifies a list (implemented as a std::vector) of entries of that type. The attributes specify exactly how that object is used in the defined protocol. Reply The object is a reply object, sent from the server to the client. Command(Reply-Type) The object is a command, send from the client to the server, and the server will send back an object of type Reply-Type. IsError(Type-Field,SubType-Field) The command is an error object, and the two files specify the data member which describes the error type and sub type. EndsConversation When this command is received, the connection is to be terminated. (ie a logout command) StreamWithCommand When this command is sent as a command, a stream follows it. boxbackup/docs/api-notes/common/lib_server/Daemon.txt0000664000175000017500000000507510347400657023546 0ustar siretartsiretartCLASS Daemon Implement a UNIX daemon which * Daemonises (detach from console, etc) * Sets up signal handlers, and does useful things with them * Reads configuration files * Writes PID file * Opens syslog all the usual UNIX basic daemon behaviour. The daemon exe takes optional arguments: The first is a configuration file filename which overrides the default. If the second argument is 'SINGLEPROCESS' the daemon will not daemonise. The configuration file must have a section like this Server { PidFile = /var/run/daemon.pid User = username } Use DAEMON_VERIFY_SERVER_KEYS (defined in Daemon.h) to include the necessary keys in your configuration file's verify structure. The "User" line is optional, and if it is present the Daemon will change user to this username just before it daemonises. Note that unless the directory the PID file is written to has write permission for this user, the PID file will not be deleted on exit. To implement a daemon, derive a class from Daemon, and override Run(). Then in your main file, do something like int main(int argc, const char *argv[]) { MAINHELPER_START BackupDaemon daemon; return daemon.Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); MAINHELPER_END } and that's it. Obviously it's worth doing a few more things as well, but that's it. FUNCTION Daemon::Run() Override with the main daemon code. It should behave like this void SomethingDaemon::Run() { // Read configuration file // Do lots of work until StopRun() returns true while(!StopRun()) { ::sleep(10); } // Clean up nicely } which allows the base class to implement the standard start, terminate and -HUP behaviours correctly. FUNCTION Daemon::DaemonName() Returns the name of the daemon, for use in syslog. FUNCTION Daemon::DaemonBanner() Returns the banner to be displayed on startup, or 0 for no banner. FUNCTION Daemon::GetConfigVerify() Returns a configuration verify structure for verifying the config file. Note that this configuration structure should include a sub-configuration called "Server, and have entries defined by DAEMON_VERIFY_SERVER_KEYS. See one of the bin/bbackupd for an example. FUNCTION Daemon::StopRun() Returns true when the daemon needs to be terminated or restarted. Use IsReloadConfigWanted() and IsTerminateWanted() to find out which one, if you need to know. FUNCTION Daemon::SetupInInitialProcess() Override to perform additional functions in the initial process, before forking and detachment happens. FUNCTION Daemon::EnterChild() Called when a child is entered. If you override it, remember to call the base class. boxbackup/docs/api-notes/common/lib_server/ServerTLS.txt0000664000175000017500000000052510347400657024167 0ustar siretartsiretartCLASS ServerTLS Implements a server which uses TLS (SSL) to encrypt and authenticate connections. Very similar to ServerStream, except it reads the certificate files for the TLSContext out of the Server sub-configuration to set up a TLSContext ("CertificateFile", "PrivateKeyFile" and "TrustedCAsFile"). Otherwise works exactly the same. boxbackup/docs/api-notes/common/lib_server/SocketStreamTLS.txt0000664000175000017500000000046310347400657025326 0ustar siretartsiretartCLASS SocketStreamTLS An implementation of IOStream which wraps a TLS (SSL) connection over a socket. The Open function takes a TLSContext reference which specifies the parameters for the connection. FUNCTION GetPeerCommonName() Returns the common name of the certificate presented by the remote end. boxbackup/docs/api-notes/common/lib_server/ServerStream.txt0000664000175000017500000000307310347400657024761 0ustar siretartsiretartCLASS ServerStream ServerStream implementes a Daemon which accepts stream connections over sockets, and forks into a child process to handle them. To implement a daemon, derive from ServerStream The type SocketStream specifies that incoming connections should be treated as normal sockets, and SERVER_LISTEN_PORT is the port to listen to (if it's a inet socket, and if not overridden in the config file). The actual addresses (or names) to bind to are specified in the configuration file by the user. Make sure SERVERSTREAM_VERIFY_SERVER_KEYS(0) is included in the configuration verification structure in the "Server" sub configuration. 0 could be replaced with a default address, for example "unix:/var/run/server.sock" to specific a default UNIX socket in the filesystem. See test/basicserver for a simple example. The ListenAddresses key in the Server subconfiguration is a comma separated list of addresses, specified as family:name. Internet sockets are family 'inet', for example 'inet:localhost' (or 'inet:localhost:1080' to specify a port number as well), and unix domain sockets are 'unix', example above. Override Connection to handle the connection. Remember to override Daemon functions like the server name, and start it up, just like a generic Daemon. FUNCTION ServerStream::Connection This function takes a connected stream as it's argument. It should then proceed to do whatever it needs to do to talk to the client. Using IOStreamGetLine or a Protocol class to communicate may be quick ways of implementing this functionality. boxbackup/docs/api-notes/common/lib_compress/0000775000175000017500000000000011652362373022122 5ustar siretartsiretartboxbackup/docs/api-notes/common/lib_compress/CompressStream.txt0000664000175000017500000000302710347400657025632 0ustar siretartsiretartTITLE CompressStream This implements a compressing stream class, which compresses and decompresses data sent to an underlying stream object. Data is likely to be buffered. WARNING: Use WriteAllBuffered() (after objects which need to be sent in their entirity) and Close() (after you have finished writing to the stream) otherwise the compression buffering will lose the last bit of data for you. The class works as a filter to an existing stream. Ownership can optionally be taken so that the source stream is deleted when this object is deleted. It can operate in one or two directions at any time. Data written to the stream is compressed, data read from the stream is decompressed. If a direction is not being (de)compressed, then it can act as a pass-through without touching the data. The constructor specifies the actions to be taken: CompressStream(IOStream *pStream, bool TakeOwnership, bool DecompressRead, bool CompressWrite, bool PassThroughWhenNotCompressed = false); pStream - stream to filter TakeOwnership - if true, delete the stream when this object is deleted. DecompressRead - reads pass through a decompress filter CompressWrite - writes pass through a compression filter PassThroughWhenNotCompressed - if not filtering a direction, pass through the data unaltered, otherwise exception if an attempt is made on that direction. Note that the buffering and compression will result in different behaviour than the underlying stream: data may not be written in one go, and data being read may come out in different sized chunks. boxbackup/docs/api-notes/common/lib_server.txt0000664000175000017500000000057310347400657022341 0ustar siretartsiretartTITLE lib/server This provides various classes for implementing client/server applications (and UNIX daemons). The stars of the show are ServerTLS and Protocol, which allow client server applications which communicate using TLS (SSL successor) to be built with surprisingly few lines of code. All details in the respective class files. Look at test/basicserver for examples. boxbackup/docs/api-notes/common/memory_leaks.txt0000664000175000017500000000352410347400657022673 0ustar siretartsiretartTITLE Memory leak detection The build system contains a primative memory leak detection system in debug builds. It works by using #defines to replace calls to malloc, free, realloc, new and delete with debug versions which record their use. When a process ends, it dumps a list of all the blocks or objects which were allocated by not freed, and the file and line of the source where they are originally allocated. It's not perfect, but should catch most things and work on most platforms. If it doesn't work on your platform, define PLATFORM_DISABLE_MEM_LEAK_TESTING in BoxPlatform.h within the relevant section. SUBTITLE Requirements in source It does require some extra lines in the source file. The last included file in each .cpp file must be #include "MemLeakFindOn.h" and if a .h file uses any of these functions, the contents of the .h file should be bounded with #include "MemLeakFindOn.h" // ... some code, but absolutely no #includes #include "MemLeakFindOff.h" The cleanupforcvs.pl script checks for correct usage. SUBTITLE Use in tests Tests will automatically dump memory leaks and regard them as a failure for anything executing in the main test process. To test for memory leaks in programs run from the test, or daemons, use something like TestRemoteProcessMemLeaks("bbackupd.memleaks"); If memory leak detection is being used, it will check the specified file for memory leak reports (deleting it afterwards). Any leak is an error. The Daemon class automactically arranges for the memory leak reports to be written. Other exes should set this up themselves, preferably using the define in MainHelper.h, as the first thing in their main() function. MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, name) File is the filename to write the report to, conventionally called ".memleaks", and name is the name of the exe. boxbackup/docs/api-notes/common/lib_common.txt0000664000175000017500000000543610347400657022326 0ustar siretartsiretartTITLE lib/common This module is the basic building block of the project. It is included implicitly as a dependency of all other modules. It provides basic services such as file and stream functionality, platform information, and various bits of utility code which doesn't fit naturally anywhere else but is useful to many other applications. SUBTITLE Box.h The main include file for the project. It should be the first file included in every cpp file, followed by any system headers, then other project headers. This order has been chosen so that eventually it can be used as a target for pre-compiled headers. (This will be important for the Windows port) SUBTITLE BoxPlatform.h This contains various bits of information on platform differences. The build scripts include a compile command line definition like PLATFORM_OPENBSD, which is then used to select the required options. Some of the stuff is not particularly pleasant, but it aims to produce a consistent compilation environment, and to have a abstracted way of setting other defines to tell the other files what things are available on this platform. GNU configure is not used, as it would be just another dependency. Although something does have to be done about compilation on Linux, which is just too different on each distribution to be caught by a common configuration here. SUBTITLE Streams Much use is made of streams -- files, connections, buffers, all sorts of things are implemented using streams. The abstract base class IOStream defines the interface, see the class file for more details. Streams are lib/common's basic contribution to the project. A standard interface for handling data, and a number of utility classes which make performing common stream functions easy. SUBTITLE Serialisation Note there is no "serialisable" class. Instead, objects which need to be serialised into Streams simply have two functions, ReadFromStream and WriteToStream. These perform as expected. As it turns out, there's no real need for a specific serialisation base class. If you do need to be able to serialise arbitary objects, there are two approaches. 1) If you're serialising an arbitary object, you're going to know roughly what type it is. So it's likely to be a subclass of a known base class... in which case, you simply use virtual functions. 2) If it really is an arbitary type, then you just write your function which accepts any type of object to serialise as a template. SUBTITLE BoxException The exception base class for the project. All exceptions thrown by this code are dervied from BoxException. In general, each module which has unique errors has it's own exception class. CommonException errors are thrown throughout the project. SUBTITLE Other bits There are some other extra useful bits which are documented only in the source files. boxbackup/docs/api-notes/common/lib_compress.txt0000664000175000017500000000054010347400657022660 0ustar siretartsiretartTITLE lib/compress This is a horrid C++ interface to zlib. It is written this way to avoid having to buffer any data, and so be unnecessarily inefficient. But this does end up just being a wrapper to the zlib interface. See test/compress for an example of how to use it. But it should be very familiar. For an easier interface, use CompressStream. boxbackup/docs/api-notes/INDEX.txt0000664000175000017500000000505711163443550017562 0ustar siretartsiretartTITLE Programmers Notes for Box Backup This directory contains the programmers notes for the Box Backup system. They will be of interest only if you want to review or modify the code. These notes are intended to be run through a script to produce HTML at some later stage, hence the marks such as 'TITLE'. SUBTITLE Organisation The project is split up into several modules. The modules within 'lib' are building blocks for creation of the actual executable programs within 'bin'. 'test' contains unit tests for lib and bin modules. The file modules.txt lists the modules which are to be built, and their dependencies. It also allows for platform differences. SUBTITLE Documentation Organisation In this directory, the files correspond to modules or areas of interest. Sub directories of the same name contain files documenting specific classes within that module. SUBTITLE Suggested reading order * common/lib_common.txt * common/lib_server.txt * bin_bbackupd.txt * backup_encryption.txt * bin_bbstored.txt * raidfile/lib_raidfile.txt and refer to other sections as required. SUBTITLE Building The makefiles are generated by makebuildenv.pl. (The top level makefile is generated by makeparcels.pl, but this is for the end user to use, not a programmer.) To build a module, cd to it and type make. If the -DRELEASE option is specified (RELEASE=1 with GNU make) the release version will be built. The object files and exes are placed in a directory structure under 'release' or 'debug'. It is intended that a test will be written for everything, so in general make commands will be issued only within the test/* modules. Once it has been built, cd to debug/test/ and run the test with ./t . SUBTITLE Programming style The code is written to be easy to write. Ease of programming is the primary concern, as this should lead to fewer bugs. Efficiency improvements can be made later when the system as a whole works. Much use is made of the STL. There is no common base class. All errors are reported using exceptions. Some of the boring code is generated by perl scripts from description files. There are a lot of modules and classes which can easily be used to build other projects in the future -- there is a lot of "framework" code. SUBTITLE Lots more documentation The files are extensively commented. Consider this notes as an overview, and then read the source files for detailed and definitive information. Each function and class has a very brief decsription of it's purpose in a standard header, and extensive efforts have been maed to comment the code itself. boxbackup/docs/api-notes/encrypt_rsync.txt0000664000175000017500000001135111163443550021607 0ustar siretartsiretartTITLE Encrypted rsync algorithm The backup system uses a modified version of the rsync algorithm. A description of the plain algorithm can be found here: http://samba.anu.edu.au/rsync/tech_report/ The algorithm is modified to allow the server side to be encrypted, yet still benefit from the reduced bandwidth usage. For a single file transfer, the result will be only slightly less efficient than plain rsync. For a backup of a large directory, the overall bandwidth may be less due to the way the backup client daemon detects changes. This document assumes you have read the rsync document. The code is in lib/backupclient/BackupStoreFile*.*. SUBTITLE Blocks Each file is broken up into small blocks. These are individually compressed and encrypted, and have an entry in an index which contains, encrypted, it's weak and strong checksums and decoded plaintext size. This is all done on the client. Why not just encrypt the file, and use the standard rsync algorithm? 1) Compression cannot be used, since encryption turns the file into essentially random data. This is not very compressible. 2) Any modification to the file will result in all data after that in the file having different ciphertext (in any cipher mode we might want to use). Therefore the rsync algorithm will only be able to detect "same" blocks up until the first modification. This significantly reduces the effectiveness of the process. Note that blocks are not all the same size. The last block in the file is unlikely to be a full block, and if data is inserted which is not a integral multiple of the block size, odd sized blocks need to be created. This is because the server cannot reassemble the blocks, because the contents are opaque to the server. SUBTITLE Modifed algorithm To produce a list of the changes to send the new version, the client requests the block index of the file. This is the same step as requesting the weak and strong checksums from the remote side with rsync. The client then decrypts the index, and builds a list of the 8 most used block sizes above a certain threshold size. The new version of the file is then scanned in exactly the same way as rsync for these 8 block sizes. If a block is found, then it is added to a list of found blocks, sorted by position in the file. If a block has already been found at that position, then the old entry is only replaced by the new entry if the new entry is a "better" (bigger) match. The block size covering the biggest file area is searched first, so that most of the file can be skipped over after the first pass without expensive checksumming. A "recipe" is then built from the found list, by trivially discarding overlapping blocks. Each entry consists of a number of bytes of "new" data, a block start number, and a number of blocks from the old file. The data is stored like this as a memory optimisation, assuming that files mostly stay the same rather than having all their blocks reordered. The file is then encoded, with new data being sent as blocks of data, and references to blocks in the old file. The new index is built completely, as the checksums and size need to be rencrypted to match their position in the index. SUBTITLE Combination on server The "diff" which is sent from the client is assembled into a full file on the server, simply by adding in blocks from the old file where they are specified in the block index. SUBTITLE Storage on server Given that the server will in general store several versions of a file, combining old and new files to form a new file is not terribly efficient on storage space. Particularly for large multi-Gb database files. An alternative scheme is outlined below, however, it is significantly more complex to implement, and so is not implemented in this version. 1) In the block index of the files, store the file ID of the file which each block is source from. This allows a single file to reference blocks from many files. 2) When the file is downloaded, the server combines the blocks from all the files into a new file as it is streamed to the client. (This is not particuarly complicated to do.) This all sounds fine, until housekeeping is considered. Old versions need to be deleted, without losing any blocks necessary for future versions. Instead of just deleting a file, the server works out which blocks are still required, and rebuilds the file omitting those blocks which aren't required. This complicates working out how much space a file will release when it is "deleted", and indeed, adds a whole new level of complexity to the housekeeping process. (And the tests!) The directory structure will need an additional flag, "Partial file", which specifies that the entry cannot be built as previous blocks are no longer available. Entries with this flag should never be sent to the client. boxbackup/docs/api-notes/bin_bbackupd.txt0000664000175000017500000001166111163443550021314 0ustar siretartsiretartTITLE bin/bbackupd The backup client daemon. This aims to maintain as little information as possible to record which files have been uploaded to the server, while minimising the amount of queries which have to be made to the server. SUBTITLE Scanning The daemon is given a length of time, t, which files over this age should be uploaded to the server. This is to stop recently updated files being uploaded immediately to avoid uploading something repeatedly (on the assumption that if a file has been written, it is likely to be modified again shortly). It will scan the files at a configured interval, and connect to the server if it needs to upload files or make queries about files and directories. The scan interval is actually varied slightly between each run by adding a random number up to a 64th of the configured time. This is to reduce cyclic patterns of load on the backup servers -- otherwise if all the boxes are turned on at about 9am, every morning at 9am there will be a huge spike in load on the server. Each scan chooses a time interval, which ends at the current time - t. This will be from 0 to current time - t on the first run, then the next run takes the start time as the end time of the previous run. The scan is only performed if the difference between the start and end times is greater or equal to t. For each configured location, the client scans the directories on disc recursively. For each directory * If the directory has never been scanned before (in this invocation of the daemon) or the modified time on the directory is not that recorded, the listing on the server is downloaded. * For each file, if it's modified time is within the time period, it is uploaded. If the directory has been downloaded, it is compared against that, and only uploaded if it's changed. * Find all the new files, and upload them if they lie within the time interval. * Recurse to sub directories, creating them on the server if necessary. Hence, the first time it runs, it will download and compare the entries on the disc to those on the server, but in future runs it will use the file and directory modification times to work out if there is anything which needs uploading. If there aren't any changes, it won't even need to connect to the server. There are some extra details which allow this to work reliably, but they are documented in the source. SUBTITLE File attributes The backup client will update the file attributes on files as soon as it notices they are changed. It records most of the details from stat(), but only a few can be restored. Attributes will only be considered changed if the user id, group id or mode is changed. Detection is by a 64 bit hash, so detection is strictly speaking probablistic. SUBTITLE Encryption All the user data is encrypted. There is a separate file, backup_encryption.txt which describes this, and where in the code to look to verify it works as described. SUBTITLE Tracking files and directories Renaming files is a difficult problem under this minimal data scanning scheme, because you don't really know whether a file has been renamed, or another file deleted and new one created. The solution is to keep (on disc) a map of inode numbers to server object IDs for all directories and files over a certain user configurable threshold. Then, when a new file is discovered, it is first checked to see if it's in this map. If so, a rename is considered, which will take place if the local object corresponding to the name of the tracked object doesn't exist any more. Because of the renaming requirement, deletions of objects from the server are recorded and delayed until the end of the scan. SUBTITLE Running out of space If the store server indicates on login to the backup client, it will scan, but not upload anything nor adjust it's internal stored details of the local objects. However, deletions and renames happen. This is to allow deletions to still work and reduce the amount of storage space used on the server, in the hope that in the future there will be enough space. Just not doing anything would mean that one big file created and then deleted at the wrong time would stall the whole backup process. SUBTITLE BackupDaemon This is the daemon class for the backup daemon. It handles setting up of all the objects, and implements calulcation of the time intervals for the scanning. SUBTITLE BackupClientContext State information for the scans, including maintaining a connection to the store server if required. SUBTITLE BackupClientDirectoryRecord A record of state of a directory on the local filesystem. Containing the recursive scanning function, which is long and entertaining, but very necessary. It contains lots of comments which explain the exact details of what's going on. SUBTITLE BackupClientInodeToIDMap A implementation of a map of inode number to object ID on the server. If Berkeley DB is available on the platform, it is stored on disc, otherwise there is an in memory version which isn't so good. boxbackup/docs/api-notes/bin_bbstored.txt0000664000175000017500000000463611163443550021351 0ustar siretartsiretartTITLE bin/bbstored The backup store daemon. Maintains a store of encrypted files, and every so often goes through deleting unnecessary data. Uses an implementation of Protocol to communicate with the backup client daemon. See bin/bbstored/backupprotocol.txt for details. SUBTITLE Data storage The data is arranged as a set of objects within a RaidFile disc set. Each object has a 64 bit object ID, which is turned into a filename in a mildly complex manner which ensure that directories don't have too many objects in them, but there is a minimal number of nested directories. See StoreStructure::MakeObjectFilename in lib/backupstore/StoreStructure.cpp for more details. An object can be a directory or a file. Directories contain files and other directories. Files in directories are supersceded by new versions when uploaded, but the old versions are flagged as such. A new version has a different object ID to the old version. Every so often, a housekeeping process works out what can be deleted, and deletes unnecessary files to take them below the storage limits set in the store info file. SUBTITLE Note about file storage and downloading There's one slight entertainment to file storage, in that the format of the file streamed depends on whether it's being downloaded or uploaded. The problem is that it contains an index of all the blocks. For efficiency in managing these blocks, they all need to be in the same place. Files are encoded and decoded as they are streamed to and from the server. With encoding, the index is only completely known at the end of the process, so it's sent last, and lives in the filesystem last. When it's downloaded, it can't be decoded without knowing the index. So the index is sent first, followed by the data. SUBTITLE BackupContext The context of the current connection, and the object which modifies the store. Maintains a cache of directories, to avoid reading them continuously, and keeps a track of a BackupStoreInfo object which is written back periodiocally. SUBTITLE BackupStoreDaemon A ServerTLS daemon which forks off a separate housekeeping process as it starts up. Handling connections is delegated to a Protocol implementation. SUBTITLE BackupCommands.cpp Implementation of all the commands. Work which requires writing is handled in the context, read only commands mainly in this file. SUBTITLE HousekeepStoreAccount A class which performs housekeeping on a single account. boxbackup/docs/api-notes/win32_build_on_linux_using_mingw.txt0000664000175000017500000000653511163443550025357 0ustar siretartsiretartHow to build Box Backup for Windows (Native) on Linux using MinGW By Chris Wilson, 2005-12-07 Install the MinGW cross-compiler for Windows: - Debian and Ubuntu users can "apt-get install mingw32" - Fedora and SuSE users can download RPM packages from [http://mirzam.it.vu.nl/mingw/] You will need to know the prefix used by the cross-compiler executables. It will usually be something like "ix86-mingw32*-". All the binaries in the cross-compiler package will start with this prefix. The documentation below assumes that it is "i386-mingw32-". Adjust to taste. You will also need to install Wine and the Linux kernel "binary formats" (binfmt) support, so that you can run Windows executables on Linux, otherwise the configure scripts will not work properly with a cross-compiler. On Ubuntu, run: apt-get install wine binfmt-support /etc/init.d/binfmt-support start Start by downloading Zlib from [http://www.zlib.net/], unpack and enter source directory: export CC=i386-mingw32-gcc export AR="i386-mingw32-ar rc" export RANLIB="i386-mingw32-ranlib" ./configure make make install prefix=/usr/local/i386-mingw32 Download OpenSSL 0.9.8b from [http://www.openssl.org/source/openssl-0.9.8b.tar.gz] Unpack and configure: tar xzvf openssl-0.9.8b.tar.gz cd openssl-0.9.8b ./Configure --prefix=/usr/local/i386-mingw32 mingw make makefile.one wget http://www.boxbackup.org/svn/box/chris/win32/support/openssl-0.9.8b-mingw-cross.patch patch -p1 < openssl-0.9.8b-mingw-cross.patch make -f makefile.one make -f makefile.one install Download PCRE from [http://prdownloads.sourceforge.net/pcre/pcre-6.3.tar.bz2?download] Unpack: tar xjvf pcre-6.3.tar.bz2 cd pcre-6.3 Configure and make: export AR=i386-mingw32-ar ./configure --host=i386-mingw32 --prefix=/usr/local/i386-mingw32/ make winshared If you get this error: ./dftables.exe pcre_chartables.c /bin/bash: ./dftables.exe: cannot execute binary file make: *** [pcre_chartables.c] Error 126 then run: wine ./dftables.exe pcre_chartables.c make winshared to complete the build. Finally: cp .libs/libpcre.a /usr/local/i386-pc-mingw32/lib cp .libs/libpcreposix.a /usr/local/i386-pc-mingw32/lib cp pcreposix.h /usr/local/i386-pc-mingw32/include You will need to find a copy of mingwm10.dll that matches your cross-compiler. Most MinGW distributions should come with it. On Debian and Ubuntu, for some bizarre reason, you'll find it compressed as /usr/share/doc/mingw32-runtime/mingwm10.dll.gz, in which case you'll have to un-gzip it with "gzip -d". Copy it to a known location, e.g. /usr/local/i386-mingw32/bin. Download and extract Box Backup, and change into the base directory, e.g. boxbackup-0.11rc2. Change the path to mingwm10.dll in parcels.txt to match where you found or installed it. Now configure Box with: ./configure --host=i386-mingw32 \ CXXFLAGS="-mthreads -I/usr/local/i386-mingw32/include" \ LDFLAGS=" -mthreads -L/usr/local/i386-mingw32/lib" \ LIBS="-lcrypto -lws2_32 -lgdi32" make or, if that fails, try this: export CXX="i386-mingw32-g++" export AR=i386-mingw32-ar export RANLIB=i386-mingw32-ranlib export CFLAGS="-mthreads" export CXXFLAGS="-mthreads" export LDFLAGS="-mthreads" export LIBS="-lcrypto -lws2_32 -lgdi32" (if you don't have a "configure" file, run "./bootstrap") ./configure --target=i386-mingw32 make CXX="$CXX" AR="$AR" RANLIB="$RANLIB" WINDRES="i386-mingw32-windres" boxbackup/docs/api-notes/backup_encryption.txt0000664000175000017500000001151011163443550022421 0ustar siretartsiretartTITLE Encryption in the backup system This document explains how everything is encrypted in the backup system, and points to the various functions which need reviewing to ensure they do actually follow this scheme. SUBTITLE Security objectives The crpyto system is designed to keep the following things secret from an attacker who has full access to the server. * The names of the files and directories * The contents of files and directories * The exact size of files Things which are not secret are * Directory heirarchy and number of files in each directory * How the files change over time * Approximate size of files SUBTITLE Keys There are four separate keys used: * Filename * File attributes * File block index * File data and an additional secret for file attribute hashes. The Cipher is Blowfish in CBC mode in most cases, except for the file data. All keys are maximum length 448 bit keys, since the key size only affects the setup time and this is done very infrequently. The file data is encrypted with AES in CBC mode, with a 256 bit key (max length). Blowfish is used elsewhere because the larger block size of AES, while more secure, would be terribly space inefficient. Note that Blowfish may also be used when older versions of OpenSSL are in use, and for backwards compatibility with older versions. The keys are generated using "openssl rand", and a 1k file of key material is stored in /etc/box/bbackupd. The configuration scripts make this readable only by root. Code for review: BackupClientCryptoKeys_Setup() in lib/backupclient/BackupClientCryptoKeys.cpp SUBTITLE Filenames Filenames need to be secret from the attacker, but they need to be compared on the server so it can determine whether or not is it a new version of an old file. So, the same Initialisation Vector is used for every single filename, so the same filename encrypted twice will have the same binary representation. Filenames use standard PKCS padding implemented by OpenSSL. They are proceeded by two bytes of header which describe the length, and the encoding. Code for review: BackupStoreFilenameClear::EncryptClear() in lib/backupclient/BackupStoreFilenameClear.cpp SUBTITLE File attributes These are kept secret as well, since they reveal information. Especially as they contain the target name of symbolic links. To encrypt, a random Initialisation Vector is choosen. This is stored first, followed by the attribute data encrypted with PKCS padding. Code for review: BackupClientFileAttributes::EncryptAttr() in lib/backupclient/BackupClientFileAttributes.cpp SUBTITLE File attribute hashes To detect and update file attributes efficiently, the file status change time is not used, as this would give suprious results and result in unnecessary updates to the server. Instead, a hash of user id, group id, and mode is used. To avoid revealing details about attributes 1) The filename is added to the hash, so that an attacker cannot determine whether or not two files have identical attributes 2) A secret is added to the hash, so that an attacker cannot compare attributes between accounts. The hash used is the first 64 bits of an MD5 hash. SUBTITLE File block index Files are encoded in blocks, so that the rsync algorithm can be used on them. The data is compressed first before encryption. These small blocks don't give the best possible compression, but there is no alternative because the server can't see their contents. The file contains a number of blocks, which contain among other things * Size of the block when it's not compressed * MD5 checksum of the block * RollingChecksum of the block We don't want the attacker to know the size, so the first is bad. (Because of compression and padding, there's uncertainty on the size.) When the block is only a few bytes long, the latter two reveal it's contents with only a moderate amount of work. So these need to be encrypted. In the header of the index, a 64 bit number is chosen. The sensitive parts of the block are then encrypted, without padding, with an Initialisation Vector of this 64 bit number + the block index. If a block from an previous file is included in a new version of a file, the same checksum data will be encrypted again, but with a different IV. An eavesdropper will be able to easily find out which data has been re-encrypted, but the plaintext is not revealed. Code for review: BackupStoreFileEncodeStream::Read() (IV base choosen about half-way through) BackupStoreFileEncodeStream::EncodeCurrentBlock() (encrypt index entry) in lib/backupclient/BackupStoreFileEncodeStream.cpp SUBTITLE File data As above, the first is split into chunks and compressed. Then, a random initialisation vector is chosen, stored first, followed by the compressed file data encrypted using PKCS padding. Code for review: BackupStoreFileEncodeStream::EncodeCurrentBlock() in lib/backupclient/BackupStoreFileEncodeStream.cpp boxbackup/docs/docbook/0000775000175000017500000000000011652362373015672 5ustar siretartsiretartboxbackup/docs/docbook/bbackupquery.xml0000664000175000017500000003774511163413441021117 0ustar siretartsiretart bbackupquery 8 Box Backup Box Backup 0.11 bbackupquery Box Backup store query and file retrieval bbackupquery -q -c configfile command ... Description bbackupquery is the main way of interacting with the backup store from a Box Backup client machine. It supports both interactive and batch modes of operation. It can be used to reviewing the status of a client machine's backup store, getting status from the store server. The main use is to retrieve files and directories when needed. bbackupquery supports interactive and batch modes of operation. Interactive mode allows for interaction with the server much like an interactive FTP client. Batch mode is invoked by putting commands into the invocation of bbackupquery. Example: bbackupquery "list home-dirs" quit Note that commands that contain spaces are enclosed in double quotes. If the quit command is omitted, after the preceding commands are completed, bbackupquery will enter interactive mode. Options Quiet. Suppresses status output while running. Use configfile instead of the default bbackupd.conf file. Can be a relative or full path. Commands The commands that can be used in bbackupquery are listed below. help Displays the basic help message, which gives information about the commands available in bbackupquery. Use the form help command to get help on a specific command. quit End the session with the store server, and quit bbackupquery. cd options directory-name Change directory. Options: consider deleted directories for traversal consider old versions of directories for traversal. This option should never be useful in a correctly formed store. lcd local-directory-name Change directory on the client machine. To list the contents of the local directory, type sh ls (on Unix-like machines). list options directory-name The list (or its synonym ls) command lists the content of the current, or specified, directory. The options are as follows: recursively list all files list deleted files and directories list old versions of files and directories don't display object IDs don't display flags show file modification time (and attr mod time, if the object has attributes). show file size in blocks used on server. Note that this is only a very approximate indication of local file size. ls options directory-name Synonym for list. pwd Print current directory, always relative to the backup store root. sh shell-command Everything after the sh is passed to a shell and run. All output from the command is displayed in the client. Example: to list the contents of the current directory on the client machine type sh ls. compare -a compare -l location-name compare store-dir-name local-dir-name Compare the current data in the store with the data on the disc. Please note that all the data will be downloaded from the store, so this can be a very lengthy process depending on the size of the store, and the size of the part you are comparing. Options: compare all locations. compare one backup location as specified in the configuration file. This compares one of the top level store directories. set return code. The return code is set to the following values, if quit is the next command. So, if another command is run after the compare, the return code will not refer to the compare. This option is very useful for automating compares. Return code values: -- no differences were found -- differences were found -- an error occured get object-filename local-filename get -i object-id local-filename Gets a file from the store. Object is specified as the filename within the current directory. Local filename is optional. Ignores old and deleted files when searching the directory for the file to retrieve. To get an old or deleted file, use the option and select the object as a hex object ID (first column in listing). The local filename must be specified. getobject object-id local-filename Gets the object specified by the object id (in hex) and stores the raw contents in the local file specified. Note: This is only useful for debugging as it does not decode files from the stored format, which is encrypted and compressed. restore -d directory-name local-directory-name restore -r Restores a directory to the local disc. The local directory specified must not exist (unless a previous restore is being restarted). The root cannot be restored -- restore locations individually. Options: restore a deleted directory resume an interrupted restore If a restore operation is interrupted for any reason, it can be restarted using the switch. Restore progress information is saved in a file at regular intervals during the restore operation to allow restarts. usage -m Show space used on the server for this account. Display fields: Used: Total amount of space used on the server Old files: Space used by old files Deleted files: Space used by deleted files Directories: Space used by the directory structure When Used exceeds the soft limit, the server will start to remove old and deleted files until the usage drops below the soft limit. After a while, you should expect to see the usage stay at just below the soft limit. You only need more space if the space used by old and deleted files is near zero. The option displays output in machine-readable form. Bugs If you find a bug in Box Backup and you want to let us know about it, join the mailing list and send us a description of the problem there. To report a bug, give us at least the following information: The version of Box Backup you are running The platform you are running on (hardware and OS), for both client and server. If possible attach your config files (bbstored.conf, bbackupd.conf) to the bug report. Also attach any log file output that helps shed light on the problem you are seeing. And last but certainly not least, a description of what you are seeing, in as much detail as possible. Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bbstored.xml0000664000175000017500000000455211163413441020214 0ustar siretartsiretart bbstored 8 Box Backup Box Backup 0.11 bbstored Box Backup store daemon bbstored config-file Description bbstored runs on a central server. Clients running bbackupd connect to the server and upload files. The only argument is optional and specifies a non-default configuration file. By default it will look for the configuration file as /etc/box/bbstored.conf. Files /etc/box/bbstored.conf See Also bbstored.conf 5 , bbstored-config 8 , raidfile-config 8 , raidfile.conf 5 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bbstoreaccounts.xml0000664000175000017500000003417711163413441021616 0ustar siretartsiretart bbstoreaccounts 8 Box Backup Box Backup 0.11 bbstoreaccounts Box Backup store accounts manager bbstoreaccounts -c config-file command account-id command-specific arguments Description bbstoreaccounts is the tool for managing accounts on the store server. It can be used to view information related to accounts, as well as create, change and delete accounts on the store server. bbstoreaccounts always takes at least 2 parameters: the command name and the account ID. Some commands require additional parameters, and some commands have optional parameters. Options The configfile to use for connecting to the store. Default is /etc/box/bbstored.conf. Commands The commands tells bbstoreaccounts what action to perform. check account-id fix The check command verifies the integrity of the store account given, and optionally fixes any corruptions. Note: It is recommended to run the 'simple' check command (without fix) before using the fix option. This gives an overview of the extent of any problems, before attempting to fix them. create account-id disc-set soft-limit hard-limit Creates a new store account with the parameters given. The parameters are as follows: The ID of the new account to be created. A 32-bit hexadecimal number. Cannot already exist on the server. The disc set from raidfile.conf 5 where the backups for this client will be stored. A number. Each RAID-file set has a number in raidfile.conf. This number is what's used. The soft limit is the amount of storage that the server will guarantee to be available for storage. The amount of storage that the the server will allow, before rejecting uploads, and starting to eliminate old and deleted files to get back down to soft-limit. delete account-id yes Deletes the account from the store server completely. Removes all backups and deletes all references to the account in the config files. delete will ask for confirmation from the user, when called. Using the flag, eliminates that need. This is useful when deleting accounts from within a script or some other automated means. 0 info account-id Display information about the given account. Example:[root]# bbstoreaccounts info 1 Account ID: 00000001 Last object ID: 58757 Blocks used: 9864063 (38531.50Mb) Blocks used by old files: 62058 (242.41Mb) Blocks used by deleted files: 34025 (132.91Mb) Blocks used by directories: 6679 (26.09Mb) Block soft limit: 11796480 (46080.00Mb) Block hard limit: 13107200 (51200.00Mb) Client store marker: 1139559852000000 Explanation: Account ID The account ID being displayed. Last Object ID A counter that keeps track of the objects that have been backed up. This number refers to the last file that was written to the store. The ID is displayed as a decimal number, and the object ID can be converted to a path name to a file as follows: convert the number to hex (e.g.: 58757 => 0xE585); The last backed up file will be (relative from the client's store root): e5/o85.rfw. Longer numbers infer more directories in the structure, so as an example 3952697264 as the last object ID gives 0xEB995FB0, which translates to a backup pathname of eb/99/5f/ob0.rfw. Blocks used The number of blocks used by the store. The size in Mb depends on the number of blocks, as well as the block size for the disc set given in raidfile.conf 5 . In this case the block size is 4096. Blocks used by old files The number of blocks occupied by files that have newer versions in the store. This data is at risk for being removed during housekeeping. Blocks used by deleted files The number of blocks used by files that have been deleted on the client. This data is at risk for being removed during housekeeping. Blocks used by directories The number of blocks used by directories in the store. Block soft limit The soft limit in blocks. The soft limit is the maximum guaranteed storage space available to the account. When housekeeping starts, and the old and deleted files are removed, they are removed in chronological order (oldest first), until the data used is less than the soft limit. Block hard limit The hard limit in blocks. The hard limit is the most amount of storage the server will allow in an account. Any data above this amount will be rejected. Housekeeping will reduce the storage use, so more data can be uploaded. Client store marker bbstored 8 uses this number to determine if it needs to rescan the entire store. If this number is different from the last time it checked, a rescan will take place. setlimit account-id soft-limit hard-limit Changes the storage space allocation for the given account. No server restart is needed. Parameters: The ID of the account to be modified. The soft limit is the amount of storage that the server will guarantee to be available for storage. The amount of storage that the the server will allow before rejecting uploads and starting to eliminate old and deleted files to get back down to . Examples Create an account with ID 3af on disc set 0, with a 20GB soft-limit and a 22GB hard-limit:bbstoreaccounts create 3af 0 20G 22GAlter existing account ID 20 to have a 50GB soft-limit and a 55GB hard-limit:bbstoreaccounts setlimit 20 50G 55G Files /etc/box/bbstored/accounts.txt See Also bbstored 8 , bbstored-config 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bbackupd.xml0000664000175000017500000001527011163413441020162 0ustar siretartsiretart bbackupd 8 Box Backup Box Backup 0.11 bbackupd Box Backup client daemon bbackupd -DFkqvVT -c config-file -t tag Description bbackupd runs on client computers in the background, finding new files to back up. When it is time for a backup, bbackupd will connect to the server (bbstored) to upload the files. A running bbackupd daemon can be controlled with the bbackupctl command, to make it shut down, reload its configuration, or start an immediate backup. bbackupd needs to be configured to tell it which files to back up, how often, and to which server (running bbstored). See the Client Configuration page for more information. For this, you must write a configuration file. You must either place it in the default location, or tell bbackupd where to find it. You can check the default location with the option. The default on Unix systems is usually /etc/box/bbackupd.conf. On Windows systems, it is bbackupd.conf in the same directory where bbackupd.exe is located. If bbackupd cannot find or read the configuration file, it will log an error message and exit. bbackupd usually writes log messages to the system logs, using the facility local5, which you can use to filter them to send them to a separate file. It can also write them to the console, see options below. If bbackupd is not doing what you expect, please check the logs first of all. Options config-file Use the specified configuration file. If is omitted, the last argument is the configuration file. If none is specified, the default is used (see above). Debugging mode. Do not fork into the background (do not run as a daemon). Not available on Windows. No-fork mode. Same as for bbackupd. Not available on Windows. Keep console open after fork, keep writing log messages to it. Not available on Windows. Run more quietly. Reduce verbosity level by one. Available levels are NOTHING, FATAL, ERROR, WARNING, NOTICE, INFO, TRACE, EVERYTHING. Default level is NOTICE in non-debugging builds. Use once to drop to WARNING level, twice for ERROR level, four times for no logging at all. -v Run more verbosely. Increase verbosity level by one. Use once to raise to INFO level, twice for TRACE level, three times for EVERYTHING (currently the same as TRACE). Run at maximum verbosity (EVERYTHING level). tag Tag each console message with specified marker. Mainly useful in testing when running multiple daemons on the same console. Timestamp each line of console output. Files /etc/box/bbackupd.conf See Also bbackupd.conf 5 , bbackupd-config 8 , bbackupctl 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/instguide.xml0000664000175000017500000007263511163413441020412 0ustar siretartsiretart Box Backup Build and Installation Guide License Copyright © 2003 - 2007, Ben Summers and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. All use of this software and associated advertising materials must display the following acknowledgement: This product includes software developed by Ben Summers. The names of the Authors may not be used to endorse or promote products derived from this software without specific prior written permission. [Where legally impermissible the Authors do not disclaim liability for direct physical injury or death caused solely by defects in the software unless it is modified by a third party.] THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Introduction The backup daemon, bbackupd, runs on all machines to be backed up. The store server daemon, bbstored runs on a central server. Data is sent to the store server, which stores all data on local filesystems, that is, only on local hard drives. Tape or other archive media is not used. The system is designed to be easy to set up and run, and cheap to use. Once set up, there should be no need for user or administrative intervention, apart from usual system maintenance.
    Client daemon bbackupd is configured with a list of directories to back up. It has a lazy approach to backing up data. Every so often, the directories are scanned, and new data is uploaded to the server. This new data must be over a set age before it is uploaded. This prevents rapid revisions of a file resulting in many uploads of the same file in a short period of time. It can also operate in a snapshot mode, which behaves like traditional backup software. When instructed by an external bbackupctl program, it will upload all changed files to the server. The daemon is always running, although sleeping most of the time. In lazy mode, it is completely self contained -- scripts running under cron jobs are not used. The objective is to keep files backed up, not to make snapshots of the filesystem at particular points in time available. If an old version of the file is present on the server, a modified version of the rsync algorithm is used to upload only the changed portions of the file. After a new version is uploaded, the old version is still available (subject to disc space on the server). Similarly, a deleted file is still available. The only limit to their availability is space allocated to this account on the server Future versions will add the ability to mark the current state of files on the server, and restore from this mark. This will emulate the changing of tapes in a tape backup system.
    Restoration Restoring files is performed using a query tool, bbackupquery. This can be used to restore entire directories, or as an 'FTP-like' tool to list and retrieve individual files. Old versions and deleted files can be retrieved using this tool for as long as they are kept on the server.
    Client Resource Usage bbackupd uses only a minimal amount of disc space to store records on uploaded files -- less than 32 bytes per directory and file over a set size threshold. However, it minimises the amount of queries it must make to the server by storing, in memory, a data structure which allows it to determine what data is new. It does not need to store a record of all files, essentially just the directory names and last modification times. This is not a huge amount of memory. If there are no changes to the directories, then the client will not even connect to the server.
    Security Box Backup is designed to be secure in several ways. The data stored on the backup store server is encrypted using secret-key cryptography. Additionally, the transport layer is encrypted using TLS, to ensure that the communications can't be snooped.
    Encryption The files, directories, filenames and file attributes are all encrypted. By examining the stored files on the server, it is only possible to determine the approximate sizes of a files and the tree structure of the disc (not names, just number of files and subdirectories in a directory). By monitoring the actions performed by a client, it is possible to determine the frequency and approximate scope of changes to files and directories. The connections between the server and client are encrypted using TLS (latest version of SSL). Traffic analysis is possible to some degree, but limited in usefulness. An attacker will not be able to recover the backed up data without the encryption keys. Of course, you won't be able to recover your files without the keys either, so you must make a conventional, secure, backup of these keys.
    Authentication SSL certificates are used to authenticate clients. UNIX user accounts are not used to minimise the dependence on the configuration of the operating system hosting the server. A script is provided to run the necessary certification authority with minimal effort.
    Server daemon The server daemon is designed to be simple to deploy, and run on the cheapest hardware possible. To avoid the necessity to use expensive hardware RAID or software RAID with complex setup, it (optionally) stores files using RAID techniques. It does not need to run as a privileged user. Each account has a set amount of disc space allocated, with a soft and a hard limit. If the account exceeds the soft limit, a housekeeping process will start deleting old versions and deleted files to reduce the space used to below the soft limit. If the backup client attempts to upload a file which causes the store to exceed the hard limit, the upload will be refused.
    Building and installing
    Before you start Firstly, check that all the clocks on your clients, servers and signing machines are accurate and in sync. A disagreement in time between a client and a server is the biggest cause of installation difficulties, as the times in the generated certificates will cause login failures if the start date is in the future.
    Box Backup compile In the following instructions, replace 0.00 with the actual version number of the archive you have downloaded. For help building on Windows, see the Windows Compile Appendix. And if you want to build a Linux RPM, look here. You need the latest version of OpenSSL, as some of the extra APIs it provides are required. You should have this anyway, as earlier versions have security flaws. (If you have an earlier version installed, the configuration script will give you instructions on enabling experimental support for older versions.) See OpenSSL notes for more information on OpenSSL issues. There are some notes in the archive about compiling on various platforms within the boxbackup-0.00 directory -- read them first. For example, if you are compiling under Linux, look for LINUX.txt as boxbackup-0.00/LINUX.txt after untaring the archive. Download the archive, then in that directory type tar xvzf boxbackup-0.00.tgz cd boxbackup-0.00 ./configure make The server and client will be built and packaged up for installation on this machine, or ready to be transferred as tar files to another machine for installation. This builds two parcels of binaries and scripts, 'backup-client' and 'backup-server'. The generated installation scripts assumes you want everything installed in /usr/local/bin Optionally, type make test to run all the tests.
    Local installation Type make install-backup-client to install the backup client. Type make install-backup-server to install the backup server.
    Remote installation In the parcels directory, there are tar files for each parcel. The name reflects the version and platform you have built it for. Transfer this tar file to the remote server, and unpack it, then run the install script. For example: tar xvzf boxbackup-0.00-backup-server-OpenBSD.tgz cd boxbackup-0.00-backup-server-OpenBSD ./install-backup-server
    Configure options You can use arguments to the configure script to adjust the compile and link lines in the generated Makefiles, should this be necessary for your platform. The configure script takes the usual GNU autoconf arguments, a full list of which can be obtained with --help. Additional options for Box Backup include: --enable-gnu-readline Use GNU readline if present. Linking Box Backup against GNU readline may create licence implications if you then distribute the binaries. libeditline is also supported as a safe alternative, and is used by default if available. --disable-largefile Omit support for large files --with-bdb-lib=DIR Specify Berkeley DB library location --with-bdb-headers=DIR Specify Berkeley DB headers location --with-random=FILE Use FILE as random number seed (normally auto-detected) --with-tmp-dir=DIR Directory for temporary files (normally /tmp) See OpenSSL notes for the OpenSSL specific options.
    Tests There are a number of unit tests provided. To compile and run one type: ./runtest.pl bbackupd release ./runtest.pl common debug ./runtest.pl ALL The runtest.pl script will compile and run the test. The first argument is the test name, and the second the type of build. Use ALL as a test name to run all the tests. The output from the tests is slightly muddled using this script. If you're developing, porting or trying out new things, it might be better to use the following scheme: cd test/bbackupd make cd ../../debug/test/bbackupd ./t or in release mode... cd test/bbackupd make -D RELEASE cd ../../release/test/bbackupd ./t (use RELEASE=1 with GNU make) I tend to use two windows, one for compilation, and one for running tests.
    Box Backup and SSL
    General notes Ideally, you need to use version 0.9.7 or later of OpenSSL. If this is installed on your system by default (and it is on most recent releases of UNIX like OSes) then everything should just work. However, if it isn't, you have a few options.
    Upgrade your installation The best option is to upgrade your installation to use 0.9.7. Hopefully your package manager will make this easy for you. This may require reinstallation of lots of software which depends on OpenSSL, so may not be ideal. (But as there have been a few security flaws in OpenSSL recently, you probably want to upgrade it anyway.)
    Install another OpenSSL The second best option is to install another copy. If you download and install from source, it will probably install into /usr/local/ssl. You can then configure Box Backup to use it using: ./configure --with-ssl-headers=/usr/local/ssl/include --with-ssl-lib=/usr/local/ssl/lib which will set up the various includes and libraries for you. The configuration scripts may be a problem, depending on your installation. See below for more information.
    Use the old version of OpenSSL If you have an old version installed, the configuration script will give you instructions on how to enable support for older versions. Read the warnings, and please, whatever you do, don't release binary packages or ports which enable this option. You may have issues with the configuration scripts, see below.
    If you have problems with the config scripts If you get OpenSSL related errors with the configuration scripts, there are two things to check: The bin directory within your OpenSSL directory is in the path (if you have installed another version) You have an openssl.cnf file which works and can be found.
    OpenSSL config file You need to have an openssl.cnf file. The default will generally work well (see example at end). Make sure the openssl utility can find it, either set the OPENSSL_CONF environment variable, or install it into the location that is mentioned in the error messages. Example OpenSSL config file: # # OpenSSL example configuration file. # This is mostly being used for generation of certificate requests. # RANDFILE = /dev/arandom #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes [ req_distinguished_name ] countryName = Country Name (2 letter code) #countryName_default = AU countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) #stateOrProvinceName_default = Some-State localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) #0.organizationName_default = Internet Widgits Pty Ltd # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = CryptSoft Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) #organizationalUnitName_default = commonName = Common Name (eg, fully qualified host name) commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ x509v3_extensions ] nsCaRevocationUrl = http://www.cryptsoft.com/ca-crl.pem nsComment = "This is a comment" # under ASN.1, the 0 bit would be encoded as 80 nsCertType = 0x40
    Compiling bbackupd on Windows using Visual C++ This Appendix explains how to build the bbackupd daemon for Windows using the Visual C++ compiler. If you have any problems following these instructions, please sign up to the mailing list and report them to us, or send an email to Chris Wilson. Thanks! Note: bbstored will not be built with this process. bbstored is not currently supported on Windows. There are no plans for bbstored support on Windows.
    Tools You will need quite a bit of software to make this work. All of it is available for free on the Internet, although Visual C++ Express has license restrictions and a time limit.
    Visual C++ Microsoft's Visual C++ compiler and development environment are part of their commercial product Visual Studio. Visual Studio 2005 is supported, and 2003 should work as well. You can also download a free copy of Visual C++ 2005 Express. This copy must be registered (activated) within 30 days, and is free for one year. You will need the Platform SDK to allow you to compile Windows applications.
    Perl Download and install ActivePerl for Windows, which you can probably find here.
    Libraries You will need to download and install several libraries. They must all be built in the same directory, to be able to link properly. Choose a directory where you will unpack and compile OpenSSL, Zlib and Box Backup. We will call this the base directory. An example might be: C:\Documents and Settings\Your Username\Desktop\Box Make sure you know the full path to this directory.
    OpenSSL You will need to compile OpenSSL using Visual C++. The latest release at this time, OpenSSL 0.9.8a, does not compile with Visual C++ 2005 out of the box, so you need a patched version. The original source and patch are also available. To compile OpenSSL: Use a Windows unzipper such as WinZip (free trial) to extract the openssl-0.9.8a-vc2005.tar.gz archive, which you just downloaded, into the base directory. Rename the folder from openssl-0.9.8a-vc2005 to openssl Open a command shell (run cmd.exe) and cd to the openssl directory Run the following commands: perl Configure VC-WIN32 ms\do_ms "c:\program files\Microsoft Visual Studio 8\Common7\Tools\vsvars32.bat" nmake -f ms\ntdll.mak
    Zlib You will need to download the Zlib compiled DLL. Create a directory called zlib in the base directory, and unzip the file you just downloaded into that directory. You don't need to compile anything.
    Download Box Backup The first version of Box Backup that's known to compile and with Visual C++ 2005 is available on the Subversion server. However, this version has not been extensively tested and may be out of date. The changes are expected to be merged into the Subversion trunk at some point, and this page should then be updated. If in doubt, please sign up to the mailing list and ask us. To get the source code out of Subversion you will need a Subversion client for Windows. After installing it, open a new command prompt, go to the base directory, and type: svn co http://www.boxbackup.org/svn/box/chris/win32/vc2005-compile-fixes/ boxbackup This should create a directory called boxbackup inside the base directory.
    Configure Box Backup Open a command prompt, change to the base directory then boxbackup, and run win32.bat to configure the sources. Otherwise, Visual C++ will complain about missing files whose names start with autogen, and missing config.h.
    Compile Box Backup Open Visual C++. Choose "File/Open/Project", navigate to the base directory, then to boxbackup\infrastructure\msvc\2005 (or 2003 if using Visual Studio 2003), and open any project or solution file in that directory. Press F7 to compile Box Backup. If the compilation is successful, boxbackup\Debug\bbackupd.exe will be created.
    Install Box Backup Create the destination directory, C:\Program Files\Box Backup\bbackupd. Write a configuration file, keys and certificate on a Unix machine, and copy them into the Box Backup directory, together with the following files from the base directory: boxbackup\Debug\bbackupd.exe openssl\out32dll\libeay32.dll openssl\out32dll\ssleay32.dll zlib\zlib1.dll Ensure that the user running Box Backup can read from the Box Backup directory, and write to the bbackupd directory inside it. Run Box Backup by double-clicking on it, and check that it connects to the server. If the window opens and closes immediately, it's probably due to a problem with the configuration file - check the Windows Event Viewer for details.
    Windows Service Box Backup can also run as a Windows service, in which case it will be automatically started at boot time in the background. To install this, open a command prompt, and run: cd "C:\Program Files\Box Backup" bbackupd.exe -i This should output Box Backup service installed.
    Compilation and installation by building an RPM on Linux It is very easy to build an RPM of Box Backup on Linux platforms. This should work on all Red Hat distributions (including Fedora), SuSE, and probably others too. Given that you have the correct development packages installed simply execute this command (replacing the version number): rpmbuild -ta boxbackup-0.00.tgz rpmbuild will report where the packages have been written to, and these can be installed in the normal manner. If you have never built an RPM before you should set up a convenient build area as described in the RPM book. If you wish to customise the package you can find the spec file in the contrib/rpm directory.
    boxbackup/docs/docbook/bb-book.xsl0000664000175000017500000000127011163413441017723 0ustar siretartsiretart boxbackup/docs/docbook/bbackupctl.xml0000664000175000017500000001440411163413441020517 0ustar siretartsiretart bbackupctl 8 Box Backup Box Backup 0.11 bbackupctl Control the Box Backup client daemon bbackupctl -q -c config-file command Description bbackupctl sends commands to a running bbackupd daemon on a client machine. It can be used to force an immediate backup, tell the daemon to reload its configuration files or stop the daemon. If bbackupd is configured in snapshot mode, it will not back up automatically, and the bbackupctl must be used to tell it when to start a backup. Communication with the bbackupd daemon takes place over a local socket (not over the network). Some platforms (notably Windows) can't determine if the user connecting on this socket has the correct credentials to execute the commands. On these platforms, ANY local user can interfere with bbackupd. To avoid this, remove the CommandSocket option from bbackupd.conf, which will also disable bbackupctl. See the Client Configuration page for more information. bbackupctl needs to read the bbackupd configuration file to find out the name of the CommandSocket. If you have to tell bbackupd where to find the configuration file, you will have to tell bbackupctl as well. The default on Unix systems is usually /etc/box/bbackupd.conf. On Windows systems, it is bbackupd.conf in the same directory where bbackupd.exe is located. If bbackupctl cannot find or read the configuration file, it will log an error message and exit. bbackupctl usually writes error messages to the console and the system logs. If it is not doing what you expect, please check these outputs first of all. Run in quiet mode. config-file Specify configuration file. Commands The following commands are available in bbackupctl: terminate This command cleanly shuts down bbackupd. This is better than killing or terminating it any other way. reload Causes the bbackupd daemon to re-read all its configuration files. Equivalent to kill -HUP. sync Initiates a backup. If no files need to be backed up, no connection will be made to the server. force-sync Initiates a backup, even if the SyncAllowScript says that no backup should run now. wait-for-sync Passively waits until the next backup starts of its own accord, and then terminates. wait-for-end Passively waits until the next backup starts of its own accord and finishes, and then terminates. sync-and-wait Initiates a backup, waits for it to finish, and then terminates. Files /etc/box/bbackupd.conf See Also bbackupd.conf 5 , bbackupd-config 8 , bbackupctl 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/html/0000775000175000017500000000000011652362373016636 5ustar siretartsiretartboxbackup/docs/docbook/html/images/0000775000175000017500000000000011652362373020103 5ustar siretartsiretartboxbackup/docs/docbook/html/images/arrow.png0000664000175000017500000000030510413044254021726 0ustar siretartsiretart‰PNG  IHDR Í÷´1t€Yä ‰ÈKƢЙÌHêÉXF¨>b©Ö ›ÃaûH¾_`a,U¹Äº˜D²œÉFCEc*8:›n¶mÖê¯XŽfóJšJ¨®†âTó Ìÿüñ3í8×BNC&S 5Òž~<ÿx’jÁí A bª—'˜}´¢ßiø ËlìÓ|鸺ìuG±Œœëyª;QåÑ=§,õójnBiö!K¢Ž´ñ|íèr9åG¡>=ÂüÏþ±ÝÍ#ö~¢ös&R‚ŒSñÆfób€‚ɽA¸÷ ÐGÎå*©sW]‚Eö~NB4›¨*s{š(çWh–mè˪f"LÆ…'áÍÆC\›sWÐÉ¡˜Zt§ d\‡OèbŽÇwXâ ¾î Z–•PÉXŽA?è#Á©g›îØiçwk¨ ÛŽñø#½<„¥F‚OOC"¹Ûæ°¿Gs¦”‚^>îdr×X¿¬ãP&½‘ ï;~Ê áøt—É&ŽA”9õâ«Eg´CØŠÐD?– ©Ý¨°§¨:‘V¦2l‘W9Kc&·,;&:È=Å1lk:^Þ²,´fÁ‡ ÂwëûÁcéÔÊAñ5] lcÉæ¯ÝÞβ9R~Ä—zåèýb!ÊOæÍÿ|û9gJYÁƒÞl@îäÛõZ“‡Ù.ERÎ×°jÌžv‰€­Ži D ”ìÝÀ”àÚ}ÂŽ5ºYTw(+F²Û'l5ãRÖÃÝÜf,3we§™s”!Ý {-¯YªÂÊæ–ð•1ø}ÌR_åŽO ö:kw8¿œ6Ö4*?`yË¡|U7ÍæÏÌ?ÁçkE¾Ÿ¬ñXð·Ûw~p7vAï„K±¹Hg¯ó‰.m­,^‚“–C”Âh.ù…­…«&1”‚Òù˜ÓW•²¼£yÒ0–d²û2&5•pŲ± ÐâZ‰Æà“2Ȩ¤fßÙ¯¿ÃÁw?>¡Q5k»µâÉ$etV(¨%)ñz—‡¨†Ç z¾GìÆñƒ½b5ëxG18'–¨“±<|š%ÇöÊe›7_Ìf¦ï°Îå–Å|Ñ4½eäJ ˜²§–jj5 ì2u\³ÞÁœÎ˜j-nׯñKØâm¡¨5”Ê Ë´I$NÂ9&–°Jå€O›ÜNÁµ><£h¶ëmã+ƒ<ÒtÁ”=03 ä,K÷÷³:m7p»ÞlºLögë÷O›óÙL(“£¡äïüÄ5¹£Ùfˆfeø¸ì9R{§ Ã"¼èd/q$™iTŸ ðçX:@g–’t2›Ä8‘ûù¸Ö¬Þ_·«†Ó{uni5ªúy{³}yj¦Þ÷ *ÚB¼¾5\x¥*˜ìšõËËœØ;;ïñ, ô©Ä㣡ì–ÉCnÚn?wá;5$<¬dçfY(NÐÊÖXÚ®Wšë'˜,:tï²ÄXKb0„ÅÅìùq¦:¡Þ‘$ÌCå,¼?b9–¡®Ý~qF•ÀŽ„[9È!ú)3HÏŒåý¤Žàøýµó=ñÞ6Î EH¹ kX(¶q)˜ôZ.»²vTž&+F4p 'eô8²¸)ð´ùâ $éVÀò%(ˆÒž¼ôãR¤UÃÖBrÛg,ç•A-Ž`28lÂÅtAcÒ–ÊYC˜øªLRÈ&8Kßæ˜Íj‘&õuõºM¸:¤ß¬®îò—È‚¨óÆN¼eÇáUM¾€7kôÈ2¨ø øìx>x>¸[±Bòñ`ð.K´H‹a‹M¦KÛì[Lqö -¬Nì=‚µ÷üĪžáúàˆBö`Âw½Y†mŠšõ§‰F/+Ô•LH†:™f½’3V )`Ûº‚ðRúq+#j¸/S¨Ó€™V`;„ÀÕ=ÓœèJêü¸"ÛÐÓÿ…|ºÃÜ™¥Pó]pcXçI“` È£"ß Ž¿ïY]±T —õõ@Uf%¶(Åíʲòª¸í@ÌþÒG »Ñd48!ìÁZÌ•aÌ’I&êX*܂б¢íy‡ZaнFåÑÏ,$´Ùñø(Á7¿³íÖÊkŒ%¼ú:¡v"•çiˆÄZi†Ò‰ºsÈA¹„]èL&˜êkrqºWu ‹îÂ(¦F}d‰\7·©7¿e(oh"°Zh]³ß5p©ÏG j›¨×Šçšv3›­Ú1›Y ]è*ÎÁXí©mØAbÿä%6Üá]RׂG5`ñfC÷;±ÎžÛíi“ ‘‹.Õœq©Ä"µ}c^WU%LG¾Mƒ´Tjäøm¯/[™ÀИ ÒŒÀ¦þ|©´:ÎÂýþºgÐ è±öÎÐooElz)N,ºç"¥Ap¬C¬¦¾my+TÃ*r2kå¨:˰ìƒOÍ,ËD÷~z¾0M (8öcÆýÞ¡ÁO(ÑÈÅ@ÜUhàwàEÄp³¸µj˜‘RQò=ës‹dEÛímj[‹…ÿ >b/½La,‡(ö-JíÅlÖPj%©"³SÖp'Në.Ò¾ÈX36ÍÊÔtÖ¦T³ŸÔerâ]•ƒHÆ{® "ʨâ³ ÁŸÂ€êV»íʘ±®¸AÏ |º²)ÇOO2Ëñß]O›s—ЯD\X^„n×€ßãWãŠåÃñÍò›å7Ëo–ß,¿Y~³üÿÈrñÍò8LvúfùÏ ö³8÷—Ï7ËOifä|âï6úfù)½,õOü[ß,? óÜ7Ënü`þ±¿TªIEND®B`‚boxbackup/docs/docbook/html/images/stepahead.png0000664000175000017500000000045210413044254022535 0ustar siretartsiretart‰PNG  IHDR¶ åz‡gAMAÖØÔOX2tEXtSoftwareAdobe ImageReadyqÉe<PLTEÌÿÿÿÃóAtRNSÿå·0JœIDATxÚì”Û € DÏþÿO¥îÍ5Ÿ‚@!ÆvÆ£…ü²qbF|î{t轘™íjÂèÑ¢(’.è4*ËÊ@‰)m=Üz‰Œâ³›âҒ€ÅÔ°¤@¬8ÆÍ¢;cêsKÅ<£=Yu”Ä$I{´à;´¥.–i±‰´YŶ˜ßÎv}HÜžúaûüOìU»VQº"ÇIEND®B`‚boxbackup/docs/docbook/html/favicon.ico0000664000175000017500000020407611163413441020756 0ustar siretartsiretart€€ ((€ !!!!!!!!!!!!!!!!!!!#+.,(!#+,,(#!&,...............,(##&+,,+#&,...+##+5<<2&+5<5,##+5<<<<<<<<<<<<<<52+##+25<5.#!,5<<<5&,!àààÿÑÑÑÿÃÃÃÿºººÿºººÿÊÊÊÿ×××ÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿÓÓÓÿÊÊÊÿÃÃÃÿÊÊÊÿÙÙÙÿàààÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâââÿãããÿåååÿâââÿÜÜÜÿÑÑÑÿÃÃÃÿ¾¾¾ÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿ¾¾¾ÿÃÃÃÿÊÊÊÿÍÍÍÿÙÙÙÿàààÿãããÿåååÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãããÿàààÿäääÿåååÿãããÿÞÞÞÿ×××ÿÑÑÑÿÃÃÃÿ¾¾¾ÿÃÃÃÿÑÑÑÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿèèèÿâââÿÔÔÔÿÊÊÊÿ¾¾¾ÿºººÿ¾¾¾ÿÊÊÊÿÙÙÙÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿäääÿàààÿ!2#dNÿ•T;ÿ•T;ÿ‘M2ÿ±„rÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿãããÿ¥q\ÿ•T;ÿ•T;ÿP7ÿdNÿÉ­ ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿºººÿ¥q\ÿ•T;ÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ˜\Dÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿP7ÿP7ÿdNÿ»•…ÿÊÊÊÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÊÊÊÿ»•…ÿ¡kUÿ•T;ÿ•T;ÿ˜\Dÿ•T;ÿ­kÿÔÔÔÿÊÊÊÿÃÃÃÿÊÊÊÿÓÓÓÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÐÁ»ÿ•T;ÿ˜\Dÿ˜\Dÿ•T;ÿP7ÿ•T;ÿ²²²ÿÔÔÔÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿãããÿÙÙÙÿ&2#!J0ÿ‰@"ÿ‰@"ÿ~1ÿ¥q\ÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿâââÿJ0ÿ~1ÿ‚8ÿ¨xdÿÊÊÊÿèèèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÓÓÓÿ¨xdÿ‚8ÿ‰@"ÿ‘J+ÿ‘J+ÿ‘M2ÿ‘M2ÿ‘M2ÿ‘M2ÿ‘M2ÿ‘M2ÿ‘J+ÿ‘J+ÿ‹D'ÿ‰@"ÿ~1ÿ‘M2ÿµŒ{ÿÊÊÊÿèèèÿèèèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿèèèÿÑÑÑÿ»•…ÿP7ÿ~1ÿ‚8ÿ‚8ÿ»•…ÿÜÜÜÿÊÊÊÿÃÃÃÿÊÊÊÿ×××ÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÝÑËÿJ0ÿ‚8ÿ‹D'ÿ‰@"ÿ~1ÿ¡kUÿÑÑÑÿÜÜÜÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿåååÿÍÍÍÿ¾¾¾ÿ52#!P7ÿ‘J+ÿ‘M2ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÙÙÙÿJ0ÿ‰@"ÿ¥¥¥ÿéééÿèèèÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿìììÿµŒ{ÿ‰@"ÿ‘M2ÿ•T;ÿžZ8ÿžZ8ÿžZ8ÿžZ8ÿžZ8ÿ•T;ÿ˜R1ÿ‘M2ÿ‹D'ÿ‹D'ÿ±„rÿÔÔÔÿïïîÿéééÿèèèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿóòòÿÜÜÜÿ»•…ÿJ0ÿ‚8ÿ‰@"ÿ¶µµÿàààÿÊÊÊÿÊÊÊÿÍÍÍÿÙÙÙÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿïïïÿÐÁ»ÿ‚8ÿ‹D'ÿ‹D'ÿJ0ÿ¶¶¶ÿÙÙÙÿÞÞÞÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿ¾¾¾ÿ¨xdÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÊÊÊÿJ0ÿ¬¬¬ÿïïïÿÿÿÿÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿìììÿµŒ{ÿ‹D'ÿ˜R1ÿžZ8ÿžZ8ÿžZ8ÿžZ8ÿžZ8ÿžZ8ÿ˜R1ÿ‹D'ÿ•T;ÿÓËÇÿèèèÿéééÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿïïîÿÍÍÍÿ¥q\ÿ‚8ÿP7ÿÊÊÊÿÜÜÜÿÊÊÊÿÊÊÊÿÍÍÍÿÜÜÜÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÞÞÞÿ¡kUÿµŒ{ÿdNÿ‹‹‹ÿÜÜÜÿÜÜÜÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÜÜÜÿ˜\Dÿ¡bIÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÍÍÍÿ¬¬¬ÿïïïÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿéééÿ¨xdÿ‹D'ÿžZ8ÿžZ8ÿ¦d?ÿ `@ÿžZ8ÿ˜R1ÿ‹D'ÿ˜\DÿÃÃÃÿåååÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿãããÿ±„rÿ~1ÿdNÿÙÙÙÿ×××ÿÊÊÊÿÊÊÊÿÑÑÑÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÍÍÍÿÙËÃÿ²²²ÿÔÔÔÿàààÿâââÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿåååÿ±„rÿ‚8ÿdNÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿéééÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÓÓÓÿ•T;ÿ‘J+ÿ¬nIÿ̳¨ÿµŒ{ÿ˜R1ÿ‘J+ÿ•T;ÿ¾¾¾ÿâââÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿãããÿ¥q\ÿ~1ÿ­kÿàààÿÑÑÑÿÃÃÃÿÊÊÊÿÓÓÓÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿâââÿ”””ÿ¶¶¶ÿéééÿäääÿäääÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿéééÿÄ¥˜ÿ‹D'ÿ‹D'ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿÀŽÿ‰@"ÿ¡bIÿÕÄ»ÿ±„rÿ‹D'ÿJ0ÿÓËÇÿÜÜÜÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÓÓÓÿP7ÿ‚8ÿ»•…ÿÞÞÞÿÊÊÊÿÃÃÃÿÊÊÊÿÔÔÔÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿ”””ÿÃÃÃÿìììÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿèèèÿÙżÿ•T;ÿ•T;ÿ‘M2ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿåååÿäääÿåååÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÜÜÜÿ•T;ÿ˜R1ÿµŒ{ÿžZ8ÿ‰@"ÿµŒ{ÿÓÓÓÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿåååÿäääÿåååÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿÄ¥˜ÿ‚8ÿ‰@"ÿÓËÇÿ×××ÿ¾¾¾ÿ¾¾¾ÿÊÊÊÿÙÙÙÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿäääÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÓÓÓÿ¡kUÿžZ8ÿ `@ÿ˜R1ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿâââÿÞÞÞÿÜÜÜÿÜÜÜÿàààÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿµŒ{ÿ‰@"ÿ˜R1ÿ‘J+ÿ¡bIÿÃÃÃÿÓÓÓÿÞÞÞÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿâââÿÞÞÞÿÜÜÜÿÜÜÜÿÞÞÞÿâââÿäääÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿéééÿ¡kUÿ‚8ÿ‘M2ÿºººÿÊÊÊÿºººÿÃÃÃÿÍÍÍÿÜÜÜÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿèèèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàààÿ±„rÿ `@ÿ¬nIÿ¬nIÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿäääÿàààÿÙÙÙÿÑÑÑÿÍÍÍÿÍÍÍÿÓÓÓÿÜÜÜÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ¾¾¾ÿ‹D'ÿ‘J+ÿ‰@"ÿ»•…ÿÑÑÑÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿäääÿàààÿÙÙÙÿÑÑÑÿÊÊÊÿÊÊÊÿÑÑÑÿ×××ÿàààÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿ¶µµÿ‰@"ÿ‰@"ÿ˜\DÿÃÃÃÿ¾¾¾ÿºººÿÃÃÃÿÑÑÑÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿèèèÿÀŽÿ `@ÿ±uPÿ±uPÿ¬nIÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãããÿÞÞÞÿÓÓÓÿÃÃÃÿ¾¾¾ÿ¾¾¾ÿÃÃÃÿÑÑÑÿÜÜÜÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿâââÿ•T;ÿ‹D'ÿP7ÿÃÃÃÿÑÑÑÿÙÙÙÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿâââÿÜÜÜÿÑÑÑÿÃÃÃÿ¾¾¾ÿ¾¾¾ÿÃÃÃÿÊÊÊÿ×××ÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ˜\Dÿ‹D'ÿ‰@"ÿ¥q\ÿ¾¾¾ÿºººÿºººÿÃÃÃÿÓÓÓÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿéééÿ¶¶¶ÿ `@ÿ±uPÿµ|Vÿµ|Vÿ¬nIÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿçÝÛÿ̲¨ÿÀŽÿĤ˜ÿλ±ÿºººÿºººÿÃÃÃÿÓÓÓÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿèèèÿÀŽÿ±„rÿµŒ{ÿÓÓÓÿÑÑÑÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿæÛØÿ̲¨ÿÀŽÿÀŽÿÏ·¬ÿºººÿºººÿ¾¾¾ÿÍÍÍÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ±„rÿ‰@"ÿ‘M2ÿ‹D'ÿ­kÿ¾¾¾ÿºººÿ¾¾¾ÿÊÊÊÿÔÔÔÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÊÊÊÿdNÿ¬nIÿµ|Vÿ¹ƒ\ÿµ|Vÿ¬nIÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿâÖÒÿ¥q\ÿ‹D'ÿ‰@"ÿ‹D'ÿ¨xdÿÏ·¬ÿºººÿ¾¾¾ÿÍÍÍÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÍÍÍÿÓËÇÿ¾¾¾ÿ×××ÿÑÑÑÿÜÜÜÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÜÎÇÿ¡kUÿ‹D'ÿ‰@"ÿ‹D'ÿ˜\DÿĤ˜ÿºººÿºººÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿÓËÇÿ‰@"ÿ˜R1ÿ˜R1ÿ‹D'ÿ»•…ÿ¾¾¾ÿºººÿ¾¾¾ÿÊÊÊÿÙÙÙÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÜÜÜÿ¥q\ÿ `@ÿ±uPÿµ|Vÿ¹ƒ\ÿµ|Vÿ±uPÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿïèèÿ¨xdÿ‰@"ÿ‘M2ÿ˜R1ÿ‘J+ÿ‰@"ÿ­kÿÔ¾´ÿºººÿÊÊÊÿÙÙÙÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ¾¾¾ÿ¨xdÿºººÿ×××ÿÓÓÓÿÜÜÜÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿæÛØÿ¡bIÿ‰@"ÿ˜R1ÿ˜R1ÿ˜R1ÿ‹D'ÿP7ÿÈ­ ÿºººÿÃÃÃÿÔÔÔÿàààÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ¾¾¾ÿ‘J+ÿ˜R1ÿ˜R1ÿ‘J+ÿ‘M2ÿÄ¥˜ÿ¾¾¾ÿºººÿÃÃÃÿÑÑÑÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿèèèÿµŒ{ÿ˜R1ÿ¬nIÿ±uPÿµ|Vÿ¹ƒ\ÿµ|Vÿ¬nIÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÕÄ»ÿ‹D'ÿ‘M2ÿžZ8ÿžZ8ÿ˜R1ÿ‘J+ÿJ0ÿÈ­ ÿ¾¾¾ÿÊÊÊÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ¶¶¶ÿJ0ÿºººÿ×××ÿÑÑÑÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿÀŽÿ‰@"ÿ˜R1ÿžZ8ÿžZ8ÿžZ8ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÊÊÊÿ˜R1ÿ˜R1ÿžZ8ÿ˜R1ÿ‘J+ÿ˜\Dÿ¶µµÿºººÿ¾¾¾ÿÍÍÍÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿÄ¥˜ÿ˜R1ÿ¦d?ÿ¬nIÿ±uPÿµ|Vÿµ|Vÿ±uPÿ¬nIÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿÀŽÿ‰@"ÿ˜R1ÿžZ8ÿžZ8ÿžZ8ÿ‘M2ÿ‰@"ÿ´Œ{ÿÊÊÊÿÑÑÑÿÞÞÞÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÃÃÃÿJ0ÿÃÃÃÿÓÓÓÿÑÑÑÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿéééÿ¨xdÿ‹D'ÿžZ8ÿžZ8ÿžZ8ÿžZ8ÿ˜R1ÿ‹D'ÿdNÿÃÃÃÿÊÊÊÿÙÙÙÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÊÊÊÿ˜R1ÿ˜R1ÿžZ8ÿžZ8ÿ˜R1ÿ‰@"ÿdNÿºººÿÃÃÃÿÓÓÓÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÃÃÃÿ˜R1ÿžZ8ÿ¦d?ÿ¬nIÿ±uPÿ±uPÿ±uPÿ±uPÿ¦d?ÿ˜R1ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòòòÿÀŽÿ‰@"ÿ˜R1ÿžZ8ÿ `@ÿžZ8ÿ˜R1ÿ‰@"ÿ±„rÿÑÑÑÿÙÙÙÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÃÃÃÿJ0ÿÃÃÃÿÑÑÑÿÍÍÍÿÙÙÙÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿéééÿ¤q\ÿ‹D'ÿžZ8ÿžZ8ÿ¦d?ÿžZ8ÿ˜R1ÿ‹D'ÿdNÿÊÊÊÿÔÔÔÿÞÞÞÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÊÊÊÿ˜R1ÿ˜R1ÿžZ8ÿžZ8ÿ˜R1ÿ‘J+ÿ‚8ÿ±„rÿÔÔÔÿÜÜÜÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ˜\Dÿ‹D'ÿžZ8ÿ¦d?ÿ¬nIÿ¬nIÿ¬nIÿ¬nIÿ¬nIÿ¦d?ÿ˜R1ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿ̲¨ÿ‰@"ÿ‘M2ÿ `@ÿ±uPÿ˜R1ÿ‘M2ÿ‰@"ÿĤ˜ÿÜÜÜÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ¶¶¶ÿ‹D'ÿ²²²ÿÍÍÍÿÊÊÊÿÔÔÔÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿ»”…ÿ‰@"ÿ˜R1ÿ¦d?ÿ¼‰fÿžZ8ÿ˜R1ÿ‰@"ÿ¨xdÿÜÜÜÿÜÜÜÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÊÊÊÿ˜R1ÿ˜R1ÿžZ8ÿžZ8ÿ˜R1ÿ‘J+ÿ‰@"ÿÄ¥˜ÿÞÞÞÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿåååÿ­kÿ‰@"ÿžZ8ÿ¦d?ÿ¦d?ÿ¬nIÿ¬nIÿ¬nIÿ¦d?ÿ `@ÿ˜R1ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿéßÝÿ˜\Dÿ‰@"ÿ˜R1ÿžZ8ÿ˜R1ÿ‰@"ÿdNÿãÙÔÿâââÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿ¬¬¬ÿ‰@"ÿ¥¥¥ÿÊÊÊÿÃÃÃÿÑÑÑÿÞÞÞÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÜÎÇÿ‘J+ÿ‹D'ÿ˜R1ÿžZ8ÿ˜R1ÿ‹D'ÿ‹D'ÿÔ¾´ÿäääÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿºººÿ‘J+ÿ˜R1ÿ¬nIÿžZ8ÿ˜R1ÿ‰@"ÿµŒ{ÿÞÞÞÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿàààÿdNÿ‘J+ÿžZ8ÿ `@ÿµ|Vÿ¼‰fÿ¬nIÿ¦d?ÿžZ8ÿ‘J+ÿ¡kUÿÃÃÃÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòòòÿÏ·¬ÿJ0ÿ‰@"ÿ‰@"ÿ‚8ÿ•T;ÿÕÄ»ÿéééÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿœœœÿ‚8ÿ‹‹‹ÿÊÊÊÿ¾¾¾ÿÊÊÊÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÈ­ ÿ‹D'ÿ‰@"ÿ‰@"ÿ‰@"ÿ‹D'ÿĤ˜ÿìììÿåååÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿÄ¥˜ÿ‰@"ÿžZ8ÿ­kÿ˜R1ÿ‰@"ÿ¨xdÿ×××ÿàààÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÍÍÍÿ•T;ÿ˜R1ÿžZ8ÿ±uPÿÀŽÿµ|VÿžZ8ÿžZ8ÿ‘J+ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÙËÃÿ´Œ{ÿ¨xdÿ»”…ÿßÕÏÿìììÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿéééÿ±„rÿ‹D'ÿ¨xdÿÃÃÃÿºººÿÃÃÃÿÔÔÔÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÙËÃÿ´Œ{ÿ¨xdÿ´Œ{ÿÙËÃÿìììÿèèèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ­kÿ‰@"ÿ¬nIÿ¬nIÿ‰@"ÿ˜\DÿÊÊÊÿàààÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿ¶µµÿ‘J+ÿ˜R1ÿžZ8ÿ»•…ÿÀ•sÿžZ8ÿžZ8ÿ‘J+ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿïïïÿìììÿïïïÿéééÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿãããÿ¥q\ÿžZ8ÿ¡bIÿ¶µµÿºººÿ¾¾¾ÿÍÍÍÿÜÜÜÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿìììÿìììÿìììÿìììÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÞÞÞÿ˜\Dÿ‘J+ÿ¡bIÿ‘J+ÿ‘J+ÿ¶¶¶ÿàààÿãããÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿåååÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ»•…ÿ‹D'ÿ˜R1ÿµ|Vÿ̳¨ÿ¬nIÿ˜R1ÿ‘J+ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÊÊÊÿ¬nIÿ¬nIÿ¦d?ÿ»•…ÿºººÿºººÿÃÃÃÿÓÓÓÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿ¶µµÿ‹D'ÿ˜R1ÿ˜R1ÿ‹D'ÿÀŽÿàààÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿãããÿâââÿäääÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿèèèÿ¨xdÿ‹D'ÿžZ8ÿÉ­ ÿÀŽÿ˜R1ÿ‹D'ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÄ¥˜ÿ¬nIÿ¹ƒ\ÿ±uPÿ­kÿ¶¶¶ÿºººÿ¾¾¾ÿÊÊÊÿ×××ÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿ¥q\ÿ‹D'ÿ˜R1ÿ‰@"ÿ±„rÿÜÜÜÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãããÿÞÞÞÿÜÜÜÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÜÜÜÿ¡bIÿ‹D'ÿ¼‰fÿÙËÃÿ±uPÿ‹D'ÿ¡kUÿÃÃÃÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿäääÿäääÿäääÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ¼‰fÿ¹ƒ\ÿÂ’gÿÂ’gÿµ|VÿÀŽÿºººÿºººÿ¾¾¾ÿÍÍÍÿÙÙÙÿàààÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿéééÿ¶µµÿ‘J+ÿ˜R1ÿ‘J+ÿ¡kUÿÓÓÓÿàààÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿÔÔÔÿÑÑÑÿÙÙÙÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÃÃÃÿ˜R1ÿ˜R1ÿÏ·¬ÿÉ­ ÿ‘J+ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿäääÿàààÿÞÞÞÿàààÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿ¶¶¶ÿ¹ƒ\ÿÈšmÿѨxÿѨxÿÂ’gÿ¼‰fÿ¶µµÿºººÿºººÿÃÃÃÿÍÍÍÿÙÙÙÿàààÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿäääÿäääÿÑÑÑÿdNÿ˜R1ÿ˜R1ÿ `@ÿÃÃÃÿàààÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÓÓÓÿÓÓÓÿÍÍÍÿÊÊÊÿÑÑÑÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïîÿÓËÇÿ‘J+ÿ±uPÿÏ·¬ÿ¡bIÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿãããÿÜÜÜÿÔÔÔÿ×××ÿÜÜÜÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿåååÿÙÙÙÿÀ•sÿÈšmÿѨxÿܸ…ÿܸ…ÿѨxÿÂ’gÿÀ•sÿ¶¶¶ÿºººÿºººÿÃÃÃÿÊÊÊÿÔÔÔÿÜÜÜÿãããÿåååÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿâââÿàààÿ×××ÿ±„rÿ˜R1ÿžZ8ÿžZ8ÿ¶µµÿàààÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿ­kÿ¬¬¬ÿÊÊÊÿ¾¾¾ÿÊÊÊÿÙÙÙÿâââÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿìììÿµŒ{ÿ‘J+ÿ¼‰fÿ­kÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÞÞÞÿÿÿÿÿÙÙÙÿÍÍÍÿÊÊÊÿÑÑÑÿÜÜÜÿâââÿäääÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿâââÿàààÿÓËÇÿÂ’gÿѨxÿܸ…ÿéÍ•ÿéÍ•ÿܸ…ÿѨxÿÈšmÿ»•…ÿ¶¶¶ÿºººÿºººÿ¾¾¾ÿÃÃÃÿÑÑÑÿÙÙÙÿÞÞÞÿâââÿäääÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿäääÿâââÿÞÞÞÿÜÜÜÿÓÓÓÿµŒ{ÿžZ8ÿ¦d?ÿžZ8ÿÀŽÿÞÞÞÿàààÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿéééÿÑÑÑÿ `@ÿ­kÿÃÃÃÿºººÿÃÃÃÿÑÑÑÿÞÞÞÿäääÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿèèèÿãããÿ¥q\ÿ¬nIÿÀŽÿÀŽÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåååÿìììÿ”””ÿ²²²ÿâââÿÊÊÊÿÃÃÃÿÃÃÃÿÍÍÍÿ×××ÿÜÜÜÿàààÿãããÿäääÿåååÿåååÿåååÿäääÿâââÿÞÞÞÿÜÜÜÿºººÿÂ’gÿѨxÿܸ…ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿܸ…ÿܸ…ÿÈšmÿ»•…ÿ¶µµÿºººÿºººÿºººÿÃÃÃÿÊÊÊÿÑÑÑÿ×××ÿÜÜÜÿÞÞÞÿâââÿãããÿäääÿåååÿåååÿåååÿåååÿåååÿåååÿäääÿãããÿâââÿÞÞÞÿÜÜÜÿ×××ÿÔÔÔÿÃÃÃÿµŒ{ÿ¬nIÿ¬nIÿ¦d?ÿµŒ{ÿÓÓÓÿÙÙÙÿÞÞÞÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿåååÿÄ¥˜ÿ `@ÿ¬nIÿ¬¬¬ÿºººÿºººÿÊÊÊÿ×××ÿÞÞÞÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿåååÿÍÍÍÿ±„rÿ̳¨ÿÕÄ»ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿ”””ÿ‘J+ÿ¶¶¶ÿÔÔÔÿ¾¾¾ÿºººÿ¾¾¾ÿÊÊÊÿÍÍÍÿÔÔÔÿÙÙÙÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÙÙÙÿ×××ÿÔÔÔÿ¶¶¶ÿÂ’gÿѨxÿܸ…ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿܸ…ÿÈšmÿÀ•sÿ¬¬¬ÿºººÿºººÿºººÿ¾¾¾ÿÃÃÃÿÊÊÊÿÍÍÍÿÑÑÑÿÔÔÔÿÙÙÙÿÙÙÙÿÜÜÜÿÜÜÜÿÞÞÞÿÞÞÞÿÜÜÜÿÜÜÜÿÜÜÜÿÙÙÙÿÔÔÔÿÑÑÑÿÍÍÍÿÊÊÊÿ¶µµÿ¼‰fÿµ|Vÿ¹ƒ\ÿµ|Vÿ¼‰fÿ¾¾¾ÿÍÍÍÿÑÑÑÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÔÔÔÿÊÊÊÿ¹ƒ\ÿµ|Vÿ±uPÿµŒ{ÿºººÿºººÿ¾¾¾ÿÊÊÊÿÑÑÑÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿÓÓÓÿ×××ÿºººÿ̶¬ÿÏ·¬ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿ¬¬¬ÿ‰@"ÿžZ8ÿÀŽÿÃÃÃÿºººÿºººÿºººÿ¾¾¾ÿÃÃÃÿÊÊÊÿÊÊÊÿÍÍÍÿÍÍÍÿÍÍÍÿÊÊÊÿÊÊÊÿÓËÇÿÂ’gÿѨxÿܸ…ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿܸ…ÿѨxÿÈšmÿœœœÿ¬¬¬ÿºººÿºººÿºººÿºººÿ¾¾¾ÿÃÃÃÿÃÃÃÿÊÊÊÿÊÊÊÿÍÍÍÿÍÍÍÿÍÍÍÿÍÍÍÿÍÍÍÿÍÍÍÿÊÊÊÿÊÊÊÿÊÊÊÿÃÃÃÿ¶¶¶ÿÀŽÿ¼‰fÿÂ’gÿÈšmÿÂ’gÿÂ’gÿ¶µµÿ¾¾¾ÿ¾¾¾ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÀŽÿ¼‰fÿÀ•sÿÂ’gÿ¼‰fÿ¬¬¬ÿºººÿºººÿ¾¾¾ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿÊÊÊÿµŒ{ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïïÿ¶¶¶ÿ˜R1ÿžZ8ÿ `@ÿ­kÿÄ¥˜ÿºººÿºººÿºººÿºººÿ¾¾¾ÿ¾¾¾ÿÃÃÃÿÃÃÃÿÃÃÃÿ¶µµÿÀŽÿÂ’gÿѨxÿܸ…ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿܸ…ÿѨxÿÈšmÿÂ’gÿÀ•sÿ¥¥¥ÿ²²²ÿºººÿºººÿºººÿºººÿ¾¾¾ÿ¾¾¾ÿ¾¾¾ÿ¾¾¾ÿÃÃÃÿÃÃÃÿÃÃÃÿÃÃÃÿ¾¾¾ÿ¶¶¶ÿ¬¬¬ÿÀ•sÿÂ’gÿÂ’gÿѨxÿѨxÿѨxÿѨxÿÓËÇÿºººÿ¶¶¶ÿ¶¶¶ÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿ¶µµÿÀ•sÿÈšmÿѨxÿѨxÿÂ’gÿ³±°ÿºººÿ¶¶¶ÿ¶¶¶ÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿºººÿ¶¶¶ÿ»•…ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììÿÃÃÃÿžZ8ÿ¦d?ÿ¬nIÿ¬nIÿ±uPÿ¼‰fÿÀŽÿÓËÇÿÓËÇÿ¶µµÿ¶µµÿÓËÇÿÄ¥˜ÿÀ•sÿÂ’gÿÈšmÿѨxÿܸ…ÿܸ…ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿܸ…ÿܸ…ÿܸ…ÿѨxÿÈšmÿÂ’gÿÂ’gÿÀ•sÿœœœÿ¥¥¥ÿ¬¬¬ÿ¬¬¬ÿ²²²ÿ¬¬¬ÿ²²²ÿ¬¬¬ÿ¬¬¬ÿ¥¥¥ÿœœœÿÀ•sÿÂ’gÿÂ’gÿÈšmÿѨxÿѨxÿܸ…ÿܸ…ÿܸ…ÿѨxÿѨxÿѨxÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÈšmÿÈšmÿѨxÿܸ…ÿѨxÿѨxÿÈšmÿÈšmÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿÀ•sÿ¼‰fÿ¼‰fÿ¼‰fÿ­kÿ¨xdÿ¥q\ÿdNÿ­kÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÍÍÍÿ `@ÿ¦d?ÿ±uPÿ¹ƒ\ÿ¹ƒ\ÿ¹ƒ\ÿ¹ƒ\ÿ¹ƒ\ÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿÂ’gÿÂ’gÿѨxÿѨxÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿéÍ•ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿѨxÿѨxÿÈšmÿÂ’gÿÂ’gÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¹ƒ\ÿ¼‰fÿÂ’gÿÂ’gÿÈšmÿѨxÿѨxÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿѨxÿѨxÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿѨxÿѨxÿܸ…ÿܸ…ÿܸ…ÿѨxÿѨxÿѨxÿÈšmÿÈšmÿÈšmÿÈšmÿÂ’gÿÈšmÿÂ’gÿÂ’gÿÂ’gÿ¼‰fÿ¹ƒ\ÿ¹ƒ\ÿµ|Vÿ¬nIÿ `@ÿ‘J+ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéééÿÜÜÜÿ¡bIÿ¦d?ÿµ|Vÿ¹ƒ\ÿ¼‰fÿÂ’gÿÂ’gÿÂ’gÿÂ’gÿÈšmÿÈšmÿÈšmÿÈšmÿѨxÿѨxÿѨxÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿѨxÿѨxÿѨxÿѨxÿѨxÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿѨxÿѨxÿѨxÿѨxÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿܸ…ÿܸ…ÿܸ…ÿܸ…ÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿÈšmÿÈšmÿÂ’gÿÂ’gÿ¹ƒ\ÿµ|Vÿ¬nIÿžZ8ÿ¥q\ÿÃÃÃÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿâââÿ¡bIÿ¦d?ÿ±uPÿ¹ƒ\ÿ¼‰fÿÂ’gÿÂ’gÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿѨxÿÈšmÿÈšmÿÀ•sÿÂ’gÿ¼‰fÿµ|Vÿ¬nIÿžZ8ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ `@ÿ `@ÿ±uPÿµ|Vÿ¹ƒ\ÿ¼‰fÿÂ’gÿÂ’gÿÂ’gÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÈšmÿÂ’gÿÂ’gÿ¼‰fÿ¼‰fÿ¹ƒ\ÿµ|Vÿ¬nIÿ˜R1ÿ¥q\ÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ˜\DÿžZ8ÿ¬nIÿ±uPÿµ|Vÿµ|Vÿ¹ƒ\ÿ¹ƒ\ÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¹ƒ\ÿ¹ƒ\ÿ¹ƒ\ÿµ|Vÿ±uPÿ¬nIÿ¦d?ÿ˜R1ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ˜\DÿžZ8ÿ¦d?ÿ¬nIÿ¬nIÿ±uPÿ±uPÿ±uPÿ±uPÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿµ|Vÿ±uPÿ±uPÿ¬nIÿ¬nIÿ¬nIÿ `@ÿ‘M2ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ˜\Dÿ˜R1ÿ `@ÿ¦d?ÿ¬nIÿ¹ƒ\ÿ¹ƒ\ÿ¹ƒ\ÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¹ƒ\ÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ¼‰fÿ±uPÿ¦d?ÿ¦d?ÿžZ8ÿ‘J+ÿ¡kUÿ¾¾¾ÿ52#!•T;ÿ‘J+ÿ˜R1ÿ‰@"ÿ¨xdÿÃÃÃÿÃÃÃÿ×××ÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ•T;ÿ˜R1ÿžZ8ÿ¬nIÿÀŽÿÏ·¬ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÕ¿´ÿÉ­ ÿ¹ƒ\ÿ `@ÿžZ8ÿ‘J+ÿ¡kUÿÃÃÃÿ52#!P7ÿ‘M2ÿžZ8ÿ‰@"ÿ­kÿÊÊÊÿÊÊÊÿÙÙÙÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ•T;ÿ‘M2ÿžZ8ÿ¼‰fÿâ×ÒÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿøòïÿíåãÿÉ­ ÿ `@ÿ˜R1ÿ‹D'ÿ¡kUÿÊÊÊÿ.,!!P7ÿ‘J+ÿ•T;ÿ‰@"ÿ±„rÿÜÜÜÿ×××ÿÞÞÞÿäääÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ•T;ÿ‘J+ÿ˜R1ÿ¥q\ÿÕ¿´ÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿêÜÕÿÜÎÇÿ»•…ÿžZ8ÿ˜R1ÿ‹D'ÿ¥q\ÿÙÙÙÿ&#!J0ÿJ0ÿP7ÿ~1ÿ±„rÿìììÿàààÿãããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿàààÿ‘M2ÿ‰@"ÿ‘J+ÿ‘J+ÿ•T;ÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ¡bIÿ `@ÿ‘J+ÿ‘J+ÿ‘J+ÿ‚8ÿ¥q\ÿéééÿ<¨xdÿµŒ{ÿ»•…ÿ¡kUÿÀŽÿ¡bIÿP7ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿ•T;ÿJ0ÿ­kÿ5ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÁÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿboxbackup/docs/docbook/html/bbdoc.css0000664000175000017500000000377410413044254020421 0ustar siretartsiretartbody { font-family: Verdana, Geneva, Arial, sans-serif; background-color: #edeef3; font-size: .75em; line-height: 180%; text-align: left; margin-top: 20px; margin-right: 100px; margin-left: 250px; position: relative; width: auto; } table { font-family: Verdana, Geneva, Arial, sans-serif; background-color: #edeef3; font-size: 10pt; line-height: 100%; } div.navheader { font-family: Verdana, Geneva, Arial, sans-serif; background-color: #edeef3; line-height: 100%; } #header { background-color: #e4e6ed; text-align: left; padding-top: 10px; margin-right: -100px; margin-left: -250px; top: 20px; border-top: 1px solid #c4c4d5; border-bottom: 1px solid white } #logo { position: relative; margin-left: 200px } #page { font-size: .75em; line-height: 180%; text-align: left; margin-top: 50px; margin-right: 100px; margin-left: 250px; position: relative; width: auto } #disc { } .informaltable td,tr {font-size: 1em; line-height: 140%; text-align: left; background-color: #e4e6ed; padding: 4px } tr,td {font-size: 1em; line-height: 100%; background-color: #edeef3; } pre, tt { font-size: 1.3em; color: #088; letter-spacing: 1px; word-spacing: 2px} h1 { color: #c00; font-size: 16pt; margin-bottom: 2em; margin-left: -50px } h2 { color: #324e95; font-size: 12pt; margin-top: 2em; margin-left: -50px } h3 { color: #324e95; font-size: 10pt; margin-top: 2em; margin-left: -50px } dt { font-weight: bold } ul { list-style-image: url(images/arrow.png) } ul li { background-color: #e4e6ed; margin: 1em 6em 1em -2em; padding: 0.2em 0.5em 0.2em 1em; border-style: solid; border-width: 1px; border-color: #c4c4d5 #fff #fff #c4c4d5 } a:link { color: #324e95; text-decoration: none; background-color: transparent } a:visited { color: #90c; text-decoration: none } a:hover { color: #c00; text-decoration: underline; background-color: transparent } boxbackup/docs/docbook/html/bbdoc-man.css0000664000175000017500000000345710420122151021157 0ustar siretartsiretartbody { font-family: Verdana, Geneva, Arial, sans-serif; background-color: #edeef3; font-size: .75em; line-height: 180%; text-align: left; margin-top: 20px; margin-right: 100px; margin-left: 250px; position: relative; width: auto; } table { font-family: Verdana, Geneva, Arial, sans-serif; background-color: #edeef3; font-size: 10pt; line-height: 100%; } code { font-size: 11pt; } div.navheader { font-family: Verdana, Geneva, Arial, sans-serif; background-color: #edeef3; line-height: 100%; } #header { background-color: #e4e6ed; text-align: left; padding-top: 10px; margin-right: -100px; margin-left: -250px; top: 20px; border-top: 1px solid #c4c4d5; border-bottom: 1px solid white } #logo { position: relative; margin-left: 200px } #page { font-size: .75em; line-height: 180%; text-align: left; margin-top: 50px; margin-right: 100px; margin-left: 250px; position: relative; width: auto } #disc { } .informaltable td,tr {font-size: 1em; line-height: 140%; text-align: left; background-color: #e4e6ed; padding: 4px } tr,td {font-size: 1em; line-height: 100%; background-color: #edeef3; } pre, tt { font-size: 1.3em; color: #088; letter-spacing: 1px; word-spacing: 2px} h1 { color: #c00; font-size: 16pt; margin-bottom: 2em; margin-left: -50px } h2 { color: #324e95; font-size: 12pt; margin-top: 2em; margin-left: -50px } h3 { color: #324e95; font-size: 10pt; margin-top: 2em; margin-left: -50px } dt { font-weight: bold } a:link { color: #324e95; text-decoration: none; background-color: transparent } a:visited { color: #90c; text-decoration: none } a:hover { color: #c00; text-decoration: underline; background-color: transparent } boxbackup/docs/docbook/adminguide.xml0000664000175000017500000024565711163413441020533 0ustar siretartsiretart ]> Box Backup administrator's guide License Copyright © 2003 - 2007, Ben Summers and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. All use of this software and associated advertising materials must display the following acknowledgement: This product includes software developed by Ben Summers and contributors. The names of the Authors may not be used to endorse or promote products derived from this software without specific prior written permission. [Where legally impermissible the Authors do not disclaim liability for direct physical injury or death caused solely by defects in the software unless it is modified by a third party.] THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Configuration
    System configuration
    Server After you've downloaded and compiled the programs you need to install the programs on your server. As root do the following: make install-backup-server This assumes that you are installing on the same server that you compiled the software on. If not, copy the boxbackup-x.xx-backup-server-OSNAME.tgz file to the server you want to run on, and install there. For example (on Mac OS X): tar zxvf boxbackup-0.10-server-darwin8.5.0.tgz cd boxbackup-0.10-server-darwin8.5.0 ./install-backup-server Then create the user for the backup daemon on the server: useradd _bbstored Box Backup has a built-in software RAID facility (redundant array of inexpensive disks) for the backup store. This allows you to spread the store data over three disks, and recover from the loss of any one disk without losing data. However, this is now deprecated, and you are recommended to use the software or hardware RAID facilities of your operating system instead. Use the following command if you want to create a simple server without Box Backup RAID: mkdir /tmp/boxbackupRepository # Create the directory chown _bbstored /tmp/boxbackupRepository/ # Change the owner to the new boxbackup daemon user /usr/local/sbin/raidfile-config /etc/box/ 1024 /tmp/boxbackupRepository #substitute 1024 with the desired blocksize #substitute /tmp/boxbackupRepository with a directory that exists where you want the backup store located #/usr/local/sbin/raidfile-config --help shows you the options Then create the configuration file /etc/box/bbstored.conf The hostname is tricky as it is used for two things: The name of the server in the certificate and the address the server is listening on. Since you might be using NAT, might move the server around or the domain name might change, choose a name that describes the server. When the network address of the server changes, you need to update the ListenAddresses directive in the /etc/box/bbstored.conf file. /usr/local/sbin/bbstored-config /etc/box hostname _bbstored This last step outputs 5 instructions that you must execute to the letter. A lot of questions are raised on the mailing list because these steps have not been followed properly. TODO: Expand on this. Explain the 5 steps in detail. If you want to run the server as a non-root user, look here.
    Certificate Management There are two steps involved to create an account. You need to create the account on the server, and sign a certificate to give the client permission to connect to the server. Running a Certification Authority for TLS (SSL) connections is not trivial. However, a script to does most of the work in a way which should be good enough for most deployments. The certificate authority directory is intended to be stored on another server. It should not be kept on the backup server, in order to limit the impact of a server compromise. The instructions and the script assume that it will be kept elsewhere, so will ask you to copy files to and from the CA. SSL certificates contain validity dates, including a "valid from" time. If the clock on the machine which signs the certificates is not syncronised to the clocks of the machines using these certificates, you will probably get strange errors until the start time is reached on all machines. If you get strange errors when attempting to use new certificates, check the clocks on all machines (client, store and CA). You will probably just need to wait a while until the certificates become valid, rather than having to regenerate them.
    Set up a Certificate Authority It is recommended that you keep your Certificate Authority on a separate machine than either the client or the server, preferably without direct network access. The contents of this directory control who can access your backup store server. To setup the basic key structure, do the following: /usr/local/sbin/bbstored-certs ca init (See OpenSSL notes if you get an OpenSSL error) This creates the directory called ca in the current directory, and initialises it with basic keys.
    Sign a server certificate When you use the bbstored-config script to set up a config file for a server, it will generate a certificate request (CSR) for you. Transfer it to the machine with your CA, then do: /usr/local/sbin/bbstored-certs ca sign-server hostname-csr.pem This signs the certificate for the server. Follow the instructions in the output on which files to install on the server. The CSR file is now no longer needed. Make sure you run this command from the directory above the directory 'ca'. TODO: Explain instructions in output.
    Set up an account Choose an account number for the user. This must be unique on the server, and is presented as a 31 bit number in hex greater than 0, for example, 1 or 75AB23C. Then on the backup store server, create the account with: /usr/local/sbin/bbstoreaccounts create 75AB23C 0 4096M 4505M This looks complicated. The numbers are, in order: The account number allocated (hex) The RAID disc set (0 if you use raidfile-config and don't add a new set) Soft limit (size) Hard limit (size) The sizes are are specified in Mb, Gb, or blocks, depending on the suffix. 1M specifies 1 Mb, 1G specifies 1 Gb, and 1B specifies 1 block, the size of which depends on how you have configured the raidfile system with raidfile-config. In this example, I have allocated 4Gb (assuming you use 2048 byte blocks as per my example) as the soft limit, and 4Gb + 10% as the hard limit. NOTE The sizes specified here are pre-RAID. So if you are using userland RAID, you are actually allocating two-thirds of this amount. This means that, when you take compression into account, that if you allocate 2Gb on the server, it'll probably hold about 2Gb of backed up files (depending on the compressability of those files). The backup client will (voluntarily) try not to upload more data than is allowed by the soft limit. The store server will refuse to accept a file if it would take it over the hard limit, and when doing housekeeping for this account, try and delete old versions and deleted files to reduce the space taken to below the soft limit. This command will create some files on disc in the raid file directories (if you run as root, the utility will change to the user specified in the bbstored.conf file to write them) and update the accounts file. A server restart is not required. NOTE If you get a message saying 'Exception: RaidFile (2/8)', the directories you specified in the raidfile.conf are not writable by the _bbstored user -- fix it, and try again. Finally, tell the user their account number, and the hostname of your server. They will use this to set up the backup client, and send you a CSR. This has the account number embedded in it, and you should be sure that it has the right account number in it. Sign this CSR with this command: /usr/local/sbin/bbstored-certs ca sign 75AB23C-csr.pem Don't forget to check that the embedded account number is correct! Then send the two files back to the user, as instructed by the script. Please read the Troubleshooting page if you have problems. TODO: Link to troubleshooting...
    Log Files You may wish to see what's going on with the server. Edit /etc/syslog.conf, and add: local6.info /var/log/box local5.info /var/log/raidfile Note: Separators must be tabs, otherwise these entries will be ignored. touch /var/log/box touch /var/log/raidfile Set up log rotation for these new log files. For example, if you have /etc/newsyslog.conf, add the following lines to it: /var/log/box 644 7 2000 * Z /var/log/raidfile 644 7 2000 * Z If you have /etc/logrotate.d, create a new file in there (for example /etc/logrotate.d/boxbackup) containing the following: /var/log/box /var/log/raidfile { weekly create compress rotate 52 } Then restart syslogd, for example: /etc/init.d/syslogd restart
    Configuring a client Before you can do any configuration, you need to know the hostname of the server you will be using, and your account number on that server. Later in the process, you will need to send a certificate request to the administrator of that server for it to be signed. Installation is covered in the compiling and installing section. You only need the backup-client parcel. It is important that you read all the output of the config scripts. See the end of this page for an example. The backup client has to be run as root, because it needs to read all your files to back them up, although it is possible to back up a single user's files by running it as that user. (Tip: specify a directory other than /etc/box, and then give the alternate config file as the first argument to bbackupd). However, it will fall over if you don't give yourself read access to one of your files.
    Basic configuration Run the bbackupd-config script to generate the configuration files and generate a private key and certificate request. /usr/local/sbin/bbackupd-config /etc/box lazy 999 hostname /var/bbackupd /home (See OpenSSL notes if you get an OpenSSL error) The items in bold need to be changed. In order, they are the account number, the hostname of the server you're using, and finally, the directories you want backed up. You can include as many you want here. However, the directories you specify must not contain other mounted file systems within them at any depth. Specify them separately, one per mount point. No checks are currently made to catch bad configuration of this nature! You may also want to consider changing the mode from lazy to snapshot, depending on what your system is used for: Lazy Mode This mode regularly scans the files, with only a rough schedule. It uploads files as and when they are changed, if the latest version is more than a set age. This is good for backing up user's documents stored on a server, and spreads the load out over the day. Snapshot Mode This mode emulates the traditional backup behaviour of taking a snapshot of the filesystem. The backup daemon does absolutely nothing until it is instructed to make a backup using the bbackupctl utility (probably as a cron job), at which point it uploads all files which have been changed since the last time it uploaded. When you run the config script, it will tell you what you need to do next. Don't forget to read all the output. An example is shown at the end of this page, but the instructions for your installation may be different.
    Certificates After you have sent your certificate request off to the server administrator and received your certificate and CA root back, install them where instructed by the bbackupd-config script during basic bbackupd configuration. You can then run the daemon (as root) by running /usr/local/sbin/bbackupd, and of course, adding it to your system's startup scripts. The first time it's run it will upload everything. Interrupting it and restarting it will only upload files which were not uploaded before - it's very tolerant. If you run in snapshot mode, you will need to add a cron job to schedule backups. The config script will tell you the exact command to use for your system. Please read the Troubleshooting page if you have problems. Remember to make a traditional backup of the keys file, as instructed. You cannot restore files without it. It is recommended that you backup up all of /etc/box as it will make things easier if you need to restore files. But only the keys are absolutely essential. If you want to see what it's doing in more detail (probably a good idea), follow the instructions in the server setup to create new log files with syslog.
    Adding and removing backed up locations By editing the /etc/box/bbackupd.conf file, you can add and remove directories to back up - see comments in this file for help. Send bbackupd a HUP signal after you modify it. When you remove a location, it will not be marked as deleted immediately. Instead, bbackupd waits about two days before doing so, just in case you change your mind. After this, it will be eventually removed from the store by the housekeeping process. Run as root. The backup client is designed to be run as root. It is possible to run without root, but this is not recommended. Clock synchronisation for file servers. If you are using the backup client to backup a filesystem served from a fileserver, you should ideally ensure that the fileserver clocks are synchronised with the fileserver. bbackupd will cope perfectly well if the clocks are not synchronised. Errors up to about half an hour cause no problems. Larger discrepancies cause a loss of efficiency and the potential to back up a file during a write process. There is a configuration parameter MaxFileTimeInFuture, which specifies how far in the future a file must be for it to be uploaded as soon as it is seen. You should not need to adjust this (default is 2 days). Instead, get those clocks synchronised. Excluding files and directories from the backup. Within the bbackupd.conf file, there is a section named BackupLocations which specifies which locations on disc should be backed up. It has subsections, each of which is in the format: name { Path = /path/of/directory (optional exclude directives) } name is derived from the Path by the config script, but should merely be unique. The exclude directives are of the form: [Exclude|AlwaysInclude][File|Dir][|sRegex] = regex or full pathname (The regex suffix is shown as 'sRegex' to make File or Dir plural) For example: ExcludeDir = /home/guest-user ExcludeFilesRegex = *.(mp3|MP3)\$ AlwaysIncludeFile = /home/username/veryimportant.mp3 This excludes the directory /home/guest-user from the backup along with all mp3 files, except one MP3 file in particular. In general, Exclude excludes a file or directory, unless the directory is explicitly mentioned in a AlwaysInclude directive. If a directive ends in Regex, then it is a regular expression rather than a explicit full pathname. See man 7 re_format for the regex syntax on your platform.
    Example configuration output This is an example of output from the bbstored-config script. Follow the instructions output by your script, not the ones here -- they may be different for your system. /usr/local/sbin/bbackupd-config /etc/box lazy 51 server.example.com /var/bbackupd /home /etc/samba Setup bbackupd config utility. Configuration: Writing configuration file: /etc/box/bbackupd.conf Account: 51 Server hostname: server.example.com Directories to back up: /home /etc/samba Note: If other file systems are mounted inside these directories, then problems may occur with files on the store server being renamed incorrectly. This will cause efficiency problems, but not affect the integrity of the backups. WARNING: Directories not checked against mountpoints. Check mounted filesystems manually. Creating /etc/box... Creating /etc/box/bbackupd Generating private key... [OpenSSL output omitted] Generating keys for file backup Writing notify script /etc/box/bbackupd/NotifyStoreFull.sh Writing configuration file /etc/box/bbackupd.conf =================================================================== bbackupd basic configuration complete. What you need to do now... 1) Make a backup of /etc/box/bbackupd/51-FileEncKeys.raw This should be a secure offsite backup. Without it, you cannot restore backups. Everything else can be replaced. But this cannot. KEEP IT IN A SAFE PLACE, OTHERWISE YOUR BACKUPS ARE USELESS. 2) Send /etc/box/bbackupd/51-csr.pem to the administrator of the backup server, and ask for it to be signed. 3) The administrator will send you two files. Install them as /etc/box/bbackupd/51-cert.pem /etc/box/bbackupd/serverCA.pem after checking their authenticity. 4) You may wish to read the configuration file /etc/box/bbackupd.conf and adjust as appropraite. There are some notes in it on excluding files you do not wish to be backed up. 5) Review the script /etc/box/bbackupd/NotifyStoreFull.sh and check that it will email the right person when the store becomes full. This is important -- when the store is full, no more files will be backed up. You want to know about this. 6) Start the backup daemon with the command /usr/local/sbin/bbackupd in /etc/rc.local, or your local equivalent. Note that bbackupd must run as root. =================================================================== Remember to make a secure, offsite backup of your backup keys, as described in Basic configuration above. If you do not, and that key is lost, you have no backups.
    Configuration Options Box Backup has many options in its configuration file. We will try to list them all here. First of all, here is an example configuration file, for reference: Example Configuration File StoreHostname = localhost AccountNumber = 0x2 KeysFile = /etc/box/2-FileEncKeys.raw CertificateFile = /etc/box/2-cert.pem PrivateKeyFile = /etc/box/2-key.pem TrustedCAsFile = /etc/box/serverCA.pem DataDirectory = /var/run/boxbackup NotifyScript = /etc/box/NotifySysadmin.sh CommandSocket = /var/run/box/bbackupd.sock UpdateStoreInterval = 86400 MinimumFileAge = 3600 MaxUploadWait = 7200 FileTrackingSizeThreshold = 65536 DiffingUploadSizeThreshold = 65536 MaximumDiffingTime = 20 ExtendedLogging = no LogAllFileAccess = yes Server { PidFile = /var/run/bbackupd.pid } BackupLocations { etc { Path = /etc } home { Path = /home ExcludeDir = /home/shared ExcludeDir = /home/chris/.ccache ExcludeDir = /home/chris/.mozilla/firefox/vvvkq3vp.default/Cache } } As you can see from the example above, the configuration file has a number of subsections, enclosed in curly braces {}. Some options appear outside of any subsection, and we will refer to these as root options. The available options in each section are described below. Every option has the form name = value. Names are not case-sensitive, but values are. Depending on the option, the value may be: a path (to a file or directory); a number (usually in seconds or bytes); a boolean (the word Yes or No); a hostname (or IP address). Paths are specified in native format, i.e. a full Windows path with drive letter on Windows clients, or a full Unix path on Unix clients. Example: StoreObjectInfoFile = /var/state/boxbackup/bbackupd.dat StoreObjectInfoFile = C:\Program Files\Box Backup\data\bbackupd.dat The use of relative paths (which do not start with a forward slash on Unix, or a drive specification on Windows) is possible but not recommended, since they are interpreted relative to the current working directory when bbackupd was started, which is liable to change unexpectedly over time. Numbers which start with "0x" are interpreted as hexadecimal. Numbers which do not start with "0x" are interpreted as decimal.
    Root Options These options appear outside of any subsection. By convention they are at the beginning of the configuration file. Some options are required, and some are optional. StoreHostname (required) The Internet host name (DNS name) or IP address of the server. This is only used to connect to the server. AccountNumber (required) The number of the client's account on the server. This must be provided by the server operator, and must match the account number in the client's certificate, otherwise the client will not be able to log into the server. The account number may be specified in hexadecimal (starting with 0x, as in the example above) or in decimal, but since the server operator works in hexadecimal, that format is highly recommended and is the default. KeysFile (required) The path to the file containing the encryption key used for data encryption of client file data and filenames. This is the most important file to keep safe, since without it your backups cannot be decrypted and are useless. Likewise, if an attacker gets access to this key and to your encrypted backups, he can decrypt them and read all your data. Do not change the encryption key without deleting all files from the account on the server first. None of your old files on the store will be readable if you do so, and if you change it back, none of the files uploaded with the new key will be readable. CertificateFile (required) The path to the OpenSSL client certificate in PEM format. This is supplied by the server operator in response to the certificate request which you send to them. Together with the PrivateKeyFile, this provides access to the store server and the encrypted data stored there. It is not critical to protect this file or to back it up safely, since it can be regenerated by creating a new certificate request, and asking the server operator to sign it. You may wish to back it up, together with the PrivateKeyFile, to avoid this inconvenience if you lose all your data and need quick access to your backups. If you do back them up, you should keep them in a separate location to the KeysFile, since any person holding the KeysFile and the PrivateKeyFile can gain access to your encrypted data and decrypt it. PrivateKeyFile (required) The path to the OpenSSL private key in PEM format. This is generated at the same time as the certificate request, but there is no need to send it to the server operator, and you should not do so, in case the communication is intercepted by an attacker. Together with the CertificateFile, this provides access to the store server and the encrypted data stored there. See the notes under CertificateFile for information about backing up this file. TrustedCAsFile (required) The path to the OpenSSL certificate of the Client Certificate Authority (CCA), in PEM format. This is supplied by the server operator along with your account details, or along with your signed client certificate. This is used to verify that the server which you are connecting to is authorised by the person who signed your certificate. It protects you against DNS and ARP poisoning and IP spoofing attacks. DataDirectory (required) The path to a directory where bbackupd will keep local state information. This consists of timestamp files which identify the last backup start and end times, used by bbackupquery to determine whether files have changed, and optionally a database of inode numbers, which are used to check for files being renamed. The database is only saved if Box Backup is built with Berkeley Database (BDB) support. NotifyScript (optional) The path to the script or command to run when the Box Backup client detects an error during the backup process. This is normally used to notify the client system administrator by e-mail when a backup fails for any reason. The script or command is called with one of the following additional arguments to identify the cause of the problem: store-full The backup store is full. No new files are being uploaded. If some files are marked as deleted, they should be removed in due course by the server's housekeeping process. Otherwise, you need to remove some files from your backup set, or ask the store operator for more space. read-error One or more files which were supposed to be backed up could not be read. This could be due to: running the server as a non-root user; backing up a mounted filesystem such as NFS; access control lists being applied to some files; SELinux being enabled; trying to back up open files under Windows; strange directory permissions such as 0000 or 0400. Check the client logs, e.g. /var/log/bbackupd on Unix, or the Windows Event Viewer in Control Panel > Administrative Tools, for more information about which files are not being backed up and why. backup-error There was a communications error with the server, or an unexpected exception was encountered during a backup run. Check the client logs, e.g. /var/log/box on Unix, or the Windows Event Viewer in Control Panel > Administrative Tools, for more information about the problem. You may wish to check your Internet access to the server, check that the server is running, and ask your server operator to check your account on the server. CommandSocket (optional) The path to the Unix socket which bbackupd creates when running, and which bbackupctl uses to communicate with it, for example to force a sync or a configuration reload. If this option is omitted, no socket will be created, and bbackupctl will not function. Unix sockets appear within the filesystem on Unix, as a special type of file, and must be created in a directory which exists and to which bbackupd has write access, and bbackupctl has read access. On Windows, the path is ignored, and a named pipe is created instead. This does not currently have any security attached, so it can be accessed by any user. Unlike a Unix socket it can also be accessed remotely. Please use this option with extreme caution on Windows, and only on fully trusted networks. AutomaticBackup (optional) Enable or disable the client from connecting automatically to the store every UpdateStoreInterval seconds. When enabled (set to Yes), the client is in Lazy Mode. When disabled (set to No), it is in Snapshot Mode. This setting is optional, and the default value is Yes. UpdateStoreInterval (required) The approximate time between successive connections to the server, in seconds, when the client is in Lazy Mode. The actual time is randomised slightly to prevent "rush hour" traffic jams on the server, where many clients try to connect at the same time. This value is ignored if the client is in Snapshot Mode. However, it is still required. It can be set to zero in this case. You will probably need to experiment with the value of this option. A good value to start with is probably 86400 seconds, which is one day. MinimumFileAge (required) The number of seconds since a file was last modified before it will be backed up. The reason for this is to avoid repeatedly backing up files which are repeatedly changing. A good value is about 3600 seconds (one hour). If set to zero, files which have changed will always be backed up on the next backup run. The MaxUploadWait option overrides this option in some circumstances. MaxUploadWait (required) The number of seconds since a file was last uploaded before it will be uploaded again, even if it keeps changing. The reason for this is to ensure that files which are continuously modified are eventually uploaded anyway. This should be no less than the value of MinimumFileAge. A good value is about 14400 seconds (4 hours). MaxFileTimeInFuture (optional) The maximum time that a file's timestamp can be in the future, before it will be backed up anyway. Due to clock synchronisation problems, it is inevitable that you will occasionally see files timestamped in the future. Normally, for files which are dated only slightly in the future, you will want to wait until after the file's date before backing it up. However, for files whose dates are very wrong (more than a few hours) you will normally prefer to back them up immediately. A good value is about 7200 seconds (2 hours) to cope with potential problems when moving in and out of daylight saving time, if applicable in your timezone. The default value, if this setting is not provided, is 172800 seconds (2 days). FileTrackingSizeThreshold (required) The minimum size of files which will be tracked by inode number to detect renames. It is not worth detecting renames of small files, since they are quick to upload again in full, and keeping their inode numbers in memory increases the client's memory usage and slows down searches. Larger files should be tracked to avoid wasting space on the store and long uploads. A good value is about 65536 bytes (64 kilobytes). DiffingUploadSizeThreshold (required) The minimum size of files which will be compared to the old file on the server, and for which only changes will be uploaded. It is not worth comparing small files, since they are quick to upload again in full, and sending the entire file reduces the risk of data loss if the store is accidentally corrupted. Larger files should have only their differences uploaded to avoid wasting space on the store and long uploads. A good value is about 65536 bytes (64 kilobytes). MaximumDiffingTime (optional) The maximum time for which the client will attempt to find differences between the current version and the old version in the store, before giving up and uploading the entire file again. Very large files (several gigabytes) may take a very long time to scan for changes, but would also take a very long time to upload again and use a lot of space on the store, so it is normally worth omitting this value. Use this option only if, for some bizarre reason, you prefer to upload really large files in full rather than spend a long time scanning them for changes. KeepAliveTime (optional) The interval (in seconds) between sending Keep-Alive messages to the server while performing long operations such as finding differences in large files, or scanning large directories. These messages ensure that the SSL connection is not closed by the server, or an intervening firewall, due to lack of activity. The server will normally wait up to 15 minutes (900 seconds) before disconnecting the client, so the value should be given and should be less than 900. Some firewalls may time out inactive connections after 10 or 5 minutes. A good value is 300 seconds (5 minutes). You may need to reduce this if you frequently see TLSReadFailed or TLSWriteFailed errors on the client. StoreObjectInfoFile (optional) Enables the use of a state file, which stores the client's internal state when the client is not running. This is useful on clients machines which are frequently shut down, for example desktop and laptop computers, because it removes the need for the client to recontact the store and rescan all directories on the first backup run, which may take some time. This feature is somewhat experimental and not well tested. This is option is disabled by default, in which case the state is stored in memory only. The value is the path to the state file. ExtendedLogging (optional) Enables the connection debugging mode of the client, which writes all commands sent to or received from the server to the system logs. This generates a lot of output, so it should only be used when instructed, or when you suspect a connection problem or client-server protocol error (and you know how to interpret the output). This is a boolean value, which may be set to Yes or No. The default is of course No. ExtendedLogFile (optional, new in 0.11) Enables the same debugging output as ExtendedLogging, but written to a file instead of the system logs. This is useful if you need extended logging, but do not have access to the system logs, for example if you are not the administrator of the computer. The value is the path to the file where these logs will be written. If omitted, extended logs will not be written to a file. This is entirely independent of the ExtendedLogging option. It does not make much sense to use both at the same time. LogAllFileAccess (optional, new in 0.11) Enables logging of all local file and directory access, file uploads (full and differential), and excluded files. This may be useful if the client is failing to upload a particular file, or crashing while trying to upload it. The logs will be sent to the system log or Windows Event Viewer. This generates a lot of output, so it should only be used when instructed, or when you suspect that bbackupd is skipping some files and want to know why. Because it is verbose, the messages are hidden by default even if the option is enabled. To see them, you must run bbackupd with at least one -v option. This is a boolean value, which may be set to Yes or No. The default is of course No. SyncAllowScript (optional) The path to the script or command to run when the client is about to start an automatic backup run, and wishes to know whether it is safe to do so. This is useful for clients which do not always have access to the server, for example laptops and computers on dial-up Internet connections. The script should either output the word now if the backup should proceed, or else a number, in seconds, which indicates how long the client should wait before trying to connect again. Any other output will result in an error on the client, and the backup will not run. This value is optional, and by default no such script is used.
    Server Section These options appear within the Server subsection, which is at the root level. PidFile This option enables the client to write its processs identifier (PID) to the specified file after starting. The file will be deleted when the client daemon exits for any reason. This is disabled by default, but is recommended whenever you run the client daemon as a daemon (in the background), which is usually the case. This file can be used by scripts to determine whether the daemon is still running, and to send it messages to reload its configuration or to terminate. Example Server Section Server { PidFile = /var/state/boxbackup/bbackupd.pid }
    Backup Locations Section This section serves only as a container for all defined backup locations. Example Backup Locations Section BackupLocations { etc { Path = /etc } home { Path = /home ExcludeDir = /home/shared ExcludeDir = /home/chris/.ccache ExcludeDir = /home/chris/.mozilla/firefox/vvvkq3vp.default/Cache } } Each subsection is a backup location. The name of the subsection is the name that will be used on the server. The root directory of the account on the server contains one subdirectory per location. The name should be simple, not containing any spaces or special characters. If you do not define any locations, the client will not back up any files! It is currently not recommended to back up the root directory of the filesystem on Unix. Box Backup is designed to back up important data and configuration files, not full systems. Nevertheless, nothing prevents you from doing so if you desire. On Windows, it is currently not possible to back up files which are open (currently in use), such as open documents in Microsoft Office, and system files such as the registry and the paging file. You will get an error for each open file which the client attempts to back up. Once the file has been closed, it will be backed up normally. System files will always be open, and should be excluded from your backups.
    Administration This chapter deals with the dauily running and management of the Box Backup system. It explains most day-to-day tasks.
    Regular Maintenance The steps involved in maintaining and keeping the backup sets healthy are outlined in this section.
    Controlling a backup client The bbackupctl program sends control commands to the bbackupd daemon. It must be run as the same user as the daemon, and there is no exception for root. The command line syntax is: /usr/local/sbin/bbackupctl [-q] [-c config-file] command The -q option reduces the amount of output the program emits, and -c allows an alternative configuration file to be specified. Valid commands are: terminate Stop the bbackupd daemon now (equivalent to kill) reload Reload the configuration file (equivalent to kill -HUP) sync Connect to the server and synchronise files now bbackupctl communicates with the server via a UNIX domain socket, specified in bbackupd.conf with the CommandSocket directive. This does not need to be specified, and bbackupd will run without the command socket, but in this case bbackupctl will not be able to communicate with the daemon. Some platforms cannot check the user id of the connecting process, so this command socket becomes a denial of service security risk. bbackupd will warn you when it starts up if this is the case on your platform, and you should consider removing the CommandSocket directive on these platforms.
    Using bbackupctl to perform snapshots bbackupctl's main purpose is to implement snapshot based backups, emulating the behaviour of traditional backup software. Use bbackupd-config to write a configuration file in snapshot mode, and then run the following command as a cron job. /usr/local/sbin/bbackupctl -q sync This will cause the backup daemon to upload all changed files immediately. bbackupctl will exit almost immediately, and will not output anything unless there is an error.
    Checking storage space used on the server
    From the client machine bbackupquery can tell you how much space is used on the server for this account. Either use the usage command in interactive mode, or type: /usr/local/sbin/bbackupquery -q usage quit to show the space used as a single command.
    On the server bbstoreaccounts allows you to query the space used, and change the limits. To display the space used on the server for an account, use: /usr/local/sbin/bbstoreaccounts info 75AB23C To adjust the soft and hard limits on an account, use: /usr/local/sbin/bbstoreaccounts setlimit 75AB23C new-soft-limit new-hard-limit You do not need to restart the server.
    Verify and restore files Backups are no use unless you can restore them. The bbackupquery utility does this and more. You don't provide any login information to it, as it just picks up the data it needs from /etc/box/bbackupd.conf. You should run it as root so it can find everything it needs. Full documentation can be found in the bbackupquery manual page. It follows the model of a command line sftp client quite closely. TODO: Link to bbackupquery man-page here. On systems where GNU readline is available (by default) it uses that for command line history and editing. Otherwise it falls back to very basic UNIX text entry. TODO: Did the readline dependency change to editline?
    Using bbackupquery bbackupquery is the tool you use to verify, restore and investigate your backup files with. When invoked, it simply logs into the server using the certificates you have listed in bbackupd.conf. After you run bbackupquery, you will see a prompt, allowing you to execute commands. The list (or ls) command lets you view files in the store. It works much like unix ls, but with different options. An example: [pthomsen@host bbackupquery]$ bbackupquery Box Backup Query Tool v0.10, (c) Ben Summers and contributors 2003-2006 Using configuration file /etc/box/bbackupd.conf Connecting to store... Handshake with store... Login to store... Login complete. Type "help" for a list of commands. query > ls 00000002 -d---- mp3 00000003 -d---- video 00000004 -d---- home-pthomsen 00000005 -d---- root query > The ls commands shows the directories that are backed up. Now we'll take a closer look at the home-pthomsen directory: query > cd home-pthomsen query > ls 00002809 f----- sample.tiff 0000280a f----- s3.tiff 0000280b f----- s4.tiff 0000280d f----- s2.tiff 0000280e f----- foo.pdf 0000286c f----- core.28720 0000339a -d---- .emacs.d 0000339d -d---- bbackup-contrib 00003437 f----- calnut.compare.txt 0000345d f----- DSCN1783.jpg 0000345e f----- DSCN1782.jpg query > The ls command takes the following options; -r -- recursively list all files -d -- list deleted files/directories -o -- list old versions of files/directories -I -- don't display object ID -F -- don't display flags -t -- show file modification time (and attr mod time if has the object has attributes, ~ separated) -s -- show file size in blocks used on server (only very approximate indication of size locally) The flags displayed from the ls command are as follows: f = file d = directory X = deleted o = old version R = remove from server as soon as marked deleted or old a = has attributes stored in directory record which override attributes in backup file
    Verify backups As with any backup system, you should frequently check that your backups are working properly by comparing them. Box Backup makes this very easy and completely automatic. All you have to do is schedule the bbackupquery compare command to run regularly, and check its output. You can run the command manually as follows: /usr/local/sbin/bbackupquery "compare -a" quit This command will report all the differences found between the store and the files on disc. It will download everything, so may take a while. You should expect to see some differences on a typical compare, because files which have recently changed are unlikely to have been uploaded yet. It will also tell you how many files have been modified since the last backup run, since these will normally have changed, and such failures are expected. You are strongly recommended to add this command as a cron job, at least once a month, and to check the output for anything suspicious, particularly a large number of compare failures, failures on files that have not been modified, or any error (anything except a compare mismatch) that occurs during the compare operation. Consider keeping a record of these messages and comparing them with a future verification. If you would like to do a "quick" check which just downloads file checksums and compares against that, then run: /usr/local/sbin/bbackupquery "compare -aq" quit However, this does not check that the file attributes are correct, and since the checksums are generated on the client they may not reflect the data on the server if there is a problem -- the server cannot check the encrypted contents. View this as a quick indication, rather than a definite check that your backup verifies correctly.
    Restore backups You will need the keys file created when you configured the server. Without it, you cannot restore the files; this is the downside of encrypted backups. However, by keeping the small keys file safe, you indirectly keep your entire backup safe. The first step is to recreate the configuration of the backup client. It's probably best to have stored the /etc/box directory with your keys. But if you're recreating it, all you really need is to have got the login infomation correct (ie the certs and keys). Don't run bbackupd yet! It will mark all your files as deleted if you do, which is not hugely bad in terms of losing data, just a major inconvenience. (This assumes that you are working from a blank slate. If you want to restore some files to a different location, it's fine to restore while bbackupd is running, just do it outside a backed up directory to make sure it doesn't start uploading the restored files.) Type: /usr/local/sbin/bbackupquery to run it in interactive mode. Type: list to see a list of the locations stored on the server. For each location you want to restore, type: restore name-on-server local-dir-name The directory specified by local-dir-name must not exist yet. If the restore is interrupted for any reason, repeat the above steps, but add the -r flag to the restore command to tell it to resume.
    Retrieving deleted and old files Box Backup makes old versions of files and files you have deleted available, subject to there being enough disc space on the server to hold them. This is how to retrieve them using bbackupquery. Future versions will make this far more user-friendly. Firstly, run bbackupquery in interactive mode. It behaves in a similar manner to a command line sftp client. /usr/local/sbin/bbackupquery Then navigate to the directory containing the file you want, using list, cd and pwd. query > cd home/profiles/USERNAME List the directory, using the "o" option to list the files available without filtering out everything apart from the current version. (if you want to see deleted files as well, use list -odt) query > list -ot 00000078 f--o- 2004-01-21T20:17:48 NTUSER.DAT 00000079 f--o- 2004-01-21T20:17:48 ntuser.dat.LOG 0000007a f--o- 2004-01-21T17:55:12 ntuser.ini 0000007b f---- 2004-01-12T15:32:00 ntuser.pol 0000007c -d--- 1970-01-01T00:00:00 Templates 00000089 -d--- 1970-01-01T00:00:00 Start Menu 000000a0 -d--- 1970-01-01T00:00:00 SendTo 000000a6 -d--- 1970-01-01T00:00:00 Recent 00000151 -d--- 1970-01-01T00:00:00 PrintHood 00000152 -d--- 1970-01-01T00:00:00 NetHood 00000156 -d--- 1970-01-01T00:00:00 My Documents 0000018d -d--- 1970-01-01T00:00:00 Favorites 00000215 -d--- 1970-01-01T00:00:00 Desktop 00000219 -d--- 1970-01-01T00:00:00 Cookies 0000048b -d--- 1970-01-01T00:00:00 Application Data 000005da -d--- 1970-01-01T00:00:00 UserData 0000437e f--o- 2004-01-24T02:45:43 NTUSER.DAT 0000437f f--o- 2004-01-24T02:45:43 ntuser.dat.LOG 00004380 f--o- 2004-01-23T17:01:29 ntuser.ini 00004446 f--o- 2004-01-24T02:45:43 NTUSER.DAT 00004447 f--o- 2004-01-24T02:45:43 ntuser.dat.LOG 000045f4 f---- 2004-01-26T15:54:16 NTUSER.DAT 000045f5 f---- 2004-01-26T15:54:16 ntuser.dat.LOG 000045f6 f---- 2004-01-26T16:54:31 ntuser.ini (this is a listing from a server which is used as a Samba server for a network of Windows clients.) You now need to fetch the file using it's ID, rather than it's name. The ID is the hex number in the first column. Fetch it like this: query > get -i 0000437e NTUSER.DAT Object ID 0000437e fetched successfully. The object is now available on your local machine. You can use lcd to move around, and sh ls to list directories on your local machine.
    Fixing corruptions of store data This section gives help on what to do if your server has suffered corruption, for example, after an unclean shutdown or other operating system or hardware problem. In general, as updates to the store are made in an atomic manner, the most likely result is wasted disc space. However, if really bad things happen, or you believe that there is a lot of wasted space, then these instructions will help to restore your data. You know you will need to do something if you get strange errors, and bbackupd attempts to contact the server every 100 seconds or so. Or if one of the discs in your RAID disc set has failed. After following these instructions, the end result will be that bbackupquery will be able to see all the files which were stored on your server, and retrieve them. Some of them may be in lost+found directories in the root of the store (or in their original position if they have been moved) but they will all be able to be retrieved. After you have retrieved the files you want, bbackupd will upload new versions where necessary, and after about two days, mark any lost+found directories as deleted. Finally, those directories will be removed by the housekeeping process on the server. These instructions assume you're working on account 1234. Replace this with the account number that you actually want to check (the one that is experiencing errors). These steps will need to be repeated for all affected accounts.
    Stop bbackupd First, make sure that bbackupd is not running on the client machine for the account you are going to recover. Use bbackupctl terminate to stop it. This step is not strictly necessary, but is recommended. During any checks on the account, bbackupd will be unable to log in, and after they are complete, the account is marked as changed on the server so bbackupd will perform a complete scan.
    Are you using RAID on the server? The raidfile recovery tools have not been written, and probably will not be, since Box Backup RAID is deprecated. However, when two out of three files are available, the server will successfully allow access to your data, even if it complains a lot in the logs. The best thing to do is to fix the accounts, if necessary, and retrieve any files you need. Then move the old store directories aside (in case you need them) and start afresh with new accounts, and let the clients upload all their data again.
    Check and fix the account First, run the check utility, and see what errors it reports. /usr/local/sbin/bbstoreaccounts check 1234 This will take some time, and use a fair bit of memory (about 16 bytes per file and directory). If the output looks plausible and reports errors which need fixing, run it again but with the fix flag: /usr/local/sbin/bbstoreaccounts check 1234 fix This will fix any errors, and remove unrecoverable files. Directories will be recreated if necessary. NOTE: The utility may adjust the soft and hard limits on the account to make sure that housekeeping will not remove anything -- check these afterwards.
    Grab any files you need with bbackupquery At this point, you will have a working store. Every file which was on the server, and wasn't corrupt, will be available. On the client, use bbackupquery to log in and examine the store. (type help at the prompt for instructions). Retrieve any files you need, paying attention to any lost+found directories in the root directory of the store. You can skip this step if you are sure that the client machine is fine -- in this case, bbackupd will bring the store up to date.
    Restart bbackupd Restart bbackupd on the client machine. The store account will be brought up to date, and files in the wrong place will be marked for eventual deletion.
    Troubleshooting If you are trying to fix a store after your disc has been corrupted, see Fixing corruptions of store data. Unfortunately, the error messages are not particularly helpful at the moment. This page lists some of the common errors, and the most likely causes of them. When an error occurs, you will see a message like 'Exception: RaidFile/OSFileError (2/8)' either on the screen or in your log files. (it is recommended you set up another log file as recommended in the server setup instructions.) This error may not be particularly helpful, although some do have extra information about probable causes. To get further information, check the ExceptionCodes.txt file in the root of the distribution. This file is generated by the ./configure script, so you will need to have run that first. Some common causes of exceptions are listed below. Please email me with any other codes you get, and I will let you know what they mean, and add notes here.
    RaidFile (2/8) This is found either when running bbstoreaccounts or in the bbstored logs. Problem: The directories you specified in the raidfile.conf are not writable by the _bbstored user. Resolution: Change permissions appropriately.
    Common (1/2) This usually occurs when the configuration files can't be opened. Problem: You created your configurations in non-standard locations, and the programs cannot find them. Resolution: Explicitly specify configuration file locations to daemons and programs. For example /usr/local/sbin/bbstored /some/other/dir/bbstored.config /usr/local/sbin/bbackupquery -c /some/other/dir/bbackupd.config (daemons specify the name as the first argument, utility programs with the -c option). Problem: bbstored can't find the raidfile.conf file specified in bbstored.conf. Resolution: Edit bbstored.conf to point to the correct location of this additional configuration file.
    Server (3/16) The server can't listen for connections on the IP address specified when you configured it. Problem: This probably means you've specified the wrong hostname to bbstored-config -- maybe your server is behind a NAT firewall? Resolution: Edit bbstored.conf and correct the ListenAddresses line. You should replace the server address with the IP address of your machine.
    Connection (7/x) These errors all relate to connections failing -- you may see them during operation if there are network failures or other problems between the client and server. The backup system will recover from them automatically.
    Connection (7/30) - SSL problems Log snippet from client side: bbackupd[1904]: Opening connection to server xxxx.xxx... bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01 bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:rsa routines:RSA_EAY_PUBLIC_DECRYPT:padding check failed bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:asn1 encoding routines:ASN1_verify:EVP lib bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed bbackupd[1904]: TRACE: Exception thrown: ConnectionException(Conn_TLSHandshakeFailed) at SocketStreamTLS.cpp(237) bbackupd[1904]: Exception caught (7/30), reset state and waiting to retry... And from the server: bbstored[19291]: Incoming connection from xx.xxx.xx.xxx port xxxxx (handling in child xxxxx) bbstored[21588]: SSL err during Accept: error:xxxxxxxx:SSL routines:SSL3_READ_BYTES:tlsv1 alert decrypt error bbstored[21588]: in server child, exception Connection TLSHandshakeFailed (7/30) -- terminating child Solution: Create a new CA on the server side and re-generate the client certificate. Re-creating the client certificate request is not necessary.
    Advanced troubleshooting If this really doesn't help, then using the DEBUG builds of the system will give you much more information -- a more descriptive exception message and the file and line number where the error occurred. For example, if you are having problems with bbstoreaccounts, build the debug version with: cd boxbackup-0.0 cd bin/bbstoreaccounts make Within the module directories, make defaults to building the debug version. At the top level, it defaults to release. This will build an executable in debug/bin/bbstoreaccounts which you can then use instead of the release version. It will give far more useful error messages. When you get an error message, use the file and line number to locate where the error occurs in the code. There will be comments around that line to explain why the exception happened. If you are using a debug version of a daemon, these extended messages are found in the log files.
    &__ExceptionCodes__elfjz3fu; Running without root It is possible to run both the server and client without root privileges.
    Server The server, by default, runs as a non-root user. However, it expects to be run as root and changes user to a specified user as soon as it can, simply for administrative convenience. The server uses a port greater than 1024, so it doesn't need root to start. To run it entirely as a non-root user, edit the bbstored.conf file, and remove the User directive from the Server section. Then simply run the server as your desired user.
    Client The client requires root for normal operation, since it must be able to access all files to back them up. However, it is possible to run the client as a non-root user, with certain limitations. Follow the installation instructions, but install the executable files manually to somewhere in your home directory. Then use bbackupd-config to configure the daemon, but use a directory other than /etc/box, probably somewhere in your home directory. All directories you specify to be backed up must be readable, and all files must be owned by the user and readable to that user. Important: If any file or directory is not readable by this user, the backup process will skip that file or directory. Keep an eye on the logs for reports of this failure. Non-root operation of the backup client is recommended only for testing, and should not be relied on in a production environment.
    boxbackup/docs/docbook/bbackupd-config.xml0000664000175000017500000000753611163413441021433 0ustar siretartsiretart bbackupd-config 8 Box Backup Box Backup 0.11 bbackupd-config Box Backup client daemon configuration file generator bbackupd-config config-dir backup-mode account-num server-hostname working-dir backup-dir backup-dir ... Description The bbackupd-config script creates configuration files and client certificates. It takes at least six parameters: config-dir Configuration directory. Usually /etc/box. backup-mode Either lazy or snapshot. account-num The client account number. This is set by the bbstored administrator. server-hostname The hostname or IP address of the bbstored server. working-dir A directory to keep temporary state files. This is usually something like /var/bbackupd. This can be changed in bbackupd.conf later on if required. backup-dir A space-separated list of directories to be backed up. Note that this does not traverse mount points. Files /etc/box/bbackupd.conf /etc/box/bbackupd/NotifySysAdmin.sh See Also bbackupd.conf 5 , bbackupd 8 , bbackupctl 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bbackupd.conf.xml0000664000175000017500000003252611163413441021111 0ustar siretartsiretart bbackupd.conf 5 Box Backup Box Backup 0.11 bbackupd.conf Box Backup client daemon configuration file /etc/box/bbackupd.conf Description AccountNumber The account number of this client. This is set by the admin of the store server. UpdateStoreInterval Specifies the interval between scanning of the local discs. To avoid cycles of load on the server, this time is randomly adjusted by a small percentage as the daemon runs. Defaults to 1 hour. MinimumFileAge Specifies how long since a file was last modified before it will be uploaded. Defaults to 6 hours. MaxUploadWait If a file is repeatedly modified it won't be uploaded immediately in case it's modified again. However it should be uploaded eventually. This is how long we should wait after first noticing a change. Defaults to 1 day. MaxFileTimeInFuture AutomaticBackup SyncAllowScript Use this to temporarily stop bbackupd from syncronising or connecting to the store. This specifies a program or script script which is run just before each sync, and ideally the full path to the interpreter. It will be run as the same user bbackupd is running as, usually root. The script prints either "now" or a number to STDOUT (and a terminating newline, no quotes). If the result was "now", then the sync will happen. If it's a number, then the script will be asked again in that number of seconds. For example, you could use this on a laptop to only backup when on a specific network. MaximumDiffingTime How much time should be spent on diffing files. DeleteRedundantLocationsAfter FileTrackingSizeThreshold DiffingUploadSizeThreshold StoreHostname The hostname or IP address of the bbstored 8 server. StorePort The port used by the server. Defaults to 2201. ExtendedLogging Logs everything that happens between the client and server. The bbackupd 8 client must also be started with . ExtendedLogFile LogAllFileAccess LogFile LogFileLevel CommandSocket Where the command socket is created in the filesystem. KeepAliveTime StoreObjectInfoFile NotifyScript The location of the script which runs at certain events. This script is generated by bbackupd-config 8 . Defaults to /etc/box/bbackupd/NotifySysAdmin.sh. NotifyAlways CertificateFile The path to the client's public certificate. PrivateKeyFile The path to the client's private key. This should only be readable by root. TrustedCAsFile The Certificate Authority created by bbstored-certs 8 . KeysFile The data encryption key. This must be kept safe at all costs, your data is useless without it! DataDirectory A directory to keep temporary state files. This is usually something like /var/bbackupd. Server This section relates to the running daemon. PidFile The location of the process ID file. Defaults to /var/run/bbackupd.pid. BackupLocations This section defines each directory to be backed up. Each entry must have at least a Path entry and, optionally, include and exclude directives. Multiple include and exclude directives may appear. Path The path to back up. ExcludeFile Exclude a single file. ExcludeFilesRegex Exclude multiple files based on a regular expression. See re_format 7 . ExcludeDir Exclude a single directory. ExcludeDirsRegex Exclude multiple directories based on a regular expression. See re_format 7 . AlwaysIncludeFile Include a single file from a directory which has been excluded. AlwaysIncludeFilesRegex Include multiple files from an excluded directory, based on a regular expression. AlwaysIncludeDir Include a single directory from a directory which has been excluded. AlwaysIncludeDirsRegex Include multiple directories from an excluded directory, based on a regular expression. Examples The following is an example of a backup location: home { Path = /home ExcludeDir = /home/guest ExcludeDir = /home/[^/]+/tmp ExcludeFilesRegex = .*\.(mp3|MP3)$ AlwaysIncludeFile = /home/someuser/importantspeech.mp3 } Files /etc/box/bbackupd.conf See Also bbackupd 8 , bbackupd-config 8 , bbackupctl 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/raidfile-config.xml0000664000175000017500000001403111163413441021423 0ustar siretartsiretart raidfile-config 8 Box Backup Box Backup 0.11 raidfile-config Configure Box Backup's RAID files raidfile-config config-dir blocksize dir1 dir2 dir3 Description raidfile-config creates a raidfile.conf file for Box Backup. This file holds information about the directories used to store backups in. Box Backup supports userland RAID, in a restricted RAID5 configuration, where 3 and only 3 'drives' are supported. You can read more about RAID5 (and other RAID-levels) here. Parameters The parameters are as follows: config-dir The directory path where configuration files are located. Usually this is /etc/box. raidfile.conf will be written in this directory. blocksize The block size used for file storage in the system, in bytes. Using a multiple of the file system block size is a good strategy. Depending on the size of the files you will be backing up, this multiple varies. Of course it also depends on the native block size of your file system. dir1 The first directory in the built-in RAID array. dir2 The second directory in the built-in RAID array. If you are not using the built-in RAID functionality, this field should be ignored. You should not use the built-in RAID if you have a hardware RAID solution or if you're using another type of software RAID (like md on Linux). dir3 The third directory in the built-in RAID array. The same notes that apply to dir2 also apply to dir3. Note that there are currently no way to add multiple disk sets to the raidfile.conf file using command line tools, etc. See raidfile.conf 5 for details on adding more disks. Bugs If you find a bug in Box Backup, and you want to let us know about it, join the mailing list, and send a description of the problem there. To report a bug, give us at least the following information: The version of Box Backup you are running The platform you are running on (hardware and OS), for both client and server. If possible attach your config files (bbstored.conf, bbackupd.conf) to the bug report. Also attach any log file output that helps shed light on the problem you are seeing. And last but certainly not least, a description of what you are seeing, in as much detail as possible. Files raidfile-config generates the raidfile.conf 5 file. See Also bbstored-config 8 , bbstored.conf 5 , raidfile.conf 5 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bb-man.xsl0000664000175000017500000000036211175136613017553 0ustar siretartsiretart boxbackup/docs/docbook/bbstored.conf.xml0000664000175000017500000001402611163413441021135 0ustar siretartsiretart bbstored.conf 5 Box Backup Box Backup 0.11 bbstored.conf Box Backup store daemon configuration file /etc/box/bbstored.conf Description The following configuration options are valid: RaidFileConf Specifies the path to the raidfile.conf 5 . This is normally /etc/box/raidfile.conf. AccountDatabase Specifies the path to the account database created by bbstoreaccounts 8 . This is usually /etc/box/bbstored/accounts.txt. ExtendedLogging Specifies whether extended logging should be enabled to show what commands are being received from clients. TimeBetweenHousekeeping How long between scanning for files which need deleting. Server These options relate to the actual daemon. PidFile The location of the pidfile, where the daemon's process ID is kept. User The user to run as. ListenAddresses The interface addresses to listen on. Hostnames may be used instead of IP addresses. The format is: or . CertificateFile The path to the server's public certificate. PrivateKeyFile The path to the server's private key. This should only be readable by root and/or the . TrustedCAsFile The Certificate Authority created by bbstored-certs 8 . Examples The following is an example bbstored.conf: RaidFileConf = /etc/box/raidfile.conf AccountDatabase = /etc/box/bbstored/accounts.txt TimeBetweenHousekeeping = 900 Server { PidFile = /var/run/bbstored.pid User = _bbstored ListenAddresses = inet:server.example.com CertificateFile = /etc/box/bbstored/server.example.com-cert.pem PrivateKeyFile = /etc/box/bbstored/server.example.com-key.pem TrustedCAsFile = /etc/box/bbstored/clientCA.pem } Files /etc/box/bbstored.conf See Also bbstored 8 , bbstored-config 8 , raidfile-config 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bbstored-certs.xml0000664000175000017500000001220311163413441021322 0ustar siretartsiretart bbstored-certs 8 Box Backup Box Backup 0.11 bbstored-certs Manage certificates for the Box Backup system bbstored-certs certs-dir command arguments Description bbstored-certs creates and signs certificates for use in Box Backup. It allows the user to create and sign the server keys, as well as signing client keys. All commands must be followed by the certs-dir, which is the directory in which the certificates are stored. Commands There are 3 commands: init Create the certs-dir, and generate the server keys for bbstored. certs-dir cannot exist before running the command. sign-server servercsrfile Sign the server certificate. The servercsrfile is the file generated by the init command. sign clientcsrfile Sign a client certificate. The clientcsrfile is generated during client setup. See bbackupd-config 8 . Send the signed certificate back to the client, and install according to the instructions given by bbackupd-config. Files raidfile-config 8 generates the raidfile.conf 5 file. Bugs If you find a bug in Box Backup, and you want to let us know about it, join the mailing list, and send a description of the problem there. To report a bug, give us at least the following information: The version of Box Backup you are running The platform you are running on (hardware and OS), for both client and server. If possible attach your config files (bbstored.conf, bbackupd.conf) to the bug report. Also attach any log file output that helps shed light on the problem you are seeing. And last but certainly not least, a description of what you are seeing, in as much detail as possible. See Also bbstored-config 8 , bbstored.conf 5 , bbstoreaccounts 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bbstored-config.xml0000664000175000017500000001007611163413441021455 0ustar siretartsiretart bbstored-config 8 Box Backup Box Backup 0.11 bbstored-config Box Backup store daemon configuration file generator bbstored-config configdir servername username Description The bbstored-config script creates configuration files and server certificates for a bbstored instance. It takes three parameters: configdir The directory where config files will reside. A bbstored subdirectory will be created where several config files will reside. The bbstored.conf file will be created in configdir. servername The name of the server that is being configured. Usually the fully qualified domain name of the machine in question. username The name of the user that should be running the bbstored process. Recommended name: _bbstored. A valid raidfile.conf 5 must be found in configdir. Several steps are taken during the run of bbstored-config: Server certificates are created. This requires interaction from the operator. The RAID volumes are checked to ensure that the configuration is consistent and will work. Instructions for next steps to take are shown. These steps may be different for different OS platforms, so pay close attention to these instructions. Files /etc/box/bbstored.conf See Also bbstored.conf 5 , bbstored 8 , bbstored-certs 8 , raidfile-config 8 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/raidfile.conf.xml0000664000175000017500000001045311163413441021110 0ustar siretartsiretart raidfile.conf 5 Box Backup Box Backup 0.11 raidfile.conf Userland RAID for Box Backup /etc/box/raidfile.conf Description The raidfile.conf is usually generated by raidfile-config 8 but may be manually edited if the store locations move or if more than one disc set is required. discX Specifies a set of discs. SetNumber The set number of the RAID disc, referenced by each account. BlockSize The block size of the file system (usually 2048). Under BSD with FFS, set this to your file system's fragment size (most likely an 8th of the block size). Dir0 The first directory in the RAID array. Dir1 The second directory in the RAID array. If you do not wish to use the built-in RAID functionality, this field should be set to the same as Dir0. You should not use the built-in RAID if you have a hardware RAID solution or if you're using another type of software RAID (like md on Linux). Dir2 The third directory in the RAID array. The same notes that apply to Dir2 also apply to Dir3. Files /etc/box/raidfile.conf See Also raidfile-config 8 , bbstored.conf 5 Authors Ben Summers Per Thomsen James O'Gorman boxbackup/docs/docbook/bb-nochunk-book.xsl0000664000175000017500000000122711205347567021404 0ustar siretartsiretart boxbackup/docs/Makefile0000664000175000017500000001236511244051621015705 0ustar siretartsiretart# Process DocBook to HTML # This makefile is a bit obfuscated so that it works correctly on both # BSD and GNU make. Some parts apply to one version of make and not the # other; these are marked by comments. # The "all" target shouldn't be up here, but the trickery below defines # what looks like a rule to GNU make, and so we need to define the actual # default target before it. all: docs DBPROC_COMMAND = xsltproc MKDIR_COMMAND = mkdir CP_COMMAND = cp PERL_COMMAND = perl RM_COMMAND = rm -f TAR_COMMAND = tar GZIP_COMMAND = gzip -f GENERATE_SCRIPT = tools/generate_except_xml.pl DBPROC = $(DBPROC_COMMAND) MKDIR = $(MKDIR_COMMAND) CP = $(CP_COMMAND) GENERATE = $(PERL_COMMAND) $(GENERATE_SCRIPT) RM_QUIET = $(RM_COMMAND) TAR = $(TAR_COMMAND) GZIP = $(GZIP_COMMAND) PROGRESS = @ true # use a GNU make "define" command, that looks like a harmless dummy rule # to BSD make, to hide parts of the Makefile from GNU make. define IGNORED_BY_GNU_MAKE: .if 0 endef # seen by GNU make, not by BSD make ifeq ($(V),) DBPROC = @ echo " [XLSTPROC]" $^ && $(DBPROC_COMMAND) 2>/dev/null GENERATE = @ echo " [GENERATE]" $@ && $(PERL_COMMAND) $(GENERATE_SCRIPT) TAR = @ echo " [TAR] " $@ && $(TAR_COMMAND) GZIP = @ echo " [GZIP] " $< && $(GZIP_COMMAND) RM_QUIET = @ $(RM_COMMAND) PROGRESS = @ echo endif define IGNORED_BY_GNU_MAKE: .endif .ifndef V # seen by BSD make, not by GNU make DBPROC = @ echo " [XSLTPROC]" $(.ALLSRC) && $(DBPROC_COMMAND) 2>/dev/null GENERATE = @ echo " [GENERATE]" $(.TARGET) && $(PERL_COMMAND) $(GENERATE_SCRIPT) TAR = @ echo " [TAR] " $(.TARGET) && $(TAR_COMMAND) GZIP = @ echo " [GZIP] " $(.TARGET:.gz=) && $(GZIP_COMMAND) RM_QUIET = @ $(RM_COMMAND) PROGRESS = @ echo .endif # neither .endif nor endef can be followed by a colon; each creates # warnings or errors in one or other version of make. we need some # magic to make them both work. Luckily, .endfor ignores the colon. .for DUMMY in $(NO_SUCH_VARIABLE) endef .endfor : PROGRESS_RM = $(PROGRESS) " [RM] " DOCBOOK_DIR = docbook HTML_DIR = htmlguide MAN_DIR = man BOOKXSL = $(DOCBOOK_DIR)/bb-book.xsl NOCHUNKBOOKXSL = $(DOCBOOK_DIR)/bb-nochunk-book.xsl MANXSL = $(DOCBOOK_DIR)/bb-man.xsl VPATH = $(DOCBOOK_DIR) .SUFFIXES: .html .xml .gz .1 .5 .8 docs: instguide adminguide manpages @mkdir -p $(HTML_DIR)/images @cp $(DOCBOOK_DIR)/html/images/*.png $(HTML_DIR)/images/. @cp $(DOCBOOK_DIR)/html/*.css $(HTML_DIR)/. @cp $(DOCBOOK_DIR)/html/*.ico $(HTML_DIR)/. adminguide: $(DOCBOOK_DIR)/ExceptionCodes.xml $(HTML_DIR)/adminguide/index.html # $^ gives all sources on GNU make, and nothing on BSD make # $> gives all sources on BSD make, and nothing on GNU make $(HTML_DIR)/adminguide/index.html: $(BOOKXSL) $(DOCBOOK_DIR)/adminguide.xml $(DBPROC) -o $(HTML_DIR)/adminguide/ $^ $> instguide: $(HTML_DIR)/instguide/index.html $(HTML_DIR)/instguide/index.html: $(BOOKXSL) $(DOCBOOK_DIR)/instguide.xml $(DBPROC) -o $(HTML_DIR)/instguide/ $^ $> # On BSD make, $> contains all sources and $^ is empty # On GNU make, $^ contains all sources and $> is empty $(DOCBOOK_DIR)/ExceptionCodes.xml: ../ExceptionCodes.txt $(GENERATE) $> $^ $@ manpages: man-dirs man-nroff man-html man-dirs: man/.there $(HTML_DIR)/man-html/.there $(HTML_DIR)/man-html/.there: mkdir -p $(HTML_DIR)/man-html touch $(HTML_DIR)/man-html/.there man/.there: mkdir -p man touch man/.there NROFF_PAGES = bbackupd.8 bbackupd-config.8 bbackupctl.8 bbackupquery.8 \ bbstored.8 bbstored-config.8 bbstoreaccounts.8 bbstored-certs.8 \ raidfile-config.8 \ bbackupd.conf.5 bbstored.conf.5 raidfile.conf.5 NROFF_FILES = $(NROFF_PAGES:%=$(MAN_DIR)/%.gz) man-nroff: $(NROFF_FILES) HTML_FILES_1 = $(NROFF_PAGES:%.5=%.html) HTML_FILES_2 = $(HTML_FILES_1:%.8=%.html) HTML_FILES = $(HTML_FILES_2:%=$(HTML_DIR)/man-html/%) man-html: $(HTML_FILES) # $^ gives all sources on GNU make, and nothing on BSD make # GNU make $(HTML_DIR)/man-html/%.html: $(NOCHUNKBOOKXSL) $(DOCBOOK_DIR)/%.xml $(DBPROC) -o $@ $^ # GNU make $(MAN_DIR)/%.8: $(MANXSL) $(DOCBOOK_DIR)/%.xml $(DBPROC) -o $@ $^ # GNU make $(MAN_DIR)/%.8.gz: $(MAN_DIR)/%.8 $(GZIP) $< # GNU make $(MAN_DIR)/%.5: $(MANXSL) $(DOCBOOK_DIR)/%.xml $(MANXSL) $(DBPROC) -o $@ $^ # GNU make $(MAN_DIR)/%.5.gz: $(MAN_DIR)/%.5 $(GZIP) $< # BSD make: the final colon (:) is required to make the .for and .endfor # lines valid in GNU make. It creates (different) dummy rules in GNU and # BSD make. Both dummy rules are harmless. .for MAN_PAGE in $(NROFF_PAGES) : $(MAN_DIR)/$(MAN_PAGE).gz: $(MANXSL) $(DOCBOOK_DIR)/$(MAN_PAGE:R).xml $(DBPROC) -o $(.TARGET:.gz=) $(.ALLSRC) $(GZIP) $(.TARGET:.gz=) $(HTML_DIR)/man-html/$(MAN_PAGE:R).html: $(NOCHUNKBOOKXSL) \ $(DOCBOOK_DIR)/$(MAN_PAGE:R).xml $(DBPROC) -o $(.TARGET) $(.ALLSRC) .endfor : dockit: clean docs documentation-kit-0.10.tar.gz documentation-kit-0.10.tar.gz: $(TAR) zcf documentation-kit-0.10.tar.gz $(HTML_DIR)/ clean: $(PROGRESS_RM) "$(HTML_DIR)/man-html/*.html" $(RM_QUIET) $(HTML_FILES) $(PROGRESS_RM) "$(MAN_DIR)/*.[58].gz" $(RM_QUIET) $(NROFF_FILES) $(PROGRESS_RM) "$(DOCBOOK_DIR)/ExceptionCodes.xml" $(RM_QUIET) $(DOCBOOK_DIR)/ExceptionCodes.xml $(PROGRESS_RM) "documentation-kit-0.10.tar.gz" $(RM_QUIET) documentation-kit-0.10.tar.gz boxbackup/cleanupforcvs.pl0000775000175000017500000000774011101121557016526 0ustar siretartsiretart#!/usr/bin/perl use strict; my @del_macos_files; my @bad_cpp; my @test_main; my @makefiles; my @autogen_cpp; my $cleaned = 1; my $dist_archives_exist = 0; my @bad_h; open EVERYTHING,'find . -type d \( -name docs \) -prune -o -type f |' or die "Can't open find for file listing"; my %exclude_from_memtest_checks = ('PollEmulator.cpp'=>1,'DebugMemLeakFinder.cpp'=>1,'MemLeakFinder.h'=>1,'MemLeakFindOn.h'=>1,'MemLeakFindOff.h'=>1,'Box.h'=>1); while() { chomp; next if -d; if(m~/autogen_\w+\.(h|cpp)~) { push @autogen_cpp,$_ } if(m~/\._[^/]+\Z~ || m~/\.DS_Store\Z~) { # mac OS files we don't want push @del_macos_files,$_ } elsif(m/\/(\w+\.cpp)/) { my $leafname = $1; # check that Box.h is first include open CPP,$_ or die "Can't open $_ for reading"; my $box_found = 0; my $last_was_memteston = 0; my $ok = 1; while(my $l = ) { if($l =~ m/#include\s+["<](.+?)[">]/) { my $inc_name = $1; if($inc_name eq 'Box.h') { $box_found = 1; } else { # Box.h must be first include file in every cpp file $ok = 0 unless $box_found; } # is it the mem test on thing? (ignoring the wire packing .h files) if($inc_name ne 'BeginStructPackForWire.h' && $inc_name ne 'EndStructPackForWire.h') { $last_was_memteston = ($inc_name eq 'MemLeakFindOn.h'); } } } if(!exists $exclude_from_memtest_checks{$leafname}) { $ok = 0 unless $last_was_memteston; } push @bad_cpp,$_ unless $ok; close CPP; } elsif(m/\/(\w+\.h)/) { my $leafname = $1; open H,$_ or die "Can't open $_ for reading"; my $ok = 1; my $memteston = 0; while(my $l = ) { if($l =~ m/#include\s+["<](.+?)[">]/) { if($1 eq 'MemLeakFindOn.h') { $memteston = 1; } elsif($1 eq 'MemLeakFindOff.h') { $memteston = 0; } else { # don't allow #include within mem test on $ok = 0 unless !$memteston; } } else { # strip comments my $lsc = $l; $lsc =~ s~//.+$~~; if($lsc =~ m/\b(new|delete|malloc|free|realloc)\b/) { # only allow this if memory checking is ON $ok = 0 unless $memteston; } } } # mem test must be off at the end of this .h file $ok = 0 if $memteston; if($_ !~ /testfiles/ && !exists $exclude_from_memtest_checks{$leafname}) { push @bad_h,$_ unless $ok; } close H; } elsif(m~/Makefile\Z~) { push @makefiles,$_ } if(m~/_(main\.cpp|t|t-gdb)\Z~) { push @test_main,$_ } if(m~\./boxbackup~) { $dist_archives_exist = 1; } } close EVERYTHING; ask_about_delete(\@del_macos_files, "supurious MacOS X files"); ask_about_delete(\@makefiles, "automatically generated Makefiles"); ask_about_delete(\@test_main, "automatically generated test files"); ask_about_delete(\@autogen_cpp, "automatically generated source files"); if($#bad_cpp >= 0) { print "\n"; print $_,"\n" for @bad_cpp; print "There are some .cpp file where Box.h is not the first included file or MemLeakFindOn.h is not the last .h file included\n"; $cleaned = 0; } if($#bad_h >= 0) { print "\n"; print $_,"\n" for @bad_h; print "There are some .h files which use memory functions without memory leak finding on, or leave memory leak finding on at end\n"; $cleaned = 0; } if(-d 'debug') {print "debug directory exists\n"; $cleaned = 0;} if(-d 'release') {print "release directory exists\n"; $cleaned = 0;} if(-d 'parcels') {print "parcels directory exists\n"; $cleaned = 0;} if($dist_archives_exist) {print "boxbackup* files/dirs exist\n"; $cleaned = 0;} if(!$cleaned) { print <<__E; ======================================================== NOT CLEANED! ======================================================== __E } sub ask_about_delete { my ($del_r, $name) = @_; return if $#$del_r < 0; print "\n"; for(@$del_r) { print $_,"\n"; } print "Delete these ",$#$del_r + 1, " $name? "; my $in = ; chomp $in; if($in eq 'yes') { print "Deleting...\n"; unlink $_ for @$del_r } else { $cleaned = 0; } } boxbackup/infrastructure/0000775000175000017500000000000011652362373016402 5ustar siretartsiretartboxbackup/infrastructure/mingw/0000775000175000017500000000000011652362372017522 5ustar siretartsiretartboxbackup/infrastructure/mingw/configure.sh0000775000175000017500000000160311445743664022051 0ustar siretartsiretart#!/bin/sh DEP_PATH=/usr/i686-pc-mingw32 if [ ! -r "$DEP_PATH/lib/libssl.a" ]; then echo "Error: install OpenSSL as instructed by" \ "docs/backup/win32_build_on_cygwin_using_mingw.txt" >&2 exit 2 fi if [ ! -r "$DEP_PATH/lib/libpcreposix.a" \ -o ! -r "$DEP_PATH/lib/libpcre.a" \ -o ! -r "$DEP_PATH/include/pcreposix.h" ]; then echo "Error: install PCRE as instructed by" \ "docs/backup/win32_build_on_cygwin_using_mingw.txt" >&2 exit 2 fi export CXX="g++ -mno-cygwin" export LD="g++ -mno-cygwin" export CFLAGS="-mno-cygwin -mthreads" export CXXFLAGS="-mno-cygwin -mthreads" export LDFLAGS="-mno-cygwin -mthreads" export LIBS="-lcrypto -lws2_32 -lgdi32" if [ ! -x "configure" ]; then if ! ./bootstrap; then echo "Error: bootstrap failed, aborting." >&2 exit 1 fi fi if ! ./configure --target=i686-pc-mingw32; then echo "Error: configure failed, aborting." >&2 exit 1 fi exit 0 boxbackup/infrastructure/makedistribution.pl.in0000775000175000017500000001763611345265741022741 0ustar siretartsiretart#!@PERL@ use strict; use Symbol; # comment string for various endings my %comment_chars = ('cpp' => '// ', 'h' => '// ', 'pl' => '# ', 'pm' => '# ', '' => '# '); # other extensions which need text copying, just to remove the private stuff # .in is included here, as these could be any kind of source, but clearly # they have text substitutions run on them by autoconf, so we can too :) my %text_files = ('txt' => 1, 'spec' => 1, 'in' => 1); # files which don't get the license added # my %file_license = (); # 'filename' => 'GPL', 'DUAL' or 'none' # ---------------------------------------------- # filled in from the manifest file # my %dir_license = (); # 'dir' => 'GPL', 'DUAL' or 'none' # # most recently specified LICENSE become default until overridden my $current_license; # 'GPL', 'DUAL' or 'none' # distribution name my $distribution = $ARGV[0]; die "No distribution name specified on the command line" if $distribution eq ''; my $dist_root = "distribution/$distribution"; # check distribution exists die "Distribution '$distribution' does not exist" unless -d $dist_root; # get version open VERSION,"$dist_root/VERSION.txt" or die "Can't open $dist_root/VERSION.txt"; my $version = ; chomp $version; my $archive_name = ; chomp $archive_name; close VERSION; # consistency check die "Archive name '$archive_name' is not equal to the distribution name '$distribution'" unless $archive_name eq $distribution; my $svnversion = `svnversion .`; chomp $svnversion; $svnversion =~ tr/0-9A-Za-z/_/c; if($version =~ /USE_SVN_VERSION/) { # for developers, use SVN version open INFO,'svn info . |'; my $svnurl; while() { if(m/^URL: (.+?)[\n\r]+/) { $svnurl = $1; } } close INFO; $svnurl =~ m'box/(.+)$'; my $svndir = $1; $svndir =~ tr/0-9A-Za-z/_/c; $version =~ s/USE_SVN_VERSION/$svndir.'_'.$svnversion/e; } # make initial directory my $base_name = "$archive_name-$version"; system "rm -rf $base_name"; system "rm $base_name.tgz"; mkdir $base_name,0755; # get license files my %license_text; # name of license => array of lines of license text foreach my $license ("GPL", "DUAL") { my $file = "./LICENSE-$license.txt"; open LICENSE, $file or die "Can't open $file: $!"; my @lines = ; close LICENSE; unshift @lines, "distribution $base_name (svn version: $svnversion)\n"; $license_text{$license} = \@lines; } # copy files, make a note of all the modules included my %modules_included; my $private_sections_removed = 0; my $non_distribution_sections_removed = 0; sub copy_from_list { my $list = $_[0]; open LIST,$list or die "Can't open $list"; while(my $line = ) { next unless $line =~ m/\S/; chomp $line; my @words = split /\s+/, $line; my ($src,$dst,$other) = @words; $dst = $src if $dst eq ''; if($src eq 'MKDIR') { # actually we just need to make a directory here mkdir "$base_name/$dst",0755; } elsif($src eq 'LICENSE') { $current_license = $dst; } elsif($src eq 'REPLACE-VERSION-IN') { replace_version_in($dst); } elsif($src eq 'RUN') { my ($junk,$cmd) = split /\s+/, $line, 2; print "Running $cmd...\n"; if(system($cmd) != 0) { print "Error running $cmd. Aborting.\n"; exit(1); } } elsif(-d $src) { $modules_included{$line} = 1; copy_dir($src,$dst); } else { copy_file($src,$dst); } } close LIST; } copy_from_list("distribution/COMMON-MANIFEST.txt"); copy_from_list("$dist_root/DISTRIBUTION-MANIFEST.txt"); # Copy in the root directory and delete the DISTRIBUTION-MANIFEST file (system("cp $dist_root/*.* $base_name/") == 0) or die "Copy of root extra files failed"; unlink "$base_name/DISTRIBUTION-MANIFEST.txt" or die "Delete of DISTRIBUTION-MANIFEST.txt file failed"; replace_version_in("VERSION.txt"); # produce a new modules file my $modules = gensym; open $modules,"modules.txt" or die "Can't open modules.txt for reading"; open MODULES_OUT,">$base_name/modules.txt"; while(<$modules>) { # skip lines for modules which aren't included next if m/\A(\w+\/\w+)\s/ && !exists $modules_included{$1}; # skip private sections unless(skip_non_applicable_section($_, $modules, 'modules.txt')) { # copy line to out files print MODULES_OUT } } close MODULES_OUT; close $modules; # report on how many private sections were removed print "Private sections removed: $private_sections_removed\nNon-distribution sections removed: $non_distribution_sections_removed\n"; # tar it up system "tar cf - $base_name | gzip -9 - > $base_name.tgz"; sub copy_file { my ($fn,$dst_fn) = @_; my $ext; $ext = $1 if $fn =~ m/\.(\w+)\Z/; $dst_fn =~ m~\A(.+)/[^/]+?\Z~; # licensed or not? if(exists $comment_chars{$ext} && $current_license ne "none") { # copy as text, inserting license # print "source copy $fn to $base_name/$dst_fn\n"; my $in = gensym; open $in,$fn or die "$fn: $!"; open OUT,">$base_name/$dst_fn" or die "$base_name/$dst_fn: $!"; my $first = <$in>; if($first =~ m/\A#!/) { print OUT $first; $first = ''; } # write license my $b = $comment_chars{$ext}; my $this_license = $license_text{$current_license}; for (@$this_license) { print OUT $b, $_; } if($first ne '') { print OUT $first; } while(<$in>) { unless(skip_non_applicable_section($_, $in, $fn)) { print OUT } } close OUT; close $in; } elsif(exists $text_files{$ext}) { # copy this as text, to remove private stuff # print "text copy $fn to $base_name/$dst_fn\n"; my $in = gensym; open $in,$fn or die "$fn: $!"; open OUT,">$base_name/$dst_fn" or die "$base_name/$dst_fn: $!"; while(<$in>) { unless(skip_non_applicable_section($_, $in, $fn)) { print OUT } } close OUT; close $in; } else { # copy as binary # print "binary copy $fn to $base_name/$dst_fn\n"; my $cmd = "cp -p $fn $base_name/$dst_fn"; system($cmd) == 0 or die "copy failed: $cmd"; } # copy executable bit from src if(-x $fn) { system 'chmod','a+x',"$base_name/$dst_fn" } else { system 'chmod','a-x',"$base_name/$dst_fn" } } sub skip_non_applicable_section { my ($l, $filehandle, $filename) = @_; if($l =~ m/BOX_PRIVATE_BEGIN/) { # skip private section print "Removing private section from $filename\n"; $private_sections_removed++; while(<$filehandle>) {last if m/BOX_PRIVATE_END/} # skipped something return 1; } elsif($l =~ m/IF_DISTRIBUTION\((.+?)\)/) { # which distributions does this apply to? my $applies = 0; for(split /,/,$1) { $applies = 1 if $_ eq $distribution } unless($applies) { # skip section? print "Removing distribution specific section from $filename\n"; $non_distribution_sections_removed++; while(<$filehandle>) {last if m/END_IF_DISTRIBUTION/} } # hide this line return 1; } elsif($l =~ m/END_IF_DISTRIBUTION/) { # hide these lines return 1; } else { # no skipping, return this line return 0; } } sub copy_dir { my ($dir,$dst_dir) = @_; # copy an entire directory... first make sure it exists my @n = split /\//,$dst_dir; my $d = $base_name; for(@n) { $d .= '/'; $d .= $_; mkdir $d,0755; } # then do each of the files within in opendir DIR,$dir; my @items = readdir DIR; closedir DIR; for(@items) { next if m/\A\./; next if m/\A_/; next if m/\AMakefile\Z/; next if m/\Aautogen/; next if m/-smf-method\Z/; # copy only the .in versions next if m/-manifest.xml\Z/; # copy only the .in versions if($dir eq 'docs') { next if m/.(x[sm]l|tmpl)\Z/; # don't include doc sources next if m/generate_except_xml.pl/; } next if !-f "$dir/$_"; copy_file("$dir/$_","$dst_dir/$_"); } } sub replace_version_in { my ($file) = @_; my $fn = $base_name . '/' . $file; open IN,$fn or die "Can't open $fn"; open OUT,'>'.$fn.'.new' or die "Can't open $fn.new for writing"; while() { s/###DISTRIBUTION-VERSION-NUMBER###/$version/g; s/.*USE_SVN_VERSION.*/$version/g; print OUT } close OUT; close IN; rename($fn.'.new', $fn) or die "Can't rename in place $fn"; } boxbackup/infrastructure/parcelpath.pl0000664000175000017500000000046211072154720021053 0ustar siretartsiretart#!perl unless (@ARGV == 2) { die "Usage: $0 \n"; } $basedir = $0; $basedir =~ s|/.*||; $basedir .= "/.."; -d $basedir or die "$basedir: $!"; chdir $basedir or die "$basedir: $!"; require "infrastructure/BoxPlatform.pm.in"; print BoxPlatform::parcel_dir(@ARGV) . "\n"; exit 0; boxbackup/infrastructure/m4/0000775000175000017500000000000011652362372016721 5ustar siretartsiretartboxbackup/infrastructure/m4/ax_check_bdb_v1.m40000664000175000017500000000301510350277356022145 0ustar siretartsiretartdnl @synopsis AX_CHECK_BDB_V1 dnl dnl This macro find an installation of Berkeley DB version 1, or compatible. dnl It will define the following macros on success: dnl dnl HAVE_DB - If Berkeley DB version 1 or compatible is available dnl DB_HEADER - The relative path and filename of the header file dnl LIBS - Updated for correct library dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/12 dnl @license AllPermissive AC_DEFUN([AX_CHECK_BDB_V1], [ ac_have_bdb=no AC_CHECK_HEADERS([db_185.h db4/db_185.h db3/db_185.h db1/db.h db.h], [ac_bdb_header=$ac_header; break], [ac_bdb_header=""]) if test "x$ac_bdb_header" != x; then AC_SEARCH_LIBS([__db185_open], [db db-4.4 db-4.3 db-4.2 db-4.1 db-4.0 db4 db-3 db3], [ac_have_bdb=yes], [AC_SEARCH_LIBS([dbopen], [db-1 db1 db], [ac_have_bdb=yes])]) fi if test "x$ac_have_bdb" = "xyes"; then AC_MSG_CHECKING([whether found db libraries work]) AC_RUN_IFELSE([AC_LANG_PROGRAM([[ $ac_includes_default #include "$ac_bdb_header"]], [[ DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); if(dbp) dbp->close(dbp); return 0; ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_DB], 1, [Define to 1 if Berkeley DB is available]) AC_DEFINE_UNQUOTED([DB_HEADER], ["$ac_bdb_header"], [Define to the location of the Berkeley DB 1.85 header]) ], [ AC_MSG_RESULT([no]) ac_have_bdb=no ]) fi ])dnl boxbackup/infrastructure/m4/ax_check_malloc_workaround.m40000664000175000017500000000231211072137425024523 0ustar siretartsiretartdnl @synopsis AX_CHECK_MALLOC_WORKAROUND dnl dnl This macro will see if there is a potential STL memory leak, and if we can dnl work around it will define __USE_MALLOC as the fix. dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/12 dnl @license AllPermissive AC_DEFUN([AX_CHECK_MALLOC_WORKAROUND], [ if test "x$GXX" = "xyes"; then AC_CACHE_CHECK([for gcc version 3 or later], [box_cv_gcc_3_plus], [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ #if __GNUC__ < 3 #error "Old GNU C" #endif ]])], [box_cv_gcc_3_plus=yes], [box_cv_gcc_3_plus=no] )]) if test "x$box_cv_gcc_3_plus" = "xno"; then AC_CACHE_CHECK([for malloc workaround], [box_cv_malloc_workaround], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define __USE_MALLOC #include ]], [[ std::string s; s = "test"; ]])], [box_cv_malloc_workaround=yes], [box_cv_malloc_workaround=no] )]) if test "x$box_cv_malloc_workaround" = "xyes"; then AC_DEFINE([__USE_MALLOC], 1, [Define to 1 if __USE_MALLOC is required work around STL memory leaks]) fi fi fi ])dnl boxbackup/infrastructure/m4/ax_path_bdb.m40000664000175000017500000005174010642030367021417 0ustar siretartsiretartdnl @synopsis AX_PATH_BDB([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl dnl This macro finds the latest version of Berkeley DB on the system, dnl and ensures that the header file and library versions match. If dnl MINIMUM-VERSION is specified, it will ensure that the library found dnl is at least that version. dnl dnl It determines the name of the library as well as the path to the dnl header file and library. It will check both the default environment dnl as well as the default Berkeley DB install location. When found, it dnl sets BDB_LIBS, BDB_CPPFLAGS, and BDB_LDFLAGS to the necessary dnl values to add to LIBS, CPPFLAGS, and LDFLAGS, as well as setting dnl BDB_VERSION to the version found. HAVE_DB_H is defined also. dnl dnl The options --with-bdb-headers=DIR and --with-bdb-lib=DIR can be dnl used to specify a specific Berkeley DB installation to use. dnl dnl An example of its use is: dnl dnl AX_PATH_BDB([3],[ dnl LIBS="$BDB_LIBS $LIBS" dnl LDFLAGS="$BDB_LDFLAGS $LDFLAGS" dnl CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS" dnl ]) dnl dnl which will locate the latest version of Berkeley DB on the system, dnl and ensure that it is version 3.0 or higher. dnl dnl Details: This macro does not use either AC_CHECK_HEADERS or dnl AC_CHECK_LIB because, first, the functions inside the library are dnl sometimes renamed to contain a version code that is only available dnl from the db.h on the system, and second, because it is common to dnl have multiple db.h and libdb files on a system it is important to dnl make sure the ones being used correspond to the same version. dnl Additionally, there are many different possible names for libdb dnl when installed by an OS distribution, and these need to be checked dnl if db.h does not correspond to libdb. dnl dnl When cross compiling, only header versions are verified since it dnl would be difficult to check the library version. Additionally the dnl default Berkeley DB installation locations /usr/local/BerkeleyDB* dnl are not searched for higher versions of the library. dnl dnl The format for the list of library names to search came from the dnl Cyrus IMAP distribution, although they are generated dynamically dnl here, and only for the version found in db.h. dnl dnl The macro AX_COMPARE_VERSION is required to use this macro, and dnl should be available from the Autoconf Macro Archive. dnl dnl The author would like to acknowledge the generous and valuable dnl feedback from Guido Draheim, without which this macro would be far dnl less robust, and have poor and inconsistent cross compilation dnl support. dnl dnl Changes: dnl dnl 1/5/05 applied patch from Rafa Rzepecki to eliminate compiler dnl warning about unused variable, argv dnl 1/7/07 Add --with-bdb-headers and --with-bdb-lib options dnl (James O'Gorman, james@netinertia.co.uk) dnl dnl @category InstalledPackages dnl @author Tim Toolan dnl @version 2005-01-17 dnl @license GPLWithACException dnl ######################################################################### AC_DEFUN([AX_PATH_BDB], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_ok=no # Add --with-bdb-headers and --with-bdb-lib options AC_ARG_WITH([bdb-headers], [AC_HELP_STRING([--with-bdb-headers=DIR], [Berkeley DB include files location])]) AC_ARG_WITH([bdb-lib], [AC_HELP_STRING([--with-bdb-lib=DIR], [Berkeley DB library location])]) # Check if --with-bdb-dir was specified. if test "x$with_bdb_headers" = "x" -a "x$with_bdb_lib" = "x"; then # No option specified, so just search the system. AX_PATH_BDB_NO_OPTIONS([$1], [HIGHEST], [ ax_path_bdb_ok=yes ]) else ax_path_bdb_INC="$with_bdb_headers" ax_path_bdb_LIB="$with_bdb_lib" dnl # Save previous environment, and modify with new stuff. ax_path_bdb_save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="-I$ax_path_bdb_INC $CPPFLAGS" ax_path_bdb_save_LDFLAGS=$LDFLAGS LDFLAGS="-L$ax_path_bdb_LIB $LDFLAGS" # Check for specific header file db.h AC_MSG_CHECKING([db.h presence in $ax_path_bdb_INC]) if test -f "$ax_path_bdb_INC/db.h" ; then AC_MSG_RESULT([yes]) # Check for library AX_PATH_BDB_NO_OPTIONS([$1], [ENVONLY], [ ax_path_bdb_ok=yes BDB_CPPFLAGS="-I$ax_path_bdb_INC" BDB_LDFLAGS="-L$ax_path_bdb_LIB" ]) else AC_MSG_RESULT([no]) AC_MSG_NOTICE([no usable Berkeley DB not found]) fi dnl # Restore the environment. CPPFLAGS="$ax_path_bdb_save_CPPFLAGS" LDFLAGS="$ax_path_bdb_save_LDFLAGS" fi dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_ok" = "yes" ; then m4_ifvaln([$2],[$2],[:])dnl m4_ifvaln([$3],[else $3])dnl fi ]) dnl AX_PATH_BDB dnl ######################################################################### dnl Check for berkeley DB of at least MINIMUM-VERSION on system. dnl dnl The OPTION argument determines how the checks occur, and can be one of: dnl dnl HIGHEST - Check both the environment and the default installation dnl directories for Berkeley DB and choose the version that dnl is highest. (default) dnl ENVFIRST - Check the environment first, and if no satisfactory dnl library is found there check the default installation dnl directories for Berkeley DB which is /usr/local/BerkeleyDB* dnl ENVONLY - Check the current environment only. dnl dnl Requires AX_PATH_BDB_PATH_GET_VERSION, AX_PATH_BDB_PATH_FIND_HIGHEST, dnl AX_PATH_BDB_ENV_CONFIRM_LIB, AX_PATH_BDB_ENV_GET_VERSION, and dnl AX_COMPARE_VERSION macros. dnl dnl Result: sets ax_path_bdb_no_options_ok to yes or no dnl sets BDB_LIBS, BDB_CPPFLAGS, BDB_LDFLAGS, BDB_VERSION dnl dnl AX_PATH_BDB_NO_OPTIONS([MINIMUM-VERSION], [OPTION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_NO_OPTIONS], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_no_options_ok=no # Values to add to environment to use Berkeley DB. BDB_VERSION='' BDB_LIBS='' BDB_CPPFLAGS='' BDB_LDFLAGS='' # Check cross compilation here. if test "x$cross_compiling" = "xyes" ; then # If cross compiling, can't use AC_RUN_IFELSE so do these tests. # The AC_PREPROC_IFELSE confirms that db.h is preprocessable, # and extracts the version number from it. AC_MSG_CHECKING([for db.h]) AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_no_options_HEADER_VERSION])dnl HEADER_VERSION='' AC_PREPROC_IFELSE([ AC_LANG_SOURCE([[ #include #ifdef DB_VERSION_MAJOR AX_PATH_BDB_STUFF DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH #else AX_PATH_BDB_STUFF 1,0,0 #endif ]]) ],[ # Extract version from preprocessor output. HEADER_VERSION=`eval "$ac_cpp conftest.$ac_ext" 2> /dev/null \ | grep AX_PATH_BDB_STUFF | sed 's/[[^0-9,]]//g;s/,/./g;1q'` ],[]) if test "x$HEADER_VERSION" = "x" ; then AC_MSG_RESULT([no]) else AC_MSG_RESULT([$HEADER_VERSION]) # Check that version is high enough. AX_COMPARE_VERSION([$HEADER_VERSION],[ge],[$1],[ # get major and minor version numbers AS_VAR_PUSHDEF([MAJ],[ax_path_bdb_no_options_MAJOR])dnl MAJ=`echo $HEADER_VERSION | sed 's,\..*,,'` AS_VAR_PUSHDEF([MIN],[ax_path_bdb_no_options_MINOR])dnl MIN=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` dnl # Save LIBS. ax_path_bdb_no_options_save_LIBS="$LIBS" # Check that we can link with the library. AC_SEARCH_LIBS([db_version], [db db-$MAJ.$MIN db$MAJ.$MIN db$MAJ$MIN db-$MAJ db$MAJ],[ # Sucessfully found library. ax_path_bdb_no_options_ok=yes BDB_VERSION=$HEADER_VERSION # Extract library from LIBS ax_path_bdb_no_options_LEN=` \ echo "x$ax_path_bdb_no_options_save_LIBS" \ | awk '{print(length)}'` BDB_LIBS=`echo "x$LIBS " \ | sed "s/.\{$ax_path_bdb_no_options_LEN\}\$//;s/^x//;s/ //g"` ],[]) dnl # Restore LIBS LIBS="$ax_path_bdb_no_options_save_LIBS" AS_VAR_POPDEF([MAJ])dnl AS_VAR_POPDEF([MIN])dnl ]) fi AS_VAR_POPDEF([HEADER_VERSION])dnl else # Not cross compiling. # Check version of Berkeley DB in the current environment. AX_PATH_BDB_ENV_GET_VERSION([ AX_COMPARE_VERSION([$ax_path_bdb_env_get_version_VERSION],[ge],[$1],[ # Found acceptable version in current environment. ax_path_bdb_no_options_ok=yes BDB_VERSION="$ax_path_bdb_env_get_version_VERSION" BDB_LIBS="$ax_path_bdb_env_get_version_LIBS" ]) ]) # Determine if we need to search /usr/local/BerkeleyDB* ax_path_bdb_no_options_DONE=no if test "x$2" = "xENVONLY" ; then ax_path_bdb_no_options_DONE=yes elif test "x$2" = "xENVFIRST" ; then ax_path_bdb_no_options_DONE=$ax_path_bdb_no_options_ok fi if test "$ax_path_bdb_no_options_DONE" = "no" ; then ax_compare_version=false # Check for highest in /usr/local/BerkeleyDB* AX_PATH_BDB_PATH_FIND_HIGHEST([ if test "$ax_path_bdb_no_options_ok" = "yes" ; then # If we already have an acceptable version use this if higher. AX_COMPARE_VERSION( [$ax_path_bdb_path_find_highest_VERSION],[gt],[$BDB_VERSION]) else # Since we didn't have an acceptable version check if this one is. AX_COMPARE_VERSION( [$ax_path_bdb_path_find_highest_VERSION],[ge],[$1]) fi ]) dnl # If result from _AX_COMPARE_VERSION is true we want this version. if test "$ax_compare_version" = "true" ; then ax_path_bdb_no_options_ok=yes BDB_LIBS="-ldb" if test "x$ax_path_bdb_path_find_highest_DIR" != x ; then BDB_CPPFLAGS="-I$ax_path_bdb_path_find_highest_DIR/include" BDB_LDFLAGS="-L$ax_path_bdb_path_find_highest_DIR/lib" fi BDB_VERSION="$ax_path_bdb_path_find_highest_VERSION" fi fi fi dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_no_options_ok" = "yes" ; then AC_MSG_NOTICE([using Berkeley DB version $BDB_VERSION]) AC_DEFINE([HAVE_DB_H],[1], [Define to 1 if you have the header file.]) m4_ifvaln([$3],[$3])dnl else AC_MSG_NOTICE([no Berkeley DB version $1 or higher found]) m4_ifvaln([$4],[$4])dnl fi ]) dnl AX_PATH_BDB_NO_OPTIONS dnl ######################################################################### dnl Check the default installation directory for Berkeley DB which is dnl of the form /usr/local/BerkeleyDB* for the highest version. dnl dnl Result: sets ax_path_bdb_path_find_highest_ok to yes or no, dnl sets ax_path_bdb_path_find_highest_VERSION to version, dnl sets ax_path_bdb_path_find_highest_DIR to directory. dnl dnl AX_PATH_BDB_PATH_FIND_HIGHEST([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_PATH_FIND_HIGHEST], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_path_find_highest_ok=no AS_VAR_PUSHDEF([VERSION],[ax_path_bdb_path_find_highest_VERSION])dnl VERSION='' ax_path_bdb_path_find_highest_DIR='' # find highest verison in default install directory for Berkeley DB AS_VAR_PUSHDEF([CURDIR],[ax_path_bdb_path_find_highest_CURDIR])dnl AS_VAR_PUSHDEF([CUR_VERSION],[ax_path_bdb_path_get_version_VERSION])dnl for CURDIR in `ls -d /usr/local/BerkeleyDB* 2> /dev/null` do AX_PATH_BDB_PATH_GET_VERSION([$CURDIR],[ AX_COMPARE_VERSION([$CUR_VERSION],[gt],[$VERSION],[ ax_path_bdb_path_find_highest_ok=yes ax_path_bdb_path_find_highest_DIR="$CURDIR" VERSION="$CUR_VERSION" ]) ]) done AS_VAR_POPDEF([VERSION])dnl AS_VAR_POPDEF([CUR_VERSION])dnl AS_VAR_POPDEF([CURDIR])dnl dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_path_find_highest_ok" = "yes" ; then m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ]) dnl AX_PATH_BDB_PATH_FIND_HIGHEST dnl ######################################################################### dnl Checks for Berkeley DB in specified directory's lib and include dnl subdirectories. dnl dnl Result: sets ax_path_bdb_path_get_version_ok to yes or no, dnl sets ax_path_bdb_path_get_version_VERSION to version. dnl dnl AX_PATH_BDB_PATH_GET_VERSION(BDB-DIR, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_PATH_GET_VERSION], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_path_get_version_ok=no # Indicate status of checking for Berkeley DB header. AC_MSG_CHECKING([in $1/include for db.h]) ax_path_bdb_path_get_version_got_header=no test -f "$1/include/db.h" && ax_path_bdb_path_get_version_got_header=yes AC_MSG_RESULT([$ax_path_bdb_path_get_version_got_header]) # Indicate status of checking for Berkeley DB library. AC_MSG_CHECKING([in $1/lib for library -ldb]) ax_path_bdb_path_get_version_VERSION='' if test -d "$1/include" && test -d "$1/lib" && test "$ax_path_bdb_path_get_version_got_header" = "yes" ; then dnl # save and modify environment ax_path_bdb_path_get_version_save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="-I$1/include $CPPFLAGS" ax_path_bdb_path_get_version_save_LIBS="$LIBS" LIBS="$LIBS -ldb" ax_path_bdb_path_get_version_save_LDFLAGS="$LDFLAGS" LDFLAGS="-L$1/lib $LDFLAGS" # Compile and run a program that compares the version defined in # the header file with a version defined in the library function # db_version. AC_RUN_IFELSE([ AC_LANG_SOURCE([[ #include #include int main(int argc,char **argv) { (void) argv; #ifdef DB_VERSION_MAJOR int major,minor,patch; db_version(&major,&minor,&patch); if (argc > 1) printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && DB_VERSION_PATCH == patch) return 0; else return 1; #else DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); if(dbp) dbp->close(dbp); if (argc > 1) printf("1.0.0\n"); if (dbp) return 0; else return 1; #endif } ]]) ],[ # Program compiled and ran, so get version by adding argument. ax_path_bdb_path_get_version_VERSION=`./conftest$ac_exeext x` ax_path_bdb_path_get_version_ok=yes ],[],[]) dnl # restore environment CPPFLAGS="$ax_path_bdb_path_get_version_save_CPPFLAGS" LIBS="$ax_path_bdb_path_get_version_save_LIBS" LDFLAGS="$ax_path_bdb_path_get_version_save_LDFLAGS" fi dnl # Finally, execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_path_get_version_ok" = "yes" ; then AC_MSG_RESULT([$ax_path_bdb_path_get_version_VERSION]) m4_ifvaln([$2],[$2])dnl else AC_MSG_RESULT([no]) m4_ifvaln([$3],[$3])dnl fi ]) dnl AX_PATH_BDB_PATH_GET_VERSION ############################################################################# dnl Checks if version of library and header match specified version. dnl Only meant to be used by AX_PATH_BDB_ENV_GET_VERSION macro. dnl dnl Requires AX_COMPARE_VERSION macro. dnl dnl Result: sets ax_path_bdb_env_confirm_lib_ok to yes or no. dnl dnl AX_PATH_BDB_ENV_CONFIRM_LIB(VERSION, [LIBNAME]) AC_DEFUN([AX_PATH_BDB_ENV_CONFIRM_LIB], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_env_confirm_lib_ok=no dnl # save and modify environment to link with library LIBNAME ax_path_bdb_env_confirm_lib_save_LIBS="$LIBS" LIBS="$LIBS $2" # Compile and run a program that compares the version defined in # the header file with a version defined in the library function # db_version. AC_RUN_IFELSE([ AC_LANG_SOURCE([[ #include #include int main(int argc,char **argv) { (void) argv; #ifdef DB_VERSION_MAJOR int major,minor,patch; db_version(&major,&minor,&patch); if (argc > 1) printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && DB_VERSION_PATCH == patch) return 0; else return 1; #else DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); if(dbp) dbp->close(dbp); if (argc > 1) printf("1.0.0\n"); if (dbp) return 0; else return 1; #endif } ]]) ],[ # Program compiled and ran, so get version by giving an argument, # which will tell the program to print the output. ax_path_bdb_env_confirm_lib_VERSION=`./conftest$ac_exeext x` # If the versions all match up, indicate success. AX_COMPARE_VERSION([$ax_path_bdb_env_confirm_lib_VERSION],[eq],[$1],[ ax_path_bdb_env_confirm_lib_ok=yes ]) ],[],[]) dnl # restore environment LIBS="$ax_path_bdb_env_confirm_lib_save_LIBS" ]) dnl AX_PATH_BDB_ENV_CONFIRM_LIB ############################################################################# dnl Finds the version and library name for Berkeley DB in the dnl current environment. Tries many different names for library. dnl dnl Requires AX_PATH_BDB_ENV_CONFIRM_LIB macro. dnl dnl Result: set ax_path_bdb_env_get_version_ok to yes or no, dnl set ax_path_bdb_env_get_version_VERSION to the version found, dnl and ax_path_bdb_env_get_version_LIBNAME to the library name. dnl dnl AX_PATH_BDB_ENV_GET_VERSION([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([AX_PATH_BDB_ENV_GET_VERSION], [ dnl # Used to indicate success or failure of this function. ax_path_bdb_env_get_version_ok=no ax_path_bdb_env_get_version_VERSION='' ax_path_bdb_env_get_version_LIBS='' AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_env_get_version_HEADER_VERSION])dnl AS_VAR_PUSHDEF([TEST_LIBNAME],[ax_path_bdb_env_get_version_TEST_LIBNAME])dnl # Indicate status of checking for Berkeley DB library. AC_MSG_CHECKING([for db.h]) # Compile and run a program that determines the Berkeley DB version # in the header file db.h. HEADER_VERSION='' AC_RUN_IFELSE([ AC_LANG_SOURCE([[ #include #include int main(int argc,char **argv) { (void) argv; if (argc > 1) #ifdef DB_VERSION_MAJOR printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); #else printf("1.0.0\n"); #endif return 0; } ]]) ],[ # Program compiled and ran, so get version by adding an argument. HEADER_VERSION=`./conftest$ac_exeext x` AC_MSG_RESULT([$HEADER_VERSION]) ],[AC_MSG_RESULT([no])],[AC_MSG_RESULT([no])]) # Have header version, so try to find corresponding library. # Looks for library names in the order: # nothing, db, db-X.Y, dbX.Y, dbXY, db-X, dbX # and stops when it finds the first one that matches the version # of the header file. if test "x$HEADER_VERSION" != "x" ; then AC_MSG_CHECKING([for library containing Berkeley DB $HEADER_VERSION]) AS_VAR_PUSHDEF([MAJOR],[ax_path_bdb_env_get_version_MAJOR])dnl AS_VAR_PUSHDEF([MINOR],[ax_path_bdb_env_get_version_MINOR])dnl # get major and minor version numbers MAJOR=`echo $HEADER_VERSION | sed 's,\..*,,'` MINOR=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` # see if it is already specified in LIBS TEST_LIBNAME='' AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "db" TEST_LIBNAME='-ldb' AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "db-X.Y" TEST_LIBNAME="-ldb-${MAJOR}.$MINOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "dbX.Y" TEST_LIBNAME="-ldb${MAJOR}.$MINOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "dbXY" TEST_LIBNAME="-ldb$MAJOR$MINOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "db-X" TEST_LIBNAME="-ldb-$MAJOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then # try format "dbX" TEST_LIBNAME="-ldb$MAJOR" AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) fi dnl # Found a valid library. if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then if test "x$TEST_LIBNAME" = "x" ; then AC_MSG_RESULT([none required]) else AC_MSG_RESULT([$TEST_LIBNAME]) fi ax_path_bdb_env_get_version_VERSION="$HEADER_VERSION" ax_path_bdb_env_get_version_LIBS="$TEST_LIBNAME" ax_path_bdb_env_get_version_ok=yes else AC_MSG_RESULT([no]) fi AS_VAR_POPDEF([MAJOR])dnl AS_VAR_POPDEF([MINOR])dnl fi AS_VAR_POPDEF([HEADER_VERSION])dnl AS_VAR_POPDEF([TEST_LIBNAME])dnl dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ]) dnl BDB_ENV_GET_VERSION ############################################################################# boxbackup/infrastructure/m4/ax_check_mount_point.m40000664000175000017500000000343210377213062023357 0ustar siretartsiretartdnl @synopsis AX_CHECK_MOUNT_POINT([ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl dnl This macro will find out how to get mount point information if possible. dnl dnl The following defines will be set as appropriate: dnl HAVE_MOUNTS dnl HAVE_MNTENT_H dnl HAVE_SYS_MNTTAB_H dnl HAVE_SYS_MOUNT_H dnl HAVE_STRUCT_MNTENT_MNT_DIR dnl HAVE_STRUCT_MNTTAB_MNT_MOUNTP dnl HAVE_STRUCT_STATFS_F_MNTONNAME dnl HAVE_STRUCT_STATVFS_F_MNTONNAME dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/01 dnl @license AllPermissive AC_DEFUN([AX_CHECK_MOUNT_POINT], [ AC_CHECK_FUNCS([getmntent statfs]) AC_CHECK_HEADERS([sys/param.h]) AC_CHECK_HEADERS([mntent.h sys/mnttab.h sys/mount.h],,, [[ #include #ifdef HAVE_SYS_PARAM_H #include #endif ]]) # BSD AC_CHECK_MEMBERS([struct statfs.f_mntonname],,, [[ #ifdef HAVE_SYS_PARAM_H #include #endif #include ]]) # NetBSD AC_CHECK_MEMBERS([struct statvfs.f_mntonname],,, [[ #ifdef HAVE_SYS_PARAM_H #include #endif #include ]]) # Linux AC_CHECK_MEMBERS([struct mntent.mnt_dir],,, [[#include ]]) # Solaris AC_CHECK_MEMBERS([struct mnttab.mnt_mountp],,, [[ #include #include ]]) if test "x$ac_cv_member_struct_statfs_f_mntonname" = "xyes" || \ test "x$ac_cv_member_struct_statvfs_f_mntonname" = "xyes" || \ test "x$ac_cv_member_struct_mntent_mnt_dir" = "xyes" || \ test "x$ac_cv_member_struct_mnttab_mnt_mountp" = "xyes" then AC_DEFINE([HAVE_MOUNTS], [1], [Define to 1 if this platform supports mounts]) m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ])dnl boxbackup/infrastructure/m4/ax_func_syscall.m40000664000175000017500000000403111101177345022327 0ustar siretartsiretartdnl @synopsis AX_FUNC_SYSCALL dnl dnl This macro will find out how to call syscall. One or more of the following dnl defines will be made as appropriate: dnl HAVE_UNISTD_H - If unistd.h is available dnl HAVE_SYS_SYSCALL_H - If sys/syscall.h is available dnl HAVE_SYSCALL - If syscall() is available and is defined in unistd.h dnl HAVE___SYSCALL - If __syscall() is available and is defined in unistd.h dnl HAVE___SYSCALL_NEED_DEFN - If __syscall() is available but is not defined in unistd.h dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/01 dnl @license AllPermissive dnl dnl Changed by Chris on 081026: dnl dnl Reversed the test for __syscall(), remove the test for syscall(), dnl remove the definition and reverse the sense in ax_func_syscall.m4 dnl (which checks for __syscall() needing definition). dnl dnl Autoconf's AC_CHECK_FUNC defines it when testing for its presence, dnl so HAVE___SYSCALL will be true even if __syscall has no definition dnl in the system libraries, and this is precisely the case that we dnl want to test for, so now we test whether the test program compiles dnl with no explicit definition (only the system headers) and if that dnl fails, we set HAVE___SYSCALL_NEED_DEFN to 1. AC_DEFUN([AX_FUNC_SYSCALL], [ AC_CHECK_HEADERS([sys/syscall.h unistd.h]) AC_CHECK_FUNCS([syscall __syscall]) if test "x$ac_cv_func___syscall" = "xyes"; then AC_CACHE_CHECK([for __syscall needing definition], [box_cv_have___syscall_need_defn], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ $ac_includes_default #ifdef HAVE_SYS_SYSCALL_H #include #endif ]], [[ __syscall(SYS_exit, 0); return 1; ]])], [box_cv_have___syscall_need_defn=no], [box_cv_have___syscall_need_defn=yes] )]) if test "x$box_cv_have___syscall_need_defn" = "xyes"; then AC_DEFINE([HAVE___SYSCALL_NEED_DEFN], 1, [Define to 1 if __syscall is available but needs a definition]) fi fi ])dnl boxbackup/infrastructure/m4/ax_config_scripts.m40000664000175000017500000000074410405123223022655 0ustar siretartsiretartdnl @synopsis AX_CONFIG_SCRIPTS(SCRIPT_FILE, ...) dnl dnl Run AC_CONFIG_FILES on a list of scripts while preserving execute dnl permission. dnl dnl @category Automake dnl @author Martin Ebourne dnl @script dnl @license AllPermissive AC_DEFUN([AX_CONFIG_SCRIPTS],[ AC_REQUIRE([AC_CONFIG_FILES])dnl m4_foreach([SCRIPT_FILE], m4_quote(m4_split(m4_normalize([$1]))), [AC_CONFIG_FILES(SCRIPT_FILE, m4_quote(chmod +x SCRIPT_FILE))])dnl ]) boxbackup/infrastructure/m4/ax_check_define_pragma.m40000664000175000017500000000162411072137425023567 0ustar siretartsiretartdnl @synopsis AX_CHECK_DEFINE_PRAGMA([ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl dnl This macro will find out if the compiler will accept #pragma inside a dnl #define. HAVE_DEFINE_PRAGMA will be defined if this is the case, and dnl ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/03 dnl @license AllPermissive AC_DEFUN([AX_CHECK_DEFINE_PRAGMA], [ AC_CACHE_CHECK([for pre-processor pragma defines], [box_cv_have_define_pragma], [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ #define TEST_DEFINE #pragma pack(1) TEST_DEFINE ]])], [box_cv_have_define_pragma=yes], [box_cv_have_define_pragma=no] )]) if test "x$box_cv_have_define_pragma" = "xyes"; then AC_DEFINE([HAVE_DEFINE_PRAGMA], 1, [Define to 1 if #define of pragmas works]) m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ])dnl boxbackup/infrastructure/m4/ax_compare_version.m40000664000175000017500000001406310354474434023053 0ustar siretartsiretartdnl @synopsis AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl dnl This macro compares two version strings. It is used heavily in the dnl macro _AX_PATH_BDB for library checking. Due to the various number dnl of minor-version numbers that can exist, and the fact that string dnl comparisons are not compatible with numeric comparisons, this is dnl not necessarily trivial to do in a autoconf script. This macro dnl makes doing these comparisons easy. dnl dnl The six basic comparisons are available, as well as checking dnl equality limited to a certain number of minor-version levels. dnl dnl The operator OP determines what type of comparison to do, and can dnl be one of: dnl dnl eq - equal (test A == B) dnl ne - not equal (test A != B) dnl le - less than or equal (test A <= B) dnl ge - greater than or equal (test A >= B) dnl lt - less than (test A < B) dnl gt - greater than (test A > B) dnl dnl Additionally, the eq and ne operator can have a number after it to dnl limit the test to that number of minor versions. dnl dnl eq0 - equal up to the length of the shorter version dnl ne0 - not equal up to the length of the shorter version dnl eqN - equal up to N sub-version levels dnl neN - not equal up to N sub-version levels dnl dnl When the condition is true, shell commands ACTION-IF-TRUE are run, dnl otherwise shell commands ACTION-IF-FALSE are run. The environment dnl variable 'ax_compare_version' is always set to either 'true' or dnl 'false' as well. dnl dnl Examples: dnl dnl AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) dnl AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) dnl dnl would both be true. dnl dnl AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) dnl AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) dnl dnl would both be false. dnl dnl AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) dnl dnl would be true because it is only comparing two minor versions. dnl dnl AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) dnl dnl would be true because it is only comparing the lesser number of dnl minor versions of the two values. dnl dnl Note: The characters that separate the version numbers do not dnl matter. An empty string is the same as version 0. OP is evaluated dnl by autoconf, not configure, so must be a string, not a variable. dnl dnl The author would like to acknowledge Guido Draheim whose advice dnl about the m4_case and m4_ifvaln functions make this macro only dnl include the portions necessary to perform the specific comparison dnl specified by the OP argument in the final configure script. dnl dnl @category Misc dnl @author Tim Toolan dnl @version 2004-03-01 dnl @license GPLWithACException dnl ######################################################################### AC_DEFUN([AX_COMPARE_VERSION], [ # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. AS_VAR_PUSHDEF([A],[ax_compare_version_A]) A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` AS_VAR_PUSHDEF([B],[ax_compare_version_B]) B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary dnl # then the first line is used to determine if the condition is true. dnl # The sed right after the echo is to remove any indented white space. m4_case(m4_tolower($2), [lt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [gt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [le],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` ], [ge],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` ],[ dnl Split the operator from the subversion count if present. m4_bmatch(m4_substr($2,2), [0],[ # A count of zero means use the length of the shorter version. # Determine the number of characters in A and B. ax_compare_version_len_A=`echo "$A" | awk '{print(length)}'` ax_compare_version_len_B=`echo "$B" | awk '{print(length)}'` # Set A to no more than B's length and B to no more than A's length. A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` ], [[0-9]+],[ # A count greater than zero means use only that many subversions A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` ], [.+],[ AC_WARNING( [illegal OP numeric parameter: $2]) ],[]) # Pad zeros at end of numbers to make same length. ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" B="$B`echo $A | sed 's/./0/g'`" A="$ax_compare_version_tmp_A" # Check for equality or inequality as necessary. m4_case(m4_tolower(m4_substr($2,0,2)), [eq],[ test "x$A" = "x$B" && ax_compare_version=true ], [ne],[ test "x$A" != "x$B" && ax_compare_version=true ],[ AC_WARNING([illegal OP parameter: $2]) ]) ]) AS_VAR_POPDEF([A])dnl AS_VAR_POPDEF([B])dnl dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. if test "$ax_compare_version" = "true" ; then m4_ifvaln([$4],[$4],[:])dnl m4_ifvaln([$5],[else $5])dnl fi ]) dnl AX_COMPARE_VERSION boxbackup/infrastructure/m4/ax_check_syscall_lseek.m40000664000175000017500000000475611165664221023656 0ustar siretartsiretartdnl @synopsis AX_CHECK_SYSCALL_LSEEK([ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl dnl This macro will find out if the lseek syscall requires a dummy middle dnl parameter dnl dnl The following defines will be set as appropriate: dnl HAVE_LSEEK_DUMMY_PARAM dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/03 dnl @license AllPermissive AC_DEFUN([AX_CHECK_SYSCALL_LSEEK], [ AC_REQUIRE([AX_FUNC_SYSCALL])dnl if test "x$ac_cv_header_sys_syscall_h" = "xyes"; then AC_CACHE_CHECK([[whether syscall lseek requires dummy parameter]], [box_cv_have_lseek_dummy_param], [AC_TRY_RUN( [ $ac_includes_default #include #include #ifdef HAVE___SYSCALL_NEED_DEFN extern "C" off_t __syscall(quad_t number, ...); #endif #ifdef HAVE___SYSCALL // always use it if we have it #undef syscall #define syscall __syscall #endif int main() { int fh = creat("lseektest", 0600); int res = 0; if(fh>=0) { // This test tries first to seek to position 0, with NO // "dummy argument". If lseek does actually require a dummy // argument, then it will eat SEEK_SET for the offset and // try to use 99 as whence, which is invalid, so res will be // -1, the program will return zero and // have_lseek_dummy_param=yes // (whew! that took 1 hour to figure out) // The "dummy argument" probably means that it takes a 64-bit // offset, so this was probably a bug anyway, and now that // we cast the offset to off_t, it should never be needed // (if my reasoning is correct). res = syscall(SYS_lseek, fh, (off_t)0, SEEK_SET, 99); close(fh); } unlink("lseektest"); return res!=-1; } ], [box_cv_have_lseek_dummy_param=yes], [box_cv_have_lseek_dummy_param=no], [box_cv_have_lseek_dummy_param=no # assume not for cross-compiling] )]) if test "x$box_cv_have_lseek_dummy_param" = "xyes"; then AC_DEFINE([HAVE_LSEEK_DUMMY_PARAM], 1, [Define to 1 if syscall lseek requires a dummy middle parameter]) fi fi if test "x$box_cv_have_lseek_dummy_param" = "xno" then m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ])dnl boxbackup/infrastructure/m4/ax_random_device.m40000664000175000017500000000226210347400657022453 0ustar siretartsiretartdnl @synopsis AX_RANDOM_DEVICE dnl dnl This macro will check for a random device, allowing the user to explicitly dnl set the path. The user uses '--with-random=FILE' as an argument to dnl configure. dnl dnl If A random device is found then HAVE_RANDOM_DEVICE is set to 1 and dnl RANDOM_DEVICE contains the path. dnl dnl @category Miscellaneous dnl @author Martin Ebourne dnl @version 2005/07/01 dnl @license AllPermissive AC_DEFUN([AX_RANDOM_DEVICE], [ AC_ARG_WITH([random], [AC_HELP_STRING([--with-random=FILE], [Use FILE as random number seed [auto-detected]])], [RANDOM_DEVICE="$withval"], [AC_CHECK_FILE("/dev/urandom", [RANDOM_DEVICE="/dev/urandom";], [AC_CHECK_FILE("/dev/arandom", [RANDOM_DEVICE="/dev/arandom";], [AC_CHECK_FILE("/dev/random", [RANDOM_DEVICE="/dev/random";])] )]) ]) if test "x$RANDOM_DEVICE" != "x" ; then AC_DEFINE([HAVE_RANDOM_DEVICE], 1, [Define to 1 (and set RANDOM_DEVICE) if a random device is available]) AC_SUBST([RANDOM_DEVICE]) AC_DEFINE_UNQUOTED([RANDOM_DEVICE], ["$RANDOM_DEVICE"], [Define to the filename of the random device (and set HAVE_RANDOM_DEVICE)]) fi ])dnl boxbackup/infrastructure/m4/ax_check_nonaligned_access.m40000664000175000017500000000434411072137425024447 0ustar siretartsiretartdnl @synopsis AX_CHECK_NONALIGNED_ACCESS dnl dnl This macro will see if non-aligned memory accesses will fail. The following dnl defines will be made as appropriate: dnl HAVE_ALIGNED_ONLY_INT16 dnl HAVE_ALIGNED_ONLY_INT32 dnl HAVE_ALIGNED_ONLY_INT64 dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/12 dnl @license AllPermissive AC_DEFUN([AX_CHECK_NONALIGNED_ACCESS], [ AC_CACHE_CHECK([if non-aligned 16 bit word accesses fail], [box_cv_have_aligned_only_int16], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ #ifndef HAVE_UINT16_T #define uint16_t u_int16_t; #endif uint16_t scratch[2]; memset(scratch, 0, sizeof(scratch)); return *(uint16_t*)((char*)scratch+1); ]])], [box_cv_have_aligned_only_int16=no], [box_cv_have_aligned_only_int16=yes] )]) if test "x$box_cv_have_aligned_only_int16" = "xyes"; then AC_DEFINE([HAVE_ALIGNED_ONLY_INT16], 1, [Define to 1 if non-aligned int16 access will fail]) fi AC_CACHE_CHECK([if non-aligned 32 bit word accesses fail], [box_cv_have_aligned_only_int32], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ #ifndef HAVE_UINT32_T #define uint32_t u_int32_t; #endif uint32_t scratch[2]; memset(scratch, 0, sizeof(scratch)); return *(uint32_t*)((char*)scratch+1); ]])], [box_cv_have_aligned_only_int32=no], [box_cv_have_aligned_only_int32=yes] )]) if test "x$box_cv_have_aligned_only_int32" = "xyes"; then AC_DEFINE([HAVE_ALIGNED_ONLY_INT32], 1, [Define to 1 if non-aligned int32 access will fail]) fi AC_CACHE_CHECK([if non-aligned 64 bit word accesses fail], [box_cv_have_aligned_only_int64], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ #ifndef HAVE_UINT64_T #define uint64_t u_int64_t; #endif uint64_t scratch[2]; memset(scratch, 0, sizeof(scratch)); return *(uint64_t*)((char*)scratch+1); ]])], [box_cv_have_aligned_only_int64=no], [box_cv_have_aligned_only_int64=yes] )]) if test "x$box_cv_have_aligned_only_int64" = "xyes"; then AC_DEFINE([HAVE_ALIGNED_ONLY_INT64], 1, [Define to 1 if non-aligned int64 access will fail]) fi ])dnl boxbackup/infrastructure/m4/ax_check_ssl.m40000664000175000017500000000247610347400657021621 0ustar siretartsiretartdnl @synopsis AX_CHECK_SSL([ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl dnl This macro will check for OpenSSL in the standard path, allowing the user dnl to specify a directory if it is not found. The user uses dnl '--with-ssl-headers=/path/to/headers' or dnl '--with-ssl-lib=/path/to/lib' as arguments to configure. dnl dnl If OpenSSL is found the include directory gets added to CPPFLAGS, dnl '-lcrypto', '-lssl', and the libraries directory are added to LDFLAGS. dnl Also HAVE_SSL is defined to 1, and ACTION-IF-TRUE and ACTION-IF-FALSE are dnl run as appropriate dnl dnl @category InstalledPackages dnl @author Martin Ebourne dnl @version 2005/07/01 dnl @license AllPermissive AC_DEFUN([AX_CHECK_SSL], [ AC_ARG_WITH( [ssl-headers], [AC_HELP_STRING([--with-ssl-headers=DIR], [SSL include files location])], [CPPFLAGS="$CPPFLAGS -I$withval"]) AC_ARG_WITH( [ssl-lib], [AC_HELP_STRING([--with-ssl-lib=DIR], [SSL library location])], [LDFLAGS="$LDFLAGS -L$withval"]) ax_check_ssl_found=yes AC_CHECK_HEADERS([openssl/ssl.h],, [ax_check_ssl_found=no]) AC_CHECK_LIB([ssl], [SSL_read],, [ax_check_ssl_found=no], [-lcrypto]) if test "x$ax_check_ssl_found" = "xyes"; then AC_DEFINE([HAVE_SSL], 1, [Define to 1 if SSL is available]) m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ])dnl boxbackup/infrastructure/m4/ax_check_llong_minmax.m40000664000175000017500000000505610601334301023462 0ustar siretartsiretartdnl @synopsis AX_CHECK_LLONG_MINMAX dnl dnl This macro will fix up LLONG_MIN and LLONG_MAX as appropriate. I'm finding dnl it quite difficult to believe that so many hoops are necessary. The world dnl seems to have gone quite mad. dnl dnl This gem is adapted from the OpenSSH configure script so here's dnl the original copyright notice: dnl dnl Copyright (c) 1999-2004 Damien Miller dnl dnl Permission to use, copy, modify, and distribute this software for any dnl purpose with or without fee is hereby granted, provided that the above dnl copyright notice and this permission notice appear in all copies. dnl dnl @category C dnl @author Martin Ebourne and Damien Miller dnl @version 2005/07/07 AC_DEFUN([AX_CHECK_LLONG_MINMAX], [ AC_CHECK_DECL([LLONG_MAX], [have_llong_max=1], , [[#include ]]) if test -z "$have_llong_max"; then AC_MSG_CHECKING([[for max value of long long]]) AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include /* Why is this so damn hard? */ #undef __GNUC__ #undef __USE_ISOC99 #define __USE_ISOC99 #include #define DATA "conftest.llminmax" int main(void) { FILE *f; long long i, llmin, llmax = 0; if((f = fopen(DATA,"w")) == NULL) exit(1); #if defined(LLONG_MIN) && defined(LLONG_MAX) fprintf(stderr, "Using system header for LLONG_MIN and LLONG_MAX\n"); llmin = LLONG_MIN; llmax = LLONG_MAX; #else fprintf(stderr, "Calculating LLONG_MIN and LLONG_MAX\n"); /* This will work on one's complement and two's complement */ for (i = 1; i > llmax; i <<= 1, i++) llmax = i; llmin = llmax + 1LL; /* wrap */ #endif /* Sanity check */ if (llmin + 1 < llmin || llmin - 1 < llmin || llmax + 1 > llmax || llmax - 1 > llmax) { fprintf(f, "unknown unknown\n"); exit(2); } if (fprintf(f ,"%lld %lld", llmin, llmax) < 0) exit(3); exit(0); } ]])], [ read llong_min llong_max < conftest.llminmax AC_MSG_RESULT([$llong_max]) AC_DEFINE_UNQUOTED([LLONG_MAX], [${llong_max}LL], [max value of long long calculated by configure]) AC_MSG_CHECKING([[for min value of long long]]) AC_MSG_RESULT([$llong_min]) AC_DEFINE_UNQUOTED([LLONG_MIN], [${llong_min}LL], [min value of long long calculated by configure]) ], [AC_MSG_RESULT(not found)], [AC_MSG_WARN([[cross compiling: not checking]])] ) fi ])dnl boxbackup/infrastructure/m4/ax_split_version.m40000664000175000017500000000150210354474434022552 0ustar siretartsiretartdnl @synopsis AX_SPLIT_VERSION(DEFINE, VERSION) dnl dnl Splits a version number in the format MAJOR.MINOR.POINT into it's dnl separate components and AC_DEFINES _MAJOR etc with the values. dnl dnl @category Automake dnl @author Martin Ebourne dnl @version dnl @license AllPermissive AC_DEFUN([AX_SPLIT_VERSION],[ ax_major_version=`echo "$2" | sed 's/\([[^.]][[^.]]*\).*/\1/'` ax_minor_version=`echo "$2" | sed 's/[[^.]][[^.]]*.\([[^.]][[^.]]*\).*/\1/'` ax_point_version=`echo "$2" | sed 's/[[^.]][[^.]]*.[[^.]][[^.]]*.\(.*\)/\1/'` AC_DEFINE_UNQUOTED([$1_MAJOR], [$ax_major_version], [Define to major version for $1]) AC_DEFINE_UNQUOTED([$1_MINOR], [$ax_minor_version], [Define to minor version for $1]) AC_DEFINE_UNQUOTED([$1_POINT], [$ax_point_version], [Define to point version for $1]) ]) boxbackup/infrastructure/m4/ac_cxx_exceptions.m40000664000175000017500000000127010347400657022670 0ustar siretartsiretartdnl @synopsis AC_CXX_EXCEPTIONS dnl dnl If the C++ compiler supports exceptions handling (try, throw and dnl catch), define HAVE_EXCEPTIONS. dnl dnl @category Cxx dnl @author Todd Veldhuizen dnl @author Luc Maisonobe dnl @version 2004-02-04 dnl @license AllPermissive AC_DEFUN([AC_CXX_EXCEPTIONS], [AC_CACHE_CHECK(whether the compiler supports exceptions, ac_cv_cxx_exceptions, [AC_LANG_SAVE AC_LANG_CPLUSPLUS AC_TRY_COMPILE(,[try { throw 1; } catch (int i) { return i; }], ac_cv_cxx_exceptions=yes, ac_cv_cxx_exceptions=no) AC_LANG_RESTORE ]) if test "$ac_cv_cxx_exceptions" = yes; then AC_DEFINE(HAVE_EXCEPTIONS,,[define if the compiler supports exceptions]) fi ]) boxbackup/infrastructure/m4/ax_check_dirent_d_type.m40000664000175000017500000000301611165664221023636 0ustar siretartsiretartdnl @synopsis AX_CHECK_DIRENT_D_TYPE([ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl dnl This macro will find out if struct dirent.d_type is present and supported. dnl dnl The following defines will be set as appropriate: dnl HAVE_STRUCT_DIRENT_D_TYPE dnl HAVE_VALID_DIRENT_D_TYPE dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate dnl dnl @category C dnl @author Martin Ebourne dnl @version 2005/07/03 dnl @license AllPermissive AC_DEFUN([AX_CHECK_DIRENT_D_TYPE], [ AC_CHECK_MEMBERS([struct dirent.d_type],,, [[#include ]]) if test "x$ac_cv_member_struct_dirent_d_type" = "xyes"; then AC_CACHE_CHECK([[whether struct dirent.d_type is valid]], [box_cv_have_valid_dirent_d_type], [AC_TRY_RUN( [ $ac_includes_default #include int main() { DIR* dir = opendir("."); struct dirent* res = NULL; if(dir) res = readdir(dir); return res ? (res->d_type != DT_FILE && res->d_type != DT_DIR) : 1; } ], [box_cv_have_valid_dirent_d_type=yes], [box_cv_have_valid_dirent_d_type=no], [box_cv_have_valid_dirent_d_type=cross] )]) if test "x$box_cv_have_valid_dirent_d_type" = "xyes"; then AC_DEFINE([HAVE_VALID_DIRENT_D_TYPE], 1, [Define to 1 if struct dirent.d_type is valid]) fi fi if test "x$ac_cv_member_struct_dirent_d_type" = "xyes" || \ test "x$box_cv_have_valid_dirent_d_type" = "xyes" then m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ])dnl boxbackup/infrastructure/m4/ax_bswap64.m40000664000175000017500000000330511072137425021135 0ustar siretartsiretartdnl @synopsis AX_BSWAP64 dnl dnl This macro will check for a built in way of endian reversing an int64_t. dnl If one is found then HAVE_BSWAP64 is set to 1 and BSWAP64 will be defined dnl to the name of the endian swap function. dnl dnl @category C dnl @author Martin Ebourne dnl @version 2006/02/02 dnl @license AllPermissive AC_DEFUN([AX_BSWAP64], [ bswap64_function="" AC_CHECK_HEADERS([sys/endian.h asm/byteorder.h]) if test "x$ac_cv_header_sys_endian_h" = "xyes"; then AC_CACHE_CHECK([for htobe64], [box_cv_have_htobe64], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ $ac_includes_default #include ]], [[ htobe64(0); return 1; ]])], [box_cv_have_htobe64=yes], [box_cv_have_htobe64=no] )]) if test "x$box_cv_have_htobe64" = "xyes"; then bswap64_function=htobe64 fi fi if test "x$bswap64_function" = "x" && \ test "x$ac_cv_header_asm_byteorder_h" = "xyes"; then AC_CACHE_CHECK([for __cpu_to_be64], [box_cv_have___cpu_to_be64], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ $ac_includes_default #include ]], [[ __cpu_to_be64(0); return 1; ]])], [box_cv_have___cpu_to_be64=yes], [box_cv_have___cpu_to_be64=no] )]) if test "x$box_cv_have___cpu_to_be64" = "xyes"; then bswap64_function=__cpu_to_be64 fi fi if test "x$bswap64_function" != "x"; then AC_DEFINE([HAVE_BSWAP64], 1, [Define to 1 if BSWAP64 is defined to the name of a valid 64 bit endian swapping function]) AC_DEFINE_UNQUOTED([BSWAP64], [$bswap64_function], [Name of the 64 bit endian swapping function]) fi ])dnl boxbackup/infrastructure/m4/vl_lib_readline.m40000664000175000017500000001105110356613565022276 0ustar siretartsiretartdnl @synopsis VL_LIB_READLINE([ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl dnl Searches for a readline compatible library. If found, defines dnl `HAVE_LIBREADLINE'. If the found library has the `add_history' dnl function, sets also `HAVE_READLINE_HISTORY'. Also checks for the dnl locations of the necessary include files and sets `HAVE_READLINE_H' dnl or `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or dnl 'HAVE_HISTORY_H' if the corresponding include files exists. dnl dnl The libraries that may be readline compatible are `libedit', dnl `libeditline' and `libreadline'. Sometimes we need to link a dnl termcap library for readline to work, this macro tests these cases dnl too by trying to link with `libtermcap', `libcurses' or dnl `libncurses' before giving up. dnl dnl Here is an example of how to use the information provided by this dnl macro to perform the necessary includes or declarations in a C dnl file: dnl dnl #ifdef HAVE_LIBREADLINE dnl # if defined(HAVE_READLINE_READLINE_H) dnl # include dnl # elif defined(HAVE_READLINE_H) dnl # include dnl # else /* !defined(HAVE_READLINE_H) */ dnl extern char *readline (); dnl # endif /* !defined(HAVE_READLINE_H) */ dnl char *cmdline = NULL; dnl #else /* !defined(HAVE_READLINE_READLINE_H) */ dnl /* no readline */ dnl #endif /* HAVE_LIBREADLINE */ dnl dnl #ifdef HAVE_READLINE_HISTORY dnl # if defined(HAVE_READLINE_HISTORY_H) dnl # include dnl # elif defined(HAVE_HISTORY_H) dnl # include dnl # else /* !defined(HAVE_HISTORY_H) */ dnl extern void add_history (); dnl extern int write_history (); dnl extern int read_history (); dnl # endif /* defined(HAVE_READLINE_HISTORY_H) */ dnl /* no history */ dnl #endif /* HAVE_READLINE_HISTORY */ dnl dnl Modifications to add --enable-gnu-readline to work around licensing dnl problems between the traditional BSD licence and the GPL. dnl Martin Ebourne, 2005/7/11 dnl Rewrite to match headers with libraries and be more selective. dnl Martin Ebourne, 2006/1/4 dnl dnl @category InstalledPackages dnl @author Ville Laurikari dnl @version 2002-04-04 dnl @license AllPermissive AC_DEFUN([VL_LIB_READLINE], [ AC_ARG_ENABLE( [gnu-readline], AC_HELP_STRING([--enable-gnu-readline], [Use GNU readline if present (may violate GNU licence)]) ) vl_cv_lib_readline_compat_found=no if test "x$enable_gnu_readline" = "xyes"; then VL_LIB_READLINE_CHECK([readline], [readline], [readline/readline.h readline.h], [readline/history.h history.h]) fi if test "x$vl_cv_lib_readline_compat_found" = "xno"; then VL_LIB_READLINE_CHECK([editline], [edit editline], [editline/readline.h], [editline/readline.h]) fi if test "x$vl_cv_lib_readline_compat_found" = "xyes"; then m4_ifvaln([$1],[$1],[:])dnl m4_ifvaln([$2],[else $2])dnl fi ]) dnl VL_LIB_READLINE_CHECK(name, libraries, headers, history headers) AC_DEFUN([VL_LIB_READLINE_CHECK], [ AC_CACHE_CHECK([for $1 library], [vl_cv_lib_$1], [ ORIG_LIBS="$LIBS" vl_cv_lib_$1="" for readline_lib in $2; do for termcap_lib in "" termcap curses ncurses; do if test -z "$termcap_lib"; then TRY_LIB="-l$readline_lib" else TRY_LIB="-l$readline_lib -l$termcap_lib" fi LIBS="$ORIG_LIBS $TRY_LIB" AC_TRY_LINK_FUNC([readline], [vl_cv_lib_$1="$TRY_LIB"]) if test -n "$vl_cv_lib_$1"; then break fi done if test -n "$vl_cv_lib_$1"; then break fi done if test -z "$vl_cv_lib_$1"; then vl_cv_lib_$1=no LIBS="$ORIG_LIBS" fi ]) vl_cv_lib_readline_compat_found=no if test "x$vl_cv_lib_$1" != "xno"; then AC_CHECK_HEADERS([$3], [vl_cv_lib_readline_compat_found=yes]) fi if test "x$vl_cv_lib_readline_compat_found" = "xyes"; then AC_DEFINE([HAVE_LIBREADLINE], 1, [Define if you have a readline compatible library]) AC_CACHE_CHECK([whether $1 supports history], [vl_cv_lib_$1_history], [ vl_cv_lib_$1_history=no AC_TRY_LINK_FUNC([add_history], [vl_cv_lib_$1_history=yes]) ]) if test "x$vl_cv_lib_$1_history" = "xyes"; then vl_cv_lib_$1_history=no AC_CHECK_HEADERS( [$4], [AC_DEFINE([HAVE_READLINE_HISTORY], [1], [Define if your readline library has add_history])]) fi else LIBS="$ORIG_LIBS" fi ])dnl boxbackup/infrastructure/m4/ac_cxx_namespaces.m40000664000175000017500000000135510347400657022632 0ustar siretartsiretartdnl @synopsis AC_CXX_NAMESPACES dnl dnl If the compiler can prevent names clashes using namespaces, define dnl HAVE_NAMESPACES. dnl dnl @category Cxx dnl @author Todd Veldhuizen dnl @author Luc Maisonobe dnl @version 2004-02-04 dnl @license AllPermissive AC_DEFUN([AC_CXX_NAMESPACES], [AC_CACHE_CHECK(whether the compiler implements namespaces, ac_cv_cxx_namespaces, [AC_LANG_SAVE AC_LANG_CPLUSPLUS AC_TRY_COMPILE([namespace Outer { namespace Inner { int i = 0; }}], [using namespace Outer::Inner; return i;], ac_cv_cxx_namespaces=yes, ac_cv_cxx_namespaces=no) AC_LANG_RESTORE ]) if test "$ac_cv_cxx_namespaces" = yes; then AC_DEFINE(HAVE_NAMESPACES,,[define if the compiler implements namespaces]) fi ]) boxbackup/infrastructure/makebuildenv.pl.in0000775000175000017500000004666311512150162022016 0ustar siretartsiretart#!@PERL@ use strict; use Symbol; my @modules; my %module_dependency; my %module_library_link_opts; my %header_dependency; $|=1; # note: Mac OS X resource forks and .DS_Store files are explicity ignored print "Box build environment setup.\n"; my @implicit_deps = ('lib/common'); # work out platform variables use lib 'infrastructure'; use BoxPlatform; print "Building on '$build_os'.\n\n"; # keep copy of command line args my $makebuildenv_args = join(' ',@ARGV); # do command line arguments my $compile_line_extra = $platform_compile_line_extra; my $link_line_extra = $platform_link_line_extra; # make sure local files directory exists unless(-d 'local') { mkdir 'local',0755; } # flags about the environment my %env_flags; $module_dependency{"lib/common"} = ["lib/win32"]; push @implicit_deps, "lib/win32"; # print "Flag: $_\n" for(keys %env_flags); # seed autogen code print "Seeding autogen code...\n"; open FINDAUTOGEN,"find . -follow -name Makefile.extra |" or die "Can't use find for locating files"; while() { chomp; my $file = $_; $file =~ m~\A(.+)/[^/]+\Z~; my $dir = $1; open FL,$file or die "Can't open $_ for reading"; my %vars; $vars{_PERL} = "@PERL@"; my $do_cmds = 0; while() { chomp; if(m/\A(.+)\s+=\s+(.+)\Z/) { # is a variable $vars{$1} = $2; next; } next unless m/\S/; if(m/AUTOGEN SEEDING/) { $do_cmds = 1; } elsif(m/\A\S/) { $do_cmds = 0 if $do_cmds == 2; } else { # command, run it? if($do_cmds) { $do_cmds = 2; # flag something has been done # subsitute variables, repeatedly my $c = $_; $c =~ s/\A\s+//; while(1) { my $did_subst = 0; for my $k (keys %vars) { $did_subst = 1 if $c =~ s/\$\($k\)/$vars{$k}/g; } last unless $did_subst; } # run command unless (0 == system("(cd $dir; $c)")) { die "Couldn't run command $c " . "(in $dir) for $file"; } } } } close FL; } close FINDAUTOGEN; print "done\n\n"; # open test mail program template file my $test_template_file = 'infrastructure/buildenv-testmain-template.cpp'; open FL,$test_template_file or die "Can't open test template file\n"; my $test_template; read FL,$test_template,-s $test_template_file; close FL; # extra platform defines my $extra_platform_defines = ''; # read in module definitions file, and any files it includes my @modules_files; sub read_modules_file { my ($mf) = @_; my $f = gensym; open $f,$mf or die "Can't open modules file '$mf'\n"; while(<$f>) { if(m/\AINCLUDE\s+(\S+)\Z/) { # include another file read_modules_file($1) } else { push @modules_files,$_ } } close $f; } read_modules_file('modules.txt'); # prepare directories... mkdir "release",0755; mkdir "debug",0755; # is the library code in another directory? my $external_lib = readlink('lib'); if($external_lib ne '') { # adjust to root of the library distribution $external_lib =~ s!/lib\Z!!; $external_lib = '../'.$external_lib; # make symlinks make_obj_symlink('debug'); make_obj_symlink('release'); } sub make_obj_symlink { my $m = $_[0]; my $target = $external_lib."/$m/lib/"; my $link = "$m/lib"; # check link if(-e $link) { if(-l $link) { if(readlink($link) ne $target) { print "Warning: replacing $link with new link to $target\n"; unlink $link; } } else { die "$link already exists, but it isn't a symbolic link" } } if(!-e $link) { symlink $target,$link or die "Can't make $m/lib symlink"; } } print "Scanning code...\n"; my $modules_omitted = 0; my $modules_omitting = 0; # process lines in flattened modules files for(@modules_files) { # clean up line chomp; s/\A\s+//; s/#.*\Z//; s/\s+\Z//; s/\s+/ /g; next unless m/\S/; # omit bits on some platforms? if(m/\AEND-OMIT/) { $modules_omitting = 0; next; } next if $modules_omitting; if(m/\AOMIT:(.+)/) { if($1 eq $build_os or $1 eq $target_os) { $modules_omitted = 1; $modules_omitting = 1; } next; } # split up... my ($mod, @deps_i) = split / /; # ignore this module? next if ignore_module($mod); # deps for this platform my @deps; for(@deps_i) { my ($dep,$exclude_from) = split /!/; # generic library translation $dep = $env_flags{'LIBTRANS_'.$dep} if exists($env_flags{'LIBTRANS_'.$dep}); next if $dep eq ''; if($exclude_from =~ m/\A\+(.+)\Z/) { $exclude_from = $1; my $inc = 0; for(split /,/,$exclude_from) { $inc = 1 if $_ eq $build_os } push @deps,$dep if $inc } else { my $inc = 1; for(split /,/,$exclude_from) { $inc = 0 if $_ eq $build_os } push @deps,$dep if $inc } } # check directory exists die "Module $mod can't be found\n" unless -d $mod; # and put in lists push @modules,$mod; my @md; # module dependencies my @lo; # link line options for(@deps) { if(/\A-l/) { push @lo,$_ } else { push @md,$_ unless ignore_module($_) } } $module_dependency{$mod} = [@implicit_deps,@md]; $module_library_link_opts{$mod} = [@lo]; # make directories, but not if we're using an external library and this a library module my ($s,$d) = split /\//,$mod; if($s ne 'lib' || $external_lib eq '') { mkdir "release/$s",0755; mkdir "release/$s/$d",0755; mkdir "debug/$s",0755; mkdir "debug/$s/$d",0755; } } # make dirs for implicit dep foreach my $dep (@implicit_deps) { mkdir "release/$dep",0755; mkdir "debug/$dep",0755; } # write a list of all the modules we've configured to use open CONFIGURED_MODS,'>local/modules.h' or die "Can't write configured modules list"; print CONFIGURED_MODS <<__E; // automatically generated file, do not edit #ifndef _CONFIGURED_MODULES__H #define _CONFIGURED_MODULES__H __E for(@implicit_deps,@modules) { my $m = $_; $m =~ s~/~_~; print CONFIGURED_MODS "#define MODULE_$m\n"; } print CONFIGURED_MODS <<__E; #endif // _CONFIGURED_MODULES__H __E close CONFIGURED_MODS; # now make a list of all the .h files we can find, recording which module they're in my %hfiles; for my $mod (@modules, @implicit_deps) { opendir DIR,$mod; my @items = readdir DIR; closedir DIR; # add in items from autogen directories, and create output directories { my @autogen_items; for my $di (@items) { if($di =~ m/\Aautogen/ && -d "$mod/$di") { # Read items my $d = "$mod/$di"; opendir DIR,$d; my @i = readdir DIR; closedir DIR; for(@i) { next if m/\A\./; push @autogen_items,"$di/$_" } } } @items = (@items, @autogen_items); } for(grep /\.h\Z/i, @items) { next if /\A\._/; # Temp Mac OS Resource hack die "Header file $_ already used in module ".$hfiles{$_}."\n" if exists $hfiles{$_}; $hfiles{$_} = $mod } } for my $mod (@modules, @implicit_deps) { opendir DIR,$mod; for my $h (grep /\.h\Z/i, readdir DIR) { next if $h =~ /\A\./; # Ignore Mac resource forks, autosaves, etc open FL,"$mod/$h" or die "can't open $mod/$h"; my $f; read FL,$f,-s "$mod/$h"; close FL; while($f =~ m/\#include\s+"([^"]+?)"/g) { my $i = $1; # ignore autogen exceptions next if $i =~ m/\Aautogen_.+?Exception.h\Z/; # record dependency ${$header_dependency{$h}}{$i} = 1 if exists $hfiles{$i}; } } closedir DIR; } print "done\n\nGenerating Makefiles...\n"; my %module_resources_win32; # Then write a makefile for each module for my $mod (@implicit_deps, @modules) { print $mod,"\n"; my ($type,$name) = split /\//,$mod; # add additional files for tests if($type eq 'test') { my $testmain = $test_template; $testmain =~ s/TEST_NAME/$name/g; open TESTMAIN,">$mod/_main.cpp" or die "Can't open test main file for $mod for writing\n"; print TESTMAIN $testmain; close TESTMAIN; # test file... sub writetestfile { my ($filename,$runcmd,$module) = @_; open TESTFILE,">$filename" or die "Can't open " . "test script file for $module for writing\n"; print TESTFILE "#!/bin/sh\necho TEST: $module\n"; if ($target_windows) { print TESTFILE <<__E; kill_process() { if test -r testfiles/\$1.pid; then /bin/kill -0 -f `cat testfiles/\$1.pid` \\ && /bin/kill -f `cat testfiles/\$1.pid` rm testfiles/\$1.pid fi } __E } else { print TESTFILE <<__E; kill_process() { test -r testfiles/\$1.pid \\ && kill -0 `cat testfiles/\$1.pid` \\ && kill `cat testfiles/\$1.pid` } __E } if (-d "$module/testfiles") { print TESTFILE <<__E; kill_daemons() { kill_process bbackupd kill_process bbstored kill_process httpserver kill_process s3simulator } echo Killing any running daemons... kill_daemons __E } print TESTFILE <<__E; echo Removing old test files... chmod -R a+rwx testfiles rm -rf testfiles echo Copying new test files... cp -p -R ../../../$module/testfiles . __E if (-e "$module/testextra") { open FL,"$module/testextra" or die "Can't open $module/testextra"; while() {print TESTFILE} close FL; } print TESTFILE "$runcmd\n"; if (-d "$module/testfiles") { print TESTFILE <<__E; kill_daemons __E } close TESTFILE; } writetestfile("$mod/_t", "GLIBCXX_FORCE_NEW=1 ". './test' . $platform_exe_ext . ' "$@"', $mod); writetestfile("$mod/_t-gdb", "GLIBCXX_FORCE_NEW=1 ". 'gdb ./test' . $platform_exe_ext . ' "$@"', $mod); } my @all_deps_for_module; { # work out what dependencies need to be run my @deps_raw; sub add_mod_deps { my ($arr_r,$nm) = @_; if($#{$module_dependency{$nm}} >= 0) { push @$arr_r,@{$module_dependency{$nm}}; for(@{$module_dependency{$nm}}) { add_mod_deps($arr_r,$_) } } } add_mod_deps(\@deps_raw, $mod); # and then dedup and reorder them my %d_done; foreach my $dep (reverse @deps_raw) { if(!exists $d_done{$dep}) { # insert push @all_deps_for_module, $dep; # mark as done $d_done{$dep} = 1; } } } # make include path my $include_paths = join(' ',map {'-I../../'.$_} @all_deps_for_module); # is target a library? my $target_is_library = ($type ne 'bin' && $type ne 'test'); # make target name my $end_target = $name; if ($target_is_library) { $end_target .= '.a'; } else { $end_target .= $platform_exe_ext; } $end_target = 'test'.$platform_exe_ext if $type eq 'test'; # adjust for outdir $end_target = '$(OUTDIR)/' . $end_target; # start the makefile my $mk_name_extra = ($bsd_make)?'':'X'; open MAKE,">$mod/Makefile".$mk_name_extra or die "Can't open Makefile for $mod\n"; my $debug_link_extra = ($target_is_library)?'':'../../debug/lib/debug/debug.a'; my $default_cxxflags = '@CXXFLAGS@'; $default_cxxflags =~ s/ -O2//g; my $release_flags = "-O2"; if ($target_windows) { $release_flags = "-O0 -g"; } print MAKE <<__E; # # AUTOMATICALLY GENERATED FILE # do not edit! # # CXX = @CXX@ AR = @AR@ RANLIB = @RANLIB@ PERL = @PERL@ WINDRES = @WINDRES@ DEFAULT_CXXFLAGS = @CPPFLAGS@ $default_cxxflags @CXXFLAGS_STRICT@ \\ $include_paths $extra_platform_defines \\ -DBOX_VERSION="\\"$product_version\\"" LDFLAGS = @LDFLAGS@ @LDADD_RDYNAMIC@ .ifdef RELEASE CXXFLAGS = -DBOX_RELEASE_BUILD $release_flags \$(DEFAULT_CXXFLAGS) OUTBASE = ../../release OUTDIR = ../../release/$mod DEPENDMAKEFLAGS = -D RELEASE VARIENT = RELEASE .else CXXFLAGS = -g \$(DEFAULT_CXXFLAGS) OUTBASE = ../../debug OUTDIR = ../../debug/$mod DEPENDMAKEFLAGS = VARIENT = DEBUG .endif __E if ($bsd_make) { print MAKE <<__E; .ifdef V HIDE = _CXX = \$(CXX) _LINK = \$(CXX) _WINDRES = \$(WINDRES) _AR = \$(AR) _RANLIB = \$(RANLIB) _PERL = \$(PERL) .else HIDE = @ _CXX = @ echo " [CXX] " \$(*F) && \$(CXX) _LINK = @ echo " [LINK] " \$(*F) && \$(CXX) _WINDRES = @ echo " [WINDRES]" \$(*F) && \$(WINDRES) _AR = @ echo " [AR] " \$(*F) && \$(AR) _RANLIB = @ echo " [RANLIB] " \$(*F) && \$(RANLIB) _PERL = @ echo " [PERL] " \$(*F) && \$(PERL) >/dev/null .endif __E } else { print MAKE <<__E; HIDE = \$(if \$(V),,@) _CXX = \$(if \$(V),\$(CXX), @ echo " [CXX] \$<" && \$(CXX)) _LINK = \$(if \$(V),\$(CXX), @ echo " [LINK] \$@" && \$(CXX)) _WINDRES = \$(if \$(V),\$(WINDRES), @ echo " [WINDRES] \$<" && \$(WINDRES)) _AR = \$(if \$(V),\$(AR), @ echo " [AR] \$@" && \$(AR)) _RANLIB = \$(if \$(V),\$(RANLIB), @ echo " [RANLIB] \$@" && \$(RANLIB)) _PERL = \$(if \$(V),\$(PERL), @ echo " [PERL] \$@" && \$(PERL) >/dev/null) __E } # read directory opendir DIR,$mod; my @items = readdir DIR; closedir DIR; # add in items from autogen directories, and create output directories { my @autogen_items; for my $di (@items) { if($di =~ m/\Aautogen/ && -d "$mod/$di") { # Read items my $d = "$mod/$di"; opendir DIR,$d; my @i = readdir DIR; closedir DIR; for(@i) { next if m/\A\./; push @autogen_items,"$di/$_" } # output directories mkdir "release/$mod/$di",0755; mkdir "debug/$mod/$di",0755; } } @items = (@items, @autogen_items); } # first, obtain a list of dependencies within the .h files my %headers; for my $h (grep /\.h\Z/i, @items) { open FL,"$mod/$h"; my $f; read FL,$f,-s "$mod/$h"; close FL; while($f =~ m/\#include\s+"([^"]+?)"/g) { ${$headers{$h}}{$1} = 1 if exists $hfiles{$1}; } } # ready for the rest of the details... my $make; # then... do the cpp files... my @obj_base; for my $file (@items) { my $is_cpp = $file =~ m/\A(.+)\.cpp\Z/i; my $is_rc = $file =~ m/\A(.+)\.rc\Z/i; my $base = $1; if ($target_windows) { next if not $is_cpp and not $is_rc; } else { next if not $is_cpp; } next if $file =~ /\A\._/; # Temp Mac OS Resource hack # store for later push @obj_base,$base; # get the file... open FL,"$mod/$file"; my $f; read FL,$f,-s "$mod/$file"; close FL; my %dep; while($f =~ m/\#include\s+"([^"]+?)"/g) { insert_dep($1, \%dep) if exists $hfiles{$1}; } # output filename my $out_name = '$(OUTDIR)/'.$base.'.o'; # write the line for this cpp file my @dep_paths = map { ($hfiles{$_} eq $mod) ? $_ : '../../'.$hfiles{$_}."/$_" } keys %dep; $make .= $out_name.': '.join(' ',$file,@dep_paths)."\n"; if ($is_cpp) { $make .= "\t\$(_CXX) \$(CXXFLAGS) $compile_line_extra ". "-DBOX_MODULE=\"\\\"$mod\\\"\" " . "-c $file -o $out_name\n\n"; } elsif ($is_rc) { $make .= "\t\$(_WINDRES) $file $out_name\n\n"; my $res_list = $module_resources_win32{$mod}; $res_list ||= []; push @$res_list, $base.'.o'; $module_resources_win32{$mod} = $res_list; } } my $has_deps = ($#{$module_dependency{$mod}} >= 0); # ----- # always has dependencies with debug library $has_deps = 1; $has_deps = 0 if $target_is_library; # Depenency stuff my $deps_makeinfo; if($has_deps) { if($bsd_make) { $deps_makeinfo = <<'__E'; .BEGIN:: .ifndef NODEPS . if $(.TARGETS) == "" __E } else { # gnu make $deps_makeinfo = <<'__E'; .PHONY: dep_modules dep_modules: ifndef NODEPS ifeq ($(strip $(.TARGETS)),) __E } # run make for things we require for my $dep (@all_deps_for_module) { $deps_makeinfo .= "\t\t\$(HIDE) (cd ../../$dep; \$(MAKE)$sub_make_options -q \$(DEPENDMAKEFLAGS) -D NODEPS || \$(MAKE)$sub_make_options \$(DEPENDMAKEFLAGS) -D NODEPS)\n"; } $deps_makeinfo .= ".\tendif\n.endif\n\n"; } print MAKE $deps_makeinfo if $bsd_make; # get the list of library things to add -- in order of dependency so things link properly my $lib_files = join(' ',map {($_ =~ m/lib\/(.+)\Z/)?('$(OUTBASE)/'.$_.'/'.$1.'.a'):undef} (reverse(@all_deps_for_module))); # need to see if the extra makefile fragments require extra object files # or include any more makefiles my @objs = @obj_base; my @makefile_includes; additional_objects_from_make_fragment("$mod/Makefile.extra", \@objs, \@makefile_includes); additional_objects_from_make_fragment("$mod/Makefile.extra.$build_os", \@objs, \@makefile_includes); my $o_file_list = join(' ',map {'$(OUTDIR)/'.$_.'.o'} sort @objs); if ($has_deps and not $bsd_make) { print MAKE ".PHONY: all\n" . "all: dep_modules $end_target\n\n"; } print MAKE $end_target,': ',$o_file_list; print MAKE " ",$lib_files unless $target_is_library; print MAKE "\n"; if ($target_windows) { foreach my $dep (@all_deps_for_module) { my $res_list = $module_resources_win32{$dep}; next unless $res_list; $o_file_list .= ' '.join(' ', map {'$(OUTBASE)/'.$dep."/$_"} @$res_list); } } # stuff to make the final target... if($target_is_library) { # make a library archive... print MAKE "\t\$(HIDE) (echo -n > $end_target; rm $end_target)\n"; print MAKE "\t\$(_AR) cq $end_target $o_file_list\n"; print MAKE "\t\$(_RANLIB) $end_target\n"; } else { # work out library options # need to be... least used first, in absolute order they appear in the modules.txt file my @libops; sub libops_fill { my ($m,$r) = @_; push @$r,$_ for(@{$module_library_link_opts{$m}}); libops_fill($_,$r) for(@{$module_dependency{$m}}); } libops_fill($mod,\@libops); my $lo = ''; my %ldone; for(@libops) { next if exists $ldone{$_}; $lo .= ' '.$_; $ldone{$_} = 1; } # link line... print MAKE "\t\$(_LINK) \$(LDFLAGS) $link_line_extra " . "-o $end_target $o_file_list " . "$lib_files$lo $platform_lib_files\n"; } # tests need to copy the test file over if($type eq 'test') { print MAKE "\tcp _t \$(OUTDIR)/t\n\tchmod u+x \$(OUTDIR)/t\n"; print MAKE "\tcp _t-gdb \$(OUTDIR)/t-gdb\n\tchmod u+x \$(OUTDIR)/t-gdb\n"; } # dependency line? print MAKE "\n"; # module dependencies for GNU make? print MAKE $deps_makeinfo if !$bsd_make; # print the rest of the file print MAKE $make,"\n"; # and a clean target print MAKE <\n\n"; } if(-e "$mod/Makefile.extra.$build_os") { print MAKE ".include \n\n"; } for(@makefile_includes) { print MAKE ".include <$_>\n\n"; } # and finally a target for rebuilding the build system print MAKE "\nbuildsystem:\n\t(cd ../..; perl ./infrastructure/makebuildenv.pl $makebuildenv_args)\n\n"; close MAKE; if(!$bsd_make) { # need to post process this into a GNU makefile open MAKE,">$mod/Makefile"; open MAKEB,"$mod/MakefileX"; while() { s/\A\.\s*(ifdef|else|endif|ifndef)/$1/; s/\A\.\s*include\s+<(.+?)>/include $1/; s/-D\s+(\w+)/$1=1/g; print MAKE; } close MAKEB; close MAKE; unlink "$mod/MakefileX"; } } print "\nType 'cd ; $make_command' to build a module\n\n"; if($modules_omitted) { print "\nNOTE: Some modules have been omitted on this platform\n\n" } sub insert_dep { my ($h,$dep_r) = @_; # stop random recusion return if exists $$dep_r{$h}; # insert more depencies insert_dep($_,$dep_r) for keys %{$header_dependency{$h}}; # mark this one as a dependency $$dep_r{$h} = 1; } sub additional_objects_from_make_fragment { my ($fn,$objs_r,$include_r) = @_; if(-e $fn) { open FL,$fn or die "Can't open $fn"; while() { chomp; if(m/link-extra:\s*(.+)\Z/) { my $extra = $1; do { my @o = split /\s+/, $extra; for(@o) { push @$objs_r,$1 if m/\A(.+)\.o\Z/; } last unless $extra =~ m'\\$'; $extra = ; } while(1); } elsif(m/include-makefile:\s*(\S+)/) { push @$include_r,$1 } } close FL; } } sub ignore_module { exists $env_flags{'IGNORE_'.$_[0]} } boxbackup/infrastructure/printversion.pl0000664000175000017500000000034711072155616021501 0ustar siretartsiretart#!perl $basedir = $0; $basedir =~ s|/.*||; $basedir .= "/.."; -d $basedir or die "$basedir: $!"; chdir $basedir or die "$basedir: $!"; require "infrastructure/BoxPlatform.pm.in"; print "$BoxPlatform::product_version\n"; exit 0; boxbackup/infrastructure/BoxPlatform.pm.in0000664000175000017500000000601211256002004021557 0ustar siretartsiretartpackage BoxPlatform; use Exporter; @ISA = qw/Exporter/; @EXPORT = qw/$build_os $target_os $make_command $bsd_make $platform_define $platform_cpu $gcc_v3 $product_version $product_name $install_into_dir $sub_make_options $platform_compile_line_extra $platform_link_line_extra $platform_lib_files $platform_exe_ext $target_windows/; BEGIN { # which OS are we building under? $target_os = '@target_os@'; $target_windows = 0; $target_windows = 1 if $target_os =~ m'^mingw32' or $target_os eq "winnt"; if ($^O eq "MSWin32" and not -x "/usr/bin/uname") { $build_os = "winnt"; } else { $build_os = `uname`; chomp $build_os; } # Cygwin Builds usually something like CYGWIN_NT-5.0, CYGWIN_NT-5.1 # Box Backup tried on Win2000,XP only :) $build_os = 'CYGWIN' if $build_os =~ m/CYGWIN/; $make_command = ($build_os eq 'Darwin') ? 'bsdmake' : ($build_os eq 'SunOS') ? 'gmake' : 'make'; $bsd_make = ($build_os ne 'Linux' && $build_os ne 'CYGWIN' && $build_os ne "SunOS" && $build_os ne 'GNU/kFreeBSD'); # blank extra flags by default $platform_compile_line_extra = ''; $platform_link_line_extra = ''; $platform_lib_files = '@LIBS@'; $platform_exe_ext = '@EXEEXT@'; # get version my $version_file = "VERSION.txt"; if (not -r $version_file) { $version_file = "../../$version_file" } die "missing version file: $version_file" unless $version_file; open VERSION, $version_file or die "$version_file: $!"; $product_version = ; chomp $product_version; $product_name = ; chomp $product_name; close VERSION; if($product_version =~ /USE_SVN_VERSION/) { # for developers, use SVN version my $svnversion = `svnversion .`; chomp $svnversion; $svnversion =~ tr/0-9A-Za-z/_/c; open INFO,'svn info . |'; my $svnurl; while() { if(m/^URL: (.+?)[\n\r]+/) { $svnurl = $1 } } close INFO; my $svndir; if ($svnurl =~ m!/box/(.+)$!) { $svndir = $1; } elsif ($svnurl =~ m'/(boxi/.+)/boxi/boxbackup') { $svndir = $1; } $svndir =~ tr/0-9A-Za-z/_/c; $product_version =~ s/USE_SVN_VERSION/$svndir.'_'.$svnversion/e; } # where to put the files $install_into_dir = '@sbindir_expanded@'; # if it's Darwin, if($build_os eq 'Darwin') { # see how many processors there are, and set make flags accordingly my $cpus = `sysctl hw.ncpu`; if($cpus =~ m/hw.ncpu:\s(\d+)/ && $1 > 1) { print STDERR "$1 processors detected, will set make to perform concurrent jobs\n"; $sub_make_options = ' -j '.($1 + 1); } # test for fink installation if(-d '/sw/include' && -d '/sw/lib') { print STDERR "Fink installation detected, will use headers and libraries\n"; $platform_compile_line_extra = '-I/sw/include '; $platform_link_line_extra = '-L/sw/lib '; } } } sub make_flag { if($bsd_make) { return "-D $_[0]" } return $_[0].'=1'; } sub parcel_root { my $tos = $_[1] || $target_os; return $product_name.'-'.$product_version.'-'.$_[0].'-'.$tos; } sub parcel_dir { 'parcels/'.parcel_root($_[0], $_[1]) } sub parcel_target { parcel_dir($_[0]).'.tgz' } 1; boxbackup/infrastructure/msvc/0000775000175000017500000000000011652362372017351 5ustar siretartsiretartboxbackup/infrastructure/msvc/2005/0000775000175000017500000000000011652362373017740 5ustar siretartsiretartboxbackup/infrastructure/msvc/2005/boxbackup.sln0000664000175000017500000000636210374104755022441 0ustar siretartsiretartMicrosoft Visual Studio Solution File, Format Version 9.00 # Visual C++ Express 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "boxquery", "boxquery.vcproj", "{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common.vcproj", "{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupd", "bbackupd.vcproj", "{22D325FB-9131-4BD6-B390-968F0491D687}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win32test", "win32test.vcproj", "{28C29E72-76A2-4D0C-B35B-12D446733D2E}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupctl", "bbackupctl.vcproj", "{9FD51412-E945-4457-A17A-CA3C505CF431}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug|Win32.ActiveCfg = Debug|Win32 {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug|Win32.Build.0 = Debug|Win32 {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release|Win32.ActiveCfg = Release|Win32 {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release|Win32.Build.0 = Release|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug|Win32.ActiveCfg = Debug|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug|Win32.Build.0 = Debug|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release|Win32.ActiveCfg = Release|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release|Win32.Build.0 = Release|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Debug|Win32.ActiveCfg = Debug|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Debug|Win32.Build.0 = Debug|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Release|Win32.ActiveCfg = Release|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Release|Win32.Build.0 = Release|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug|Win32.ActiveCfg = Debug|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug|Win32.Build.0 = Debug|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release|Win32.ActiveCfg = Release|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release|Win32.Build.0 = Release|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug|Win32.ActiveCfg = Debug|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug|Win32.Build.0 = Debug|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Release|Win32.ActiveCfg = Release|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal boxbackup/infrastructure/msvc/2005/win32test.vcproj0000664000175000017500000001425511126433077023032 0ustar siretartsiretart boxbackup/infrastructure/msvc/2005/boxquery.vcproj0000664000175000017500000001521111126433077023037 0ustar siretartsiretart boxbackup/infrastructure/msvc/2005/bbackupctl.vcproj0000664000175000017500000001421611126433077023277 0ustar siretartsiretart boxbackup/infrastructure/msvc/2005/common.vcproj0000664000175000017500000005252111126433077022456 0ustar siretartsiretart boxbackup/infrastructure/msvc/2005/boxbackup.suo0000664000175000017500000016300010374104755022444 0ustar siretartsiretartÐÏࡱá>þÿ þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿ  *! #+"$%&'(),B1-./02:3456789;@<=>?AICUDEFGHJOKLMNP\QRSTVhWXYZ[]f^_`abcdegoiþÿÿÿjklmnpþÿÿÿqþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRoot Entryÿÿÿÿÿÿÿÿ€TlH)ÆÀ®ProjInfoExÿÿÿÿÿÿÿÿÿÿÿÿTaskListUserTasks$ÿÿÿÿÿÿÿÿIVSMDPropertyBrowser*ÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿþÿÿÿþÿÿÿþÿÿÿþÿÿÿþÿÿÿ þÿÿÿþÿÿÿ¢þÿÿÿ !"#$%&'()*+,-./01234þÿÿÿþÿÿÿ78¡:;<=>?@A–þÿÿÿþÿÿÿþÿÿÿGHIJKLMNOPQRþÿÿÿTUVWXYZ[\þÿÿÿ^_`abcdefgh~jklmnopqrþÿÿÿtuvwxyz{|ƒ€í*B}5˜˜A—I‹Yôɤ¨ðC Data XML Schema Dialog EditorMobile Web Forms Web Forms Components Windows FormsHTMLClipboard RingGeneraltimeinfo$mModificC:\Projects\boxbuild\box-wor€C:\Program Files\MicrosoIToolboxService *ÿÿÿÿšDebuggerWatches ÿÿÿÿÿÿÿÿÿÿÿÿDebuggerBreakpoints(J ÿÿÿÿ}¤DebuggerExceptions&ÿÿÿÿÿÿÿÿÿÿÿÿft Visual Studio .NET 2003\Vc7\crt\src\ŽC:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\src\mfc\ŽC:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\src\atl\Fi0x003B3170bufferDebuggerFindSource& ÿÿÿÿ¼DebuggerFindSymbol&ÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿDebuggerMemoryWindows,ÿÿÿÿxExternalFilesProjectContents: ÿÿÿÿÿÿÿÿþÿÿÿd6 ÿÿxÈl[È/"[`Í«4ïþîÿes\Microsoft VisuaMultiStartupProj=;4{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.dwStartupOpt=;?{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release|Win32.fBatchBld=;={FE9EC666-4B3A-4370-B3D4-DEBD4A21F36EDocumentWindowPositions0ÿÿÿÿÿÿÿÿÿÿÿÿ—DocumentWindowUserData. ÿÿÿÿSolutionConfiguration,ÿÿÿÿÿÿÿÿÿÿÿÿ’ObjMgrContents ÿÿÿÿ6Õ}.Debug|Win32.fBatchBld=;4{A089CEE6-EBF0-4232-A0C0-74850A8127A6}.dwStartupOpt=;?{A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release|Win32.fBatchBld=;={A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug|Win32.fBatchBld=;4{22D325FB-9131-4BD6-B390-968F0491D687}.dwStartupOpt=;StartupProject=&{22D325FB-9131-4BD6-B390-968F0491D687};?{22D325FB-9131-4BD6-B390-968F0491D687}.Release|Win32.fBatchBld=;={22D325FB-9131-4BD6-B390-968F0491D687}.Debug|Win32.fBatchBld=;4{28C29E72-76A2-4D0C-B35B-12D446733D2E}.dwStartupOpt=;?{28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release|Win32.fBatchBld=;={28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug|Win32.fBatchBld=;4{9FD51412-E945-4457-A17A-CA3C505CF431}.dwStartupOpt=;?{9FD51412-E945-4457-A17A-CA3C505CF431}.Release|Win32.fBatchBld=;={9FD51412-E945-4457-A17A-CA3C505CF431}.Debug|Win32.fBatchBld=; ActiveCfg= Debug|Win32;2;2315A214F}.dwStartupOt=;?{98598F62-FEA7-4134-AANSܾï MŠ%˜¿Ÿøç%Ò¯##G¶åá}'bm4Élü #Oÿ‡øÏ¤Ewin32testÓbbackupd[boost_regex‹boxqueryScommonÃbQ ºC:\cygwin\home\Administrator\vc2005-compile-fixes\infrastructure\msvc\2005\bbackupctl.vcproj¶C:\cygwin\home\Administrator\vc2005-compile-fixes\infrastructure\msvc\2005\bbackupd.vcproj¶C:\cygwin\home\Administrator\ClassViewContents$ÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿProjExplorerState$ÿÿÿÿ9¾UnloadedProjects"ÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿboxqueryÿÿÿÿÿÿÿÿÿÿÿÿF,vc2005-compile-fixes\infrastructure\msvc\2005\boxquery.vcprojvc:\cygwin\home\Administra$Bookmarks V001.012\boost_regeXÏ projNC:\Projects\svn\boxookmarks V001.01NC:\ProjecDebug|Win32DebugSettingsô.õ.Y:\ö.,-c "Y:\bbackupd.conf" ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiesRelease|Win32DebugSettingsô.õ.y:\ö.,-c "Y:\bbackupd.conf" ø.÷.ù.ú.(c:\box\boxquery.exeû.toutatisü.ý."$(TargetDir)" ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiesackupd.conDebug|Win32DebugSettingsô.õ.ö. ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiesRelease|Win32DebugSettingsô.õ.ö. ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigProperties,GeneralCDebug|Win32DebugSettingsô.õ.8C:\Program Files\Box Backupö."-c bbackupd.confcommonÿÿÿÿÿÿÿÿÿÿÿÿSlbbackupdÿÿÿÿ]win32testÿÿÿÿitbbackupctlÿÿÿÿÿÿÿÿsÔ ø.÷.ù.ú.Rc:\program files\box backup\bbackupd.exeû.toutatisü.ý. ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiesRelease|Win32DebugSettingsô.4$(TargetDir)\bbackupd.exeõ.8C:\Program Files\Box Backupö."-c bbackupd.conf ø.÷.ù.Debug|Win32DebugSettingsô.õ.ö. ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSettings ßÿÿVCBscMakeTool(EndConfigPropertiesRelease|Win32DebugSettingsô.õ.ö. ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiescMakeTDebug|Win32DebugSettingsô.õ.ö.h-c "C:\Program Files\Box Backup\bbackupd.conf" sync ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiesRelease|Win32DebugSettingsô.õ.ö. ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSetting˜C:\cygwin\home\Administratorú.(c:\box\bbackupd.exeû.toutatisü.ý."$(TargetDir‚þÿÿÿ„þÿÿÿ†‡ˆ‰Š‹ŒŽþÿÿÿ‘’“”•þÿÿÿ—˜žþÿÿÿ›œþÿÿÿþÿÿÿŸ þÿÿÿþÿÿÿ£²¥¦§¨©ª«±­®¯°þÿÿÿþÿÿÿ³´µþÿÿÿ·¸¹º»¼½¾¿ÀÁÂèÄÅÆÇÈÉÊËÌÍþÿÿÿÏÐÑÒÓÔÕÖרÙÚÛÜÝþÿÿÿßàáâãäåæçþÿÿÿéêëìíîïðñòóôþÿÿÿö÷øùúþÿÿÿüýþÿ)" ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiesce Files"Source FilessVCBscMakeTool(EndConfigPropertiesngsVCBscMakeToolDebug|Win32DebugSettingsô.õ.ö. ø.÷.ù.ú.û.ü.ý. ÿ.,GenTaskListShortcuts$ÿÿÿÿÿÿÿÿÿÿÿÿDResEdit.opt4ÿÿÿÿþÿÿÿIVsServerExplorer$D ÿÿÿÿþÿÿÿboost_regexÿÿÿÿÿÿÿÿ…leralConfigSettingsVCBscMakeTool(EndConfigPropertiesRelease|Win32DebugSettingsô.õ.ö. ø.÷.ù.ú.û.ü.ý. ÿ.,GeneralConfigSettingsVCBscMakeTool(EndConfigPropertiesø.÷.ù.\vc2005-compile-fixes\infrastruc²C:\cygwin\home\Administrator\vc2005-compile-fixes\infrastructure\msvc\2005\common.vcproj¸C:\cygwin\home\Administrator\vc2005-compile-fixes\infrastructure\msvc\2005\win32test.vcprojttor\box\bin\bbackupd\bbackupd.cppc:\cygwin\home\Administrator\vc2005-compile-fixes\lib\serve7 `ÿÿÿÿery\BackupQueries.cpp|| common.libUC:\cygwin\home\Administrator\vc2005-compile-fixes\infrastructure\msvc\2005\common.lib  +@,ture\msvc\2005\:+/:r\Protocol.hŒc:\cygwin\home\Administrator\box\bin\bbackupd\BaJMR:S¸¹MÀ2ÁüýM%M<$%M,,-?@MGFHQRMYvc:\cygwin\home\Administrator\box\bin\bbackupd\bbackupd.cppµ.ú÷ * !L2M QRV&OutliningState2 !#ÿÿÿÿ¬OutliningState13"8ÿÿÿÿÚOutliningState12"ÿÿÿÿÿÿÿÿÿÿÿÿÎúOutliningState11"ÿÿÿÿ&ÿÿÿÿÞ\ckupClientContext.cpp\vc2005backupctlÛ214F}.Release|Win32.fJ{A089CEE6-EBF0-4232-A0C0-74850A8127A6}|common.vcproj|c:\cygwŒc:\cygwin\home\Administrator\box\bin\bbackupd\BackupClientContext.cpp>ÚÞ   /$56M=+>IVsToolboxService"EÿÿÿÿEObjMgrContentsV8"ÿÿÿÿÿÿÿÿÿÿÿÿ5HiddenSlnFolders",ÿÿÿÿþÿÿÿOutliningStateDir$ÿÿÿÿÿÿÿÿÿÿÿÿB²BookmarkStateÿÿÿÿÿÿÿÿÿÿÿÿC(OutliningState1 ÿÿÿÿÿÿÿÿÿÿÿÿ™OutliningState4 ÿÿÿÿÿÿÿÿÿÿÿÿûôOutliningState3 ÿÿÿÿ"ÿÿÿÿ¤@[ÿÿÿÿ;QC1D<ZdeMn„pÇÿÿÿÿin\home\Administrator\vc2005-compile-fixes\lib\server\Protocol.h||{D0E1A5C6-B359-4E41-9B60-3365922C2A22}1234£b¥¬¢c:\cygwin\home\Administrator\box\lib\backupclient\BackupClientFileAttributes.cpp>¹ß   -'#() *;QC1DD6GHKKM-N!OPQ ZcdMk8lqrMyaz~Y„…MŒ9•–Mjž£b¥¬­M´Tµ»¼MÞêdë}ßM" :?E?FL!MR"Unc:\cygwin\home\Administrator\box\lib\server\Daemon.cpp=ßm+    +;<MCDLMMT ” àMUãæê ëð ÷ùfM¤ ¥ª«M²"³¸¹MÀÁÉÊMÑ4ÒðñMø/ùM $"5(ÿÿÿÿ\Projects\boxbuild\fc:\cygwin\home\Administrator\box\lib\win32\emu.cppV®:ª $8*B7Cv0w|}‡ˆŽ ¡3¢¯"±¹@»“ÅÆMÍ&V':Î79>AMH+IŽM˜»4¼è$ë,™ M134M<1=_`6aeMk€ƒlÒÓ#ÔÖMÝÞÿM$c$'M/?0r £¤©À:Á0u× ×ÿÿÿÿwin”c:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFilename.cpp"Oê\M*M%L&*+M2+367M>=?bcMj?k‚ƒMŠ>‹‘’M™HšÍÎMÕ:ÖÜÝMä2åéêMñ'ò÷øM?ÿÿÿÿ232-A0C0-74850A812þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ þÿÿÿ   þÿÿÿþÿÿÿ !"#$%&'()*+,-.þÿÿÿ0123456789:;<þÿÿÿ>?@ABCDEFGHIJþÿÿÿLMNOPþÿÿÿRSTUVWXYZ[\]^_`abcdefgþÿÿÿijklmnopqrþÿÿÿtuvwþÿÿÿyz{|}~€OutliningState6 ÿÿÿÿ-ÿÿÿÿ¶ROutliningState5 $(ÿÿÿÿõ`OutliningState10"+'ÿÿÿÿOutliningState8 )ÿÿÿÿ ä Ddc$wxM!–!˜™0›“€ŸM¦{§» ž»¾MÅÎÏÈ-hÆ12M9 i lms|…†Š¢¤F:²³Mº,»ÒÓMÚ.ÛäåMì7íôõMüÿ&Yý*+M2_3bcMjPkuvM}R~žŸM¦^§·¸MÁ…ÂàÿÿÿÿeuvM}$~tc:\cygwin\home\Administrator\box\lib\common\FdGetLine.cpp†ûé–  M$%12M9:>?MH/I¸¹MÁÂÖÿÿÿÿAdministrator\boc:\cygwin\home\Administrator\vc2005-compile-fixes\lib\server\Protocol.h¾JŸ27:OAENLMNOOV2WXcfgNn)p{…ˆŠN‘2’›ž¥¦ª!Æ ÆÿÿÿÿSIMFMTPTNT2X[U[5aeKfi+^i)lp(xc:\cygwin\home\Administrator\box\lib\common\BoxTimeToUnix.h$ô8  AC( ÿÿÿÿpc:\cygwin\home\Administrator\box\lib\crypto\MD5Digest.hN¶’(#()-6 6ÿÿÿÿšc:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFiOutliningState9 ÿÿÿÿÿÿÿÿÿÿÿÿ6OutliningState7 ÿÿÿÿÿÿÿÿÿÿÿÿÜOutliningState17"/@ÿÿÿÿ/BOutliningState16"ÿÿÿÿÿÿÿÿÿÿÿÿ=nleCmbDiff.cppJ²©M 0 1DE]La‰ ŠÖ#Øi!FÿÿÿÿŒc:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFile.cpp7 Ý   ;;;0<¥LXYMc;w>x;¶H·wdM • ./M;§<HI\;]^IcdMk0l}~M…;–™;Ÿ¢;Ì>Íç%è<]†%&M-;IIJJ.opMw­?¯Â,ÃØRÙ;Óïð=ñPx  M0$%M,K-23M:5;@AMH3IRSMZ{[kmMu@vl…MŒR’<˜™M àGál¡õöM[) dstM~;›>œh¾¿MÆ"ÇÏÐMÙ;ûNü;_  ;#(eFftÚM—1˜žŸM¦2§°±M¸8¹ÅÆMÎ=Ïäÿÿÿÿvc:\cygwin\home\Administrator\box\lib\server\SocketListen.hŠß²  /1357:2=?,BCMJPSUX*Z\_c h)ij#k dstN{ ŽHZ’6|¡£N¬»¼ רQ­ðòóöNü8ýôN 7 Lÿÿÿÿzc:\cygwin\home\Administrator\box\lib\server\SocketStream.cpp¿¨ø    %+,M3&4>?MF7GVWM^_fgMn%ovwM~ ”•–—=žM¥ Ë:ÌÍ0Î>¦åæMí ü9ýþ7ÿ9î#$M+ /'01!2,9:MA2BTUM\#]abMi!jopMx/y€ªÿÿÿÿnc:\cygwin\home\Administrator\box\lib\server\Socket.cpp(_j|    &819 O%]ƒ(fhMvRwŽM•`–²OutliningState15"1.ÿÿÿÿKFOutliningState14"ÿÿÿÿÿÿÿÿÿÿÿÿQ‚OutliningState23"ÿÿÿÿÿÿÿÿÿÿÿÿh˜OutliningState32"5Hÿÿÿÿsÿÿÿÿrc:\cygwin\home\Administrator\box\lib\server\Protocol.cppÞ›ì 0-%,;<MCDNOMV<WjkMrs ¡M©)ª¿ÀMÇ1ÈM2BCMJ.KklM+€‰ŠM‘0’›œM£©:«¬8­"¤³´M»Á:ÃÄ8Å"¼ÊËMÒ"ÓÛÜMã!äìíMô&õM2"#M*1;2314#+9:MAH;IJ1K#BOPMW#X`aMh"iqrMy/z‚ƒMŠ1‹¢£MªÐÒ,«MC<=MD4EIJMQ2RWXwÿÿÿÿ€c:\program files\microsoft visual studio 8\vc\include\process.h¥Gα   %&'"),J- >E+=E 4GJ!Ni=k„B†n‰Œ’;”•R—˜œ «²³A·¸F¼©½Àãæç çéëÿÿÿÿšc:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFileRevDiff.cppó„³M / 0:4<zP{Ÿ!ÿÿÿÿOutliningState30"%ÿÿÿÿxVOutliningState31"ÿÿÿÿÿÿÿÿÿÿÿÿ†OutliningState28"ÿÿÿÿÿÿÿÿÿÿÿÿ—„OutliningState29"6ÿÿÿÿÿÿÿÿ¦ žc:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFilenameClear.cppÙüx4 M!4"%&M-P.23M:[;@AMHVINOMV5WZ[b0cj?lpkpsMz‚ƒ„…þÿÿÿ‡ˆ‰Š‹ŒŽ‘’“”•–þÿÿÿ˜™š›œžŸ ¡¢£¤¥þÿÿÿ§¨©ª«¬þÿÿÿ®¯°±²³´µ¶·¸¹º»¼½¾¿ÀþÿÿÿÂÃÄÅÆÇÈþÿÿÿÊËÌÍÎÏÐÑþÿÿÿÓÔÕÖþÿÿÿØÙÚþÿÿÿÜÝÞßàáâãäåæçþÿÿÿéêëìíîïðñòóôõö÷øùúûüýþÿM{M—3˜¶·¹º@»¼#½ÂÎ}Ùö÷MþLÿM 7!)*M1475m2?@MG<HOÿÿÿÿ|c:\cygwin\home\Administrator\box\lib\crypto\CipherContext.cppÍus!6;.>?MGUWXYN^f%hi'ngHz{M‚‹ƒ•–Mžµ¶MÀbÁéêMõ89öilMt|H}8u€M‰‘*’:Š–—MžÌÐÑHÒÚ>ÛÜ4ÝgŸåæMí îøùM*M&DI7'QRMY\`,Zgÿÿÿÿ¤c:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFileEncodeStream.cpp®©#QM$:%89M@;AYZMb†8‡Ó5ÕÖ1×\dúûM“!"M)9%;M*²³M»C¼ßàMç5èôõMü36ý'(M/<)=D*E•0TUM\H]abMi2jopMw0x}~M…0‡ŽM–.—£ÿÿÿÿ c:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFileEncodeStream.hS´û%!!"M)05P@C 7ISY ijr s!*|& |ÿÿÿÿ”c:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFileDiff.cpp—Hç  OutliningState27"<7ÿÿÿÿ­ÜOutliningState26"ÿÿÿÿÿÿÿÿÿÿÿÿÁÖOutliningState25"ÿÿÿÿ9ÿÿÿÿÉ$OutliningState24"2:ÿÿÿÿÒ'9(8DJKMT;f>gGU~MŒÜÞGþÿM;&< >™~M†±4²Ç·ÓÔMÛò÷KM nBq~1›ÈØbÝÜÝMäöa÷åM @+AFH€ WXM_c"l{Ž!ž>Ÿ©3ª® ¯ÃÄÓ2Ô ãFäåEæÚìWaðñMø#ùþÿM#ÿÿÿÿšc:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFileCombine.cppЉ¶M,; <P QjHkn>os6te-‡ˆMVÄÅMÐTÒXYMa„bžÿÿÿÿ˜c:\cygwin\home\Administrator\box\lib\backupclient\BackupStoreFileCmbIdx.cpp j~0 23MD‰E[\Mc?dopMw1x‚MŠ8‹ÍÎMÕHÖM"C#()M0-167M>+?Dÿÿÿÿzc:\cygwin\home\Administrator\box\test\win32\testlibwin32.cppH¤ðâ :;9BDF v vÿÿÿÿvc:\cygwin\home\Administrator\boOutliningState22"=;ÿÿÿÿ×þOutliningState20"0>ÿÿÿÿÛ4OutliningState21"ÿÿÿÿÿÿÿÿÿÿÿÿèJOutliningState19"ÿÿÿÿÿÿÿÿÿÿÿÿ : x\bin\bbackupd\bbackupd.cppÝç™  *34 TUY&^ÿÿÿÿ|c:\program files\microsoft platform sdk\include\crt\process.hëRß  *P"#$ %!& ,%-./+0356:%;<%=9?C&DE&FBHKM YZ["]`Ja h,nq5tu x“©¬°¹ÀÁ:ÅÆ?Ê·ËÎñôõ õ÷ùÿÿÿÿ~c:\program files\microsoft visual studio 8\vc\include\stdlib.hÅÔš\  &+6(78:=,>AC4FNQSVLX[^egbkluxz|~… ‡„‰Ž`“šž4«³¶¾¿ÅÇËÍÐ:ÖÜaßâlç ÚéÙéìPòø}ûþ– öõ/)(+P./ 59F:;O=8CDOVsWXZelm'x…=† ˆ‡„Ž“!—œ+Ÿ ¢+£'¥®°»¼6½¿XÂÊbÍÓaÖÈIÚ ÞYßã­ä ëªõ4ûþ ùÿøÿ þÿÿÿ    !"#$%&'()*+,-.þÿÿÿ0123456789:;<=>?@ABCDEFþÿÿÿHIJKLMNOPQRSTUVWXþÿÿÿZ[\]^_þÿÿÿabcdefghijklmnopqrstuvþÿÿÿxyz{|}þÿÿÿ€C"=$+Æ,?6JM ZtBv ­‰]Œ’”1—™1žŸ'¹ÃÅÐÑ<ÒÕ=× ÜdÝ àdá1õ÷1üýBñ%-'!:<?A "ADEGÿÿÿÿ~c:\cygwin\home\Administrator\box\bin\bbackupd\BackupDaemon.cppm%¨H  !"VdeMlmxyM€&†‡M¡:¢§3¨\º»MË'ÌÛÝMæ2çîû üÿ(ïY ÜY\Mcw$€d…d†   ¢¦M­Á3Ã×/Ù7T:€$…*†—r˜®øùM/$0/>?MGK,L`lHlH M 01#+,-M5869 <8=>6? FHIK86WY Z?]i[j&XjmMt—2ÚÜàå3æ;ú!ku7B2CHBIY-^g:iM‹Ž'—˜ž¥'Œ©ªM±b²ÝÞMå0æûüMSM"%'&22A8(B*#I'ŒIKMVVWfgMo`p€Mˆ–&—ž 3¡®° œº&‰º¼MÇ>ÈÓÔMÛ,ÜML./M6"7=>ME#FUVM]4^cdMk5ltÿÿÿÿœc:\cygwin\home\Administrator\boOutliningState18"ÿÿÿÿ?ÿÿÿÿ/ÔOutliningState36"ÿÿÿÿFÿÿÿÿGZOutliningState40"CÿÿÿÿYšOutliningState39"ÿÿÿÿÿÿÿÿÿÿÿÿ`„x\bin\bbackupd\BackupClientDirectoryRecord.cppuó\   j+45M<;=IJMQ8R^_Mfq)rz*|G€ˆd‰ ™>š¢6£ ·¼Å<ÆÕ(Öáãïñ9- EGG]Ja?h‚M‰zŠª«M²¦³ÙÚMâêð3(7K+NQHR^U_zpˆ‹@Ž‘f’—i™œ8£"¥¨D©¸0¹Ó×ÚDÛBPL/MZV]flt0uˆ-‰“)—äGèIæ  M¦'(M/@HA\S^l1„…MŽ›œM£i¤³´M»6¼Âÿÿÿÿbc:\cygwin\home\Administrator\box\lib\win32\emu.hd®o›27 8D2FNVYU!_dg3bilvEjy1z€G‚…†‰ŠŽ‘’•–š›œ£;¤²³CªÔ ÔÚßãïó ô 3 6 ! '*67@AEHI,LQ [a9bg/hl nrt1x z‡ˆ‰sŠ9’§¯³*µÿÿÿÿ†c:\cygwin\home\Administrator\box\bin\bbackupquery\bbackupquery.cppRèíV   07?@ <FJK•–Ðíðù &8ÿÿÿÿˆc:\cygwin\home\Administrator\box\bin\bbackupquery\BackupQueries.cpp—ã»s   d?IJMQRUVM]2^01M8W9Z[Mb „/…†.‡ µ9¶·8¸ À3ÁÂ2ÃhcçèMñPóSTM[.\ghMo4pƒ„M‹\Œ¡¢M©OªÅÆMÍ\ÎM K'LV opMw-x…†M.Ž“”M›7œª«M²ãoåZ³M h -.M5~6GHMO¶7» ¡Á<B=WYP×ßMéZê+,M8E9YZMa"brsMzr{M—[˜¸ÿÿÿÿ~c:\cygwin\home\Administrator\boOutliningState38"GBÿÿÿÿwºOutliningState41"ÿÿÿÿÿÿÿÿ~OutliningState37"ÿÿÿÿÿÿÿÿÿÿÿÿ—OutliningState35"3AÿÿÿÿžÆx\bin\bbackupctl\bbackupctl.cppH½R¨    +%0,1 stuv {*|}O~%‹<Œ%™<šœBª­% &,ÿÿÿÿ|c:\program files\microsoft visual studio 8\vc\include\stdio.h83‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–þÿÿÿ˜™š›œþÿÿÿŸ ¡¢£¤¥¦§¨©þÿÿÿ«¬­®¯°±²³´µþÿÿÿ·¸¹ºþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÄÙ  (+03 : C9EKMNPS[]+^acr0s w|(‚‹!-!“”!–Ž—ˆš¡°±¾g¿ÀyÁÉ^ÊË^Ì×<ØÙ=Ú1Ýß1äåê‹ëõ¦öúwû U ="(l)8‡9AjC Ž’n“”€•%ìïðó,ýþ$1  1{ )p*4B6WD+F"H0I,J:KG:K:S8VŠY¹\:b+l y8{|~:W—1¡£1¨©&𵏹 ¹¼½¿ÿÿÿÿc:\cygwin\home\Administrator\box\bin\bbackupd\Win32ServiceFunctions.cpp'ÞÓ #(%)364VW,]*_¢£¶·ÕÖó óÿÿÿÿ†c:\cygwin\home\Administrator\box\lib\common\DebugMemLeakFinder.cpp#s¶/ "$* ,;U<MCNX3Y„"…Ÿ( ©ª¯»Æ&°Ê(ÌÓ)Õéêú/û  N4W #(,R-:W;N%Od;el|‚ ‚ÿÿÿÿ€c:\cygwin\home\Administrator\box\lib\server\SocketStreamTLS.cpp@8üi "'+,M3,49:MA#BZ[Mb\cijMq ‰‘”( OutliningState34"IÿÿÿÿÿÿÿÿªÔOutliningState33"ÿÿÿÿÿÿÿÿÿÿÿÿ¶XmlPackageOptions$ÿÿÿÿš‰ÿÿÿÿÿÿÿÿÿÿÿÿÁÂJrÒÓMÛJÜ M4T5ADEMLWC\uv<M„…MŒ™šM¡5¢®¯M¶0·Øÿÿÿÿ†c:\cygwin\home\Administrator\box\lib\server\LocalProcessStream.cppüõâ    ,cgjS&qÿÿÿÿqu&v{(|‚&ƒˆ(‰:•8–›:œ 8¡¦D§«>¬¯5±1¸¼ ¾È4Ó×ÌÚboxbackup/infrastructure/msvc/2005/bbackupd.vcproj0000664000175000017500000001774111126433077022746 0ustar siretartsiretart boxbackup/infrastructure/msvc/getversion.pl0000664000175000017500000000073510515010335022062 0ustar siretartsiretart#!perl $basedir = $0; $basedir =~ s/\\[^\\]*$//; $basedir =~ s/\\[^\\]*$//; $basedir =~ s/\\[^\\]*$//; $basedir =~ s/\\[^\\]*$//; $basedir =~ s/\\[^\\]*$//; -d $basedir or die "$basedir: $!"; chdir $basedir or die "$basedir: $!"; require "$basedir\\infrastructure\\BoxPlatform.pm.in"; open VERSIONFILE, "> $basedir/lib/common/BoxVersion.h" or die "BoxVersion.h: $!"; print VERSIONFILE "#define BOX_VERSION \"$BoxPlatform::product_version\"\n"; close VERSIONFILE; exit 0; boxbackup/infrastructure/msvc/2003/0000775000175000017500000000000011652362372017735 5ustar siretartsiretartboxbackup/infrastructure/msvc/2003/boxbackup.sln0000664000175000017500000000625310515010304022415 0ustar siretartsiretartMicrosoft Visual Studio Solution File, Format Version 8.00 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "boxquery", "boxquery.vcproj", "{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common.vcproj", "{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" ProjectSection(ProjectDependencies) = postProject EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupd", "bbackupd.vcproj", "{22D325FB-9131-4BD6-B390-968F0491D687}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win32test", "win32test.vcproj", "{28C29E72-76A2-4D0C-B35B-12D446733D2E}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupctl", "bbackupctl.vcproj", "{9FD51412-E945-4457-A17A-CA3C505CF431}" ProjectSection(ProjectDependencies) = postProject {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} EndProjectSection EndProject Global GlobalSection(SolutionConfiguration) = preSolution Debug = Debug Release = Release EndGlobalSection GlobalSection(ProjectConfiguration) = postSolution {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug.ActiveCfg = Debug|Win32 {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug.Build.0 = Debug|Win32 {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release.ActiveCfg = Release|Win32 {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release.Build.0 = Release|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug.ActiveCfg = Debug|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug.Build.0 = Debug|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release.ActiveCfg = Release|Win32 {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release.Build.0 = Release|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Debug.ActiveCfg = Debug|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Debug.Build.0 = Debug|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Release.ActiveCfg = Release|Win32 {22D325FB-9131-4BD6-B390-968F0491D687}.Release.Build.0 = Release|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug.ActiveCfg = Debug|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug.Build.0 = Debug|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release.ActiveCfg = Release|Win32 {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release.Build.0 = Release|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug.ActiveCfg = Debug|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug.Build.0 = Debug|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Release.ActiveCfg = Release|Win32 {9FD51412-E945-4457-A17A-CA3C505CF431}.Release.Build.0 = Release|Win32 EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution EndGlobalSection GlobalSection(ExtensibilityAddIns) = postSolution EndGlobalSection EndGlobal boxbackup/infrastructure/msvc/2003/win32test.vcproj0000664000175000017500000001226411126433077023026 0ustar siretartsiretart boxbackup/infrastructure/msvc/2003/boxquery.vcproj0000664000175000017500000001340411126433077023037 0ustar siretartsiretart boxbackup/infrastructure/msvc/2003/bbackupctl.vcproj0000664000175000017500000001255211126433077023276 0ustar siretartsiretart boxbackup/infrastructure/msvc/2003/common.vcproj0000664000175000017500000004665511126433077022467 0ustar siretartsiretart boxbackup/infrastructure/msvc/2003/bbackupd.vcproj0000664000175000017500000001632311126433077022737 0ustar siretartsiretart boxbackup/infrastructure/buildenv-testmain-template.cpp0000664000175000017500000001551111126433077024350 0ustar siretartsiretart// // AUTOMATICALLY GENERATED FILE // do not edit // // Note that infrastructure/buildenv-testmain-template.cpp is NOT // auto-generated, but test/*/_main.cpp are generated from it. // // -------------------------------------------------------------------------- // // File // Name: testmain.template.h // Purpose: Template file for running tests // Created: 2003/07/08 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include #include #include #include #include "Logging.h" #include "Test.h" #include "Timer.h" #include "MemLeakFindOn.h" int test(int argc, const char *argv[]); #ifdef BOX_RELEASE_BUILD #define MODE_TEXT "release" #else #define MODE_TEXT "debug" #endif int failures = 0; int first_fail_line; std::string first_fail_file; #ifdef WIN32 #define QUIET_PROCESS "-Q" #else #define QUIET_PROCESS "" #endif std::string bbackupd_args = QUIET_PROCESS, bbstored_args = QUIET_PROCESS, bbackupquery_args, test_args; int filedes_open_at_beginning = -1; #ifdef WIN32 // any way to check for open file descriptors on Win32? inline bool check_filedes(bool x) { return 0; } inline bool checkfilesleftopen() { return false; } #else // !WIN32 #define FILEDES_MAX 256 bool filedes_open[FILEDES_MAX]; bool check_filedes(bool report) { bool allOk = true; // See how many file descriptors there are with values < 256 for(int d = 0; d < FILEDES_MAX; ++d) { if(::fcntl(d, F_GETFD) != -1) { // File descriptor obviously exists if (report && !filedes_open[d]) { struct stat st; if (fstat(d, &st) == 0) { int m = st.st_mode; #define flag(x) ((m & x) ? #x " " : "") BOX_FATAL("File descriptor " << d << " left open (type == " << flag(S_IFIFO) << flag(S_IFCHR) << flag(S_IFDIR) << flag(S_IFBLK) << flag(S_IFREG) << flag(S_IFLNK) << flag(S_IFSOCK) << " or " << m << ")"); } else { BOX_FATAL("File descriptor " << d << " left open (and stat failed)"); } allOk = false; } else if (!report) { filedes_open[d] = true; } } else { if (report && filedes_open[d]) { BOX_FATAL("File descriptor " << d << " was open, now closed"); allOk = false; } else { filedes_open[d] = false; } } } if (!report && allOk) { filedes_open_at_beginning = 0; } return !allOk; } bool checkfilesleftopen() { if(filedes_open_at_beginning == -1) { // Not used correctly, pretend that there were things // left open so this gets investigated BOX_FATAL("File descriptor test was not initialised"); return true; } // Count the file descriptors open return check_filedes(true); } #endif int main(int argc, char * const * argv) { // Start memory leak testing MEMLEAKFINDER_START Logging::SetProgramName(BOX_MODULE); #ifdef HAVE_GETOPT_H #ifdef BOX_RELEASE_BUILD int logLevel = Log::NOTICE; // need an int to do math with #else int logLevel = Log::INFO; // need an int to do math with #endif struct option longopts[] = { { "bbackupd-args", required_argument, NULL, 'c' }, { "bbstored-args", required_argument, NULL, 's' }, { "test-daemon-args", required_argument, NULL, 'd' }, { NULL, 0, NULL, 0 } }; int ch; while ((ch = getopt_long(argc, argv, "c:d:qs:t:vPTUV", longopts, NULL)) != -1) { switch(ch) { case 'c': { if (bbackupd_args.length() > 0) { bbackupd_args += " "; } bbackupd_args += optarg; } break; case 'd': { if (test_args.length() > 0) { test_args += " "; } test_args += optarg; } break; case 's': { bbstored_args += " "; bbstored_args += optarg; } break; #ifndef WIN32 case 'P': { Console::SetShowPID(true); } break; #endif case 'q': { if(logLevel == Log::NOTHING) { BOX_FATAL("Too many '-q': " "Cannot reduce logging " "level any more"); return 2; } logLevel--; } break; case 'v': { if(logLevel == Log::EVERYTHING) { BOX_FATAL("Too many '-v': " "Cannot increase logging " "level any more"); return 2; } logLevel++; } break; case 'V': { logLevel = Log::EVERYTHING; } break; case 't': { Logging::SetProgramName(optarg); Console::SetShowTag(true); } break; case 'T': { Console::SetShowTime(true); } break; case 'U': { Console::SetShowTime(true); Console::SetShowTimeMicros(true); } break; case '?': { fprintf(stderr, "Unknown option: '%c'\n", optopt); exit(2); } default: { fprintf(stderr, "Unknown option code '%c'\n", ch); exit(2); } } } Logging::SetGlobalLevel((Log::Level)logLevel); argc -= optind - 1; argv += optind - 1; #endif // HAVE_GETOPT_H // If there is more than one argument, then the test is doing something advanced, so leave it alone bool fulltestmode = (argc == 1); if(fulltestmode) { // banner BOX_NOTICE("Running test TEST_NAME in " MODE_TEXT " mode..."); // Count open file descriptors for a very crude "files left open" test check_filedes(false); #ifdef WIN32 // Under win32 we must initialise the Winsock library // before using sockets WSADATA info; TEST_THAT(WSAStartup(0x0101, &info) != SOCKET_ERROR) #endif } try { #ifdef BOX_MEMORY_LEAK_TESTING memleakfinder_init(); #endif Timers::Init(); int returncode = test(argc, (const char **)argv); Timers::Cleanup(); fflush(stdout); fflush(stderr); // check for memory leaks, if enabled #ifdef BOX_MEMORY_LEAK_TESTING if(memleakfinder_numleaks() != 0) { failures++; printf("FAILURE: Memory leaks detected in test code\n"); printf("==== MEMORY LEAKS =================================\n"); memleakfinder_reportleaks(); printf("===================================================\n"); } #endif if(fulltestmode) { bool filesleftopen = checkfilesleftopen(); fflush(stdout); fflush(stderr); if(filesleftopen) { failures++; printf("IMPLICIT TEST FAILED: Something left files open\n"); } if(failures > 0) { printf("FAILED: %d tests failed (first at " "%s:%d)\n", failures, first_fail_file.c_str(), first_fail_line); } else { printf("PASSED\n"); } } return returncode; } catch(std::exception &e) { printf("FAILED: Exception caught: %s\n", e.what()); return 1; } catch(...) { printf("FAILED: Unknown exception caught\n"); return 1; } if(fulltestmode) { if(checkfilesleftopen()) { printf("WARNING: Files were left open\n"); } } } boxbackup/infrastructure/setupexternal.pl0000775000175000017500000000233410402167172021637 0ustar siretartsiretart#!@PERL@ use strict; # This script links in the essential directories and processes various # files to allow the Box libraries to be used in projects outside the main # box library tree. # directories to link through my @linkdirs = qw/lib infrastructure/; # ---------------------------------------------------- my $libdir = $ARGV[0]; die "Provided library dir $libdir does not exist" unless -d $libdir; # Check and remove links from the directory, then add new symlinks for my $d (@linkdirs) { if(-e $d) { die "In project, $d is not a symbolic link" unless -l $d; print "Removing existing symlink $d\n"; unlink $d; } my $link_target = "$libdir/$d"; print "Add symlink $d -> $link_target\n"; die "Can't create symlink $d" unless symlink $link_target, $d; } # Copy and create a base modules file which includes all the libraries print "Create new modules_base.txt file\n"; open OUT,">modules_base.txt" or die "Can't open modules_base.txt file for writing"; print OUT <<__E; # # Automatically generated file, do not edit # # Source: $libdir/modules.txt # __E open IN,"$libdir/modules.txt" or die "Can't open $libdir/modules.txt for reading"; while() { if(m/\A(lib\/.+?)\s/) { print OUT } } close IN; close OUT; boxbackup/infrastructure/makeparcels.pl.in0000775000175000017500000001573111443771333021643 0ustar siretartsiretart#!@PERL@ use strict; use lib 'infrastructure'; use BoxPlatform; my @parcels; my %parcel_contents; sub starts_with ($$) { my ($string,$expected) = @_; return substr($string, 0, length $expected) eq $expected; } sub os_matches ($) { my ($prefix_string) = @_; my @prefixes = split m'\,', $prefix_string; foreach my $prefix (@prefixes) { return 1 if starts_with($build_os, $prefix); return 1 if starts_with($target_os, $prefix); } return 0; } my $copy_command = "cp -p"; if ($build_os eq 'CYGWIN') { $copy_command = "cp -pu"; # faster } open PARCELS,"parcels.txt" or die "Can't open parcels file"; { my $cur_parcel = ''; while() { chomp; s/#.+\Z//; s/\s+\Z//; s/\s+/ /g; next unless m/\S/; # omit bits on some platforms? next if m/\AEND-OMIT/; if(m/\AOMIT:(.+)/) { if (os_matches($1)) { while() { last if m/\AEND-OMIT/; } } next; } if (m'\AONLY:(.+)') { if (not os_matches($1)) { while () { last if m'\AEND-ONLY'; } } next; } next if (m'\AEND-ONLY'); if (m'\AEXCEPT:(.+)') { if (os_matches($1)) { while () { last if m'\AEND-EXCEPT'; } } next; } next if (m'\AEND-EXCEPT'); # new parcel, or a new parcel definition? if(m/\A\s+(.+)\Z/) { push @{$parcel_contents{$cur_parcel}},$1 } else { $cur_parcel = $_; push @parcels,$_; } } } close PARCELS; # create parcels directory mkdir "parcels",0755; mkdir "parcels/scripts",0755; # write master makefile open MAKE,">Makefile" or die "Can't open master Makefile for writing"; print MAKE <<__E; # # AUTOMATICALLY GENERATED FILE # do not edit! # # MAKE = $make_command __E print MAKE "all:\t",join(' ',map {"build-".$_} @parcels),"\n\n"; print MAKE <<__END_OF_FRAGMENT; test: release/common/test release/common/test: ./runtest.pl ALL release .PHONY: docs docs: \$(MAKE) -C docs __END_OF_FRAGMENT my $release_flag = BoxPlatform::make_flag('RELEASE'); my @clean_deps; for my $parcel (@parcels) { my $version = BoxPlatform::parcel_root($parcel); my $target = BoxPlatform::parcel_target($parcel); my $dir = BoxPlatform::parcel_dir($parcel); my @parcel_deps; unless ($target_windows) { open SCRIPT,">parcels/scripts/install-$parcel" or die "Can't open installer script for $parcel for writing"; print SCRIPT "#!/bin/sh\n\n"; } for(@{$parcel_contents{$parcel}}) { my @args = split /\s+/; my ($type,$name,$dest) = @args; my $optional = 0; my $install = 1; if ($type eq 'optional') { $optional = 1; shift @args; ($type,$name,$dest) = @args; } if ($type eq 'noinstall') { $install = 0; shift @args; ($type,$name,$dest) = @args; } if($type eq 'bin') { my $exeext = $platform_exe_ext; print MAKE < $root.tgz )\n"; print MAKE "\n"; unless ($target_windows) { print MAKE "install-$parcel:\n"; print MAKE "\t(cd $dir; ./install-$parcel)\n\n"; } } print MAKE <local/install.msg" or die "Can't open install message file for writing"; print INSTALLMSG <<__E; Parcels need to be installed separately, and as root. Type one of the following: __E for(@parcels) { print INSTALLMSG " $make_command install-".$_."\n"; } print INSTALLMSG "\n"; close INSTALLMSG; boxbackup/bootstrap0000775000175000017500000000007410347400657015264 0ustar siretartsiretart#!/bin/sh aclocal -I infrastructure/m4 autoheader autoconf boxbackup/COPYING.txt0000664000175000017500000005746611345265741015215 0ustar siretartsiretartBox Backup, http://www.boxbackup.org/ Copyright (c) 2003-2010, Ben Summers and contributors. All rights reserved. The license of the code was changed on 23-Jan-2010 in order to meet the Fedora Project's definition of Free Software, and therefore allow inclusion in Fedora, Red Hat Linux and CentOS. This also solves a long-standing incompatibility with the GNU Readline library that prevented us from distributing Box Backup binaries compiled against that library. You can review our discussions of the change in the mailing list archives at: http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000005.html Note that this project uses mixed licensing. Different parts of the project may be used and distributed under different licenses, as described below. The two licenses used are "Box Backup GPL" and a BSD-style license. Unless stated otherwise in the file, all files in the following directories fall under the "Box Backup GPL" license, described below: bin/bbackupctl bin/bbackupd bin/bbackupobjdump bin/bbackupquery bin/bbstoreaccounts bin/bbstored bin/s3simulator lib/backupclient lib/backupstore test/backupdiff test/backupstore test/backupstorefix test/backupstorepatch test/bbackupd contrib/bbadmin contrib/bbreporter contrib/cygwin contrib/debian contrib/mac_osx contrib/redhat contrib/rpm contrib/solaris contrib/suse contrib/windows distribution/boxbackup The "Box Backup GPL" license follows: --------------------------------------------------------------------- The Box Backup GPL is based on the GPLv2 (GNU General Public License version 2 or later) as published by the Free Software Foundation. However, it includes optional exceptions which allow linking with OpenSSL and Microsoft VSS, listed below. This means that Box Backup is not under pure GPLv2 as published by the Free Software Foundation. These are the only differences between the Box Backup GPL license and the original GPL from the Free Software Foundation. Please note that while the Box Backup GPL is similar in spirit to the original GPL, it is not fully compatible. Because of these optional exceptions, you may not include or combine fully GPL source code (such as the GNU readline library) with Box Backup, or distribute the resulting binaries, under the Box Backup GPL license, without specific permission from the authors of such code. You may do so under the pure GPL license, by omitting the optional exclusion clauses below, however you may not legally link such code with OpenSSL or Microsoft VSS, which may limit the usefulness or functionality of the resulting code. The license exemption section below was based on the license of the Bacula project [http://bacula.git.sourceforge.net/git/gitweb.cgi?p=bacula/bacula;a=blob_plain;f=bacula/LICENSE;hb=HEAD]. For the most part, Box Backup is licensed under the GPL version 2 [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]. The sections that follow are optional exemptions to the GPL version 2 license, that apply to code that is copyrighted by Ben Summers and Contributors. Linking: As a special exception to the GPLv2, the Box Backup Project gives permission to link any code falling under this license (the Box Backup GPL) with any software that can be downloaded from the OpenSSL website [http://www.openssl.org] under either the "OpenSSL License" or the "Original SSLeay License", and to distribute the linked executables under the terms of the "Box Backup GPL" license. As a special exception to the GPLv2, the Box Backup Project gives permission to link any code falling under this license (the Box Backup GPL) with any version of Microsoft's Volume Shadow Copy Service 7.2 SDK or Microsoft Windows Software Development Kit (SDK), including vssapi.lib, that can be downloaded from the Microsoft website [*.microsoft.com], and to distribute the linked executables under the terms of the "Box Backup GPL" license. The sections above are optional, and you may distribute the code that falls under this license under the original GPLv2 license by omitting them. The original GPLv2 license follows. GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. --------------------------------------------------------------------- Unless stated otherwise in the file or in the list of directories above, all files in the following directories are dual licensed under the BSD and GPL licenses. You may use and distribute them providing that you comply EITHER with the terms of the BSD license, OR the GPL license. It is not necessary to comply with both licenses, only one. lib/common lib/compress lib/crypto lib/httpserver lib/intercept lib/raidfile lib/server lib/win32 test/basicserver test/common test/compress test/crypto test/httpserver test/raidfile test/win32 infrastructure distribution The BSD license follows: --------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the Box Backup nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------- [http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29] boxbackup/BUGS.txt0000664000175000017500000000217610367470545014634 0ustar siretartsiretart================================================================================================================ 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 boxbackup/configure.ac0000664000175000017500000003135711512150162015603 0ustar siretartsiretart# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) AC_INIT([Box Backup], 0.11, [boxbackup@boxbackup.org]) AC_CONFIG_SRCDIR([lib/common/Box.h]) AC_CONFIG_HEADERS([lib/common/BoxConfig.h]) touch install-sh AC_CANONICAL_SYSTEM test -s install-sh || rm install-sh ### Checks for programs. AC_LANG([C++]) AC_PROG_CXX AC_CXX_EXCEPTIONS AC_CXX_NAMESPACES if test "x$ac_cv_cxx_exceptions" != "xyes" || \ test "x$ac_cv_cxx_namespaces" != "xyes"; then AC_MSG_ERROR([[basic compile checks failed, the C++ compiler is broken]]) fi if test "x$GXX" = "xyes"; then # Use -Wall if we have gcc. This gives better warnings AC_SUBST([CXXFLAGS_STRICT], ['-Wall -Wundef']) # Use -rdynamic if we have gcc, but not mingw. This is needed for backtrace case $target_os in mingw*) ;; *) AC_SUBST([LDADD_RDYNAMIC], ['-rdynamic']) ;; esac fi AC_PATH_PROG([PERL], [perl], [AC_MSG_ERROR([[perl executable was not found]])]) case $target_os in mingw*) TARGET_PERL=perl ;; *) TARGET_PERL=$PERL ;; esac AC_SUBST([TARGET_PERL]) AC_DEFINE_UNQUOTED([PERL_EXECUTABLE], ["$TARGET_PERL"], [Location of the perl executable]) AC_CHECK_TOOL([AR], [ar], [AC_MSG_ERROR([[cannot find ar executable]])]) AC_CHECK_TOOL([RANLIB], [ranlib], [AC_MSG_ERROR([[cannot find ranlib executable]])]) case $target_os in mingw*) AC_CHECK_TOOL([WINDRES], [windres], [AC_MSG_ERROR([[cannot find windres executable]])]) ;; esac ### Checks for libraries. case $target_os in mingw32*) ;; winnt) ;; *) AC_SEARCH_LIBS([nanosleep], [rt], [ac_have_nanosleep=yes], [AC_MSG_ERROR([[cannot find a short sleep function (nanosleep)]])]) ;; esac AC_CHECK_HEADER([zlib.h],, [AC_MSG_ERROR([[cannot find zlib.h]])]) AC_CHECK_LIB([z], [zlibVersion],, [AC_MSG_ERROR([[cannot find zlib]])]) VL_LIB_READLINE([have_libreadline=yes], [have_libreadline=no]) ## Check for Berkely DB. Restrict to certain versions AX_PATH_BDB([1.x or 4.1], [ LIBS="$BDB_LIBS $LIBS" LDFLAGS="$BDB_LDFLAGS $LDFLAGS" CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS" AX_COMPARE_VERSION([$BDB_VERSION],[ge],[4.1],, [AX_COMPARE_VERSION([$BDB_VERSION],[lt],[2],, [AC_MSG_ERROR([[only Berkely DB versions 1.x or at least 4.1 are currently supported]])] )] ) AX_SPLIT_VERSION([BDB_VERSION], [$BDB_VERSION]) ]) ## Check for Open SSL, use old versions only if explicitly requested AC_SEARCH_LIBS([gethostbyname], [nsl socket resolv]) AC_SEARCH_LIBS([shutdown], [nsl socket resolv]) AX_CHECK_SSL(, [AC_MSG_ERROR([[OpenSSL is not installed but is required]])]) AC_ARG_ENABLE( [old-ssl], [AC_HELP_STRING([--enable-old-ssl], [Allow use of pre-0.9.7 Open SSL - NOT RECOMMENDED, read the documentation])]) AC_CHECK_LIB( [crypto], [EVP_CipherInit_ex],, [ if test "x$enable_old_ssl" = "xyes"; then AC_DEFINE([HAVE_OLD_SSL], 1, [Define to 1 if SSL is pre-0.9.7]) else AC_MSG_ERROR([[found an old (pre 0.9.7) version of SSL. Upgrade or read the documentation for alternatives]]) fi ]) ### Checks for header files. case $target_os in mingw32*) ;; winnt*) ;; *) AC_HEADER_DIRENT ;; esac AC_HEADER_STDC AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([dlfcn.h execinfo.h getopt.h process.h pwd.h signal.h]) AC_CHECK_HEADERS([syslog.h time.h cxxabi.h]) AC_CHECK_HEADERS([netinet/in.h]) AC_CHECK_HEADERS([sys/param.h sys/socket.h sys/time.h sys/types.h sys/wait.h]) AC_CHECK_HEADERS([sys/uio.h sys/xattr.h]) AC_CHECK_HEADERS([bsd/unistd.h]) AC_CHECK_HEADER([regex.h], [have_regex_h=yes]) if test "$have_regex_h" = "yes"; then AC_DEFINE([HAVE_REGEX_H], [1], [Define to 1 if regex.h is available]) else AC_CHECK_HEADER([pcreposix.h], [have_pcreposix_h=yes]) fi if test "$have_pcreposix_h" = "yes"; then AC_DEFINE([PCRE_STATIC], [1], [Box Backup always uses static PCRE]) AC_SEARCH_LIBS([regcomp], ["pcreposix -lpcre"],,[have_pcreposix_h=no_regcomp]) fi if test "$have_pcreposix_h" = "yes"; then AC_DEFINE([HAVE_PCREPOSIX_H], [1], [Define to 1 if pcreposix.h is available]) fi if test "$have_regex_h" = "yes" -o "$have_pcreposix_h" = "yes"; then have_regex_support=yes AC_DEFINE([HAVE_REGEX_SUPPORT], [1], [Define to 1 if regular expressions are supported]) else have_regex_support=no fi AC_SEARCH_LIBS([dlsym], ["dl"]) ### Checks for typedefs, structures, and compiler characteristics. AC_CHECK_TYPES([u_int8_t, u_int16_t, u_int32_t, u_int64_t]) AC_CHECK_TYPES([uint8_t, uint16_t, uint32_t, uint64_t]) AC_HEADER_STDBOOL AC_C_CONST AC_C_BIGENDIAN AC_TYPE_UID_T AC_TYPE_MODE_T AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_CHECK_MEMBERS([struct stat.st_flags]) AC_CHECK_MEMBERS([struct stat.st_mtimespec]) AC_CHECK_MEMBERS([struct stat.st_atim.tv_nsec]) AC_CHECK_MEMBERS([struct stat.st_atimensec]) AC_CHECK_MEMBERS([struct sockaddr_in.sin_len],,, [[ #include #include ]]) AC_CHECK_MEMBERS([DIR.d_fd],,, [[#include ]]) AC_CHECK_MEMBERS([DIR.dd_fd],,, [[#include ]]) AC_CHECK_DECLS([INFTIM],,, [[#include ]]) AC_CHECK_DECLS([SO_PEERCRED],,, [[#include ]]) AC_CHECK_DECLS([O_BINARY],,,) # Solaris provides getpeerucred() instead of getpeereid() or SO_PEERCRED AC_CHECK_HEADERS([ucred.h]) AC_CHECK_FUNCS([getpeerucred]) AC_CHECK_DECLS([optreset],,, [[#include ]]) AC_CHECK_DECLS([dirfd],,, [[ #include #include ]]) AC_HEADER_TIME AC_STRUCT_TM AX_CHECK_DIRENT_D_TYPE AC_SYS_LARGEFILE AX_CHECK_DEFINE_PRAGMA if test "x$ac_cv_c_bigendian" != "xyes"; then AX_BSWAP64 fi case $target_os in mingw32*) ;; winnt*) ;; *) AX_RANDOM_DEVICE AX_CHECK_MOUNT_POINT(,[ AC_MSG_ERROR([[cannot work out how to discover mount points on your platform]]) ]) AC_CHECK_MEMBERS([struct dirent.d_ino],,, [[#include ]]) ;; esac AX_CHECK_MALLOC_WORKAROUND ### Checks for library functions. AC_FUNC_CLOSEDIR_VOID AC_FUNC_ERROR_AT_LINE AC_TYPE_SIGNAL AC_FUNC_STAT AC_CHECK_FUNCS([getpeereid lchown setproctitle getpid gettimeofday waitpid]) AC_SEARCH_LIBS([setproctitle], ["bsd"]) # NetBSD implements kqueue too differently for us to get it fixed by 0.10 # TODO: Remove this when NetBSD kqueue implementation is working netbsd_hack=`echo $target_os | sed 's/netbsd.*/netbsd/'` if test "$netbsd_hack" != "netbsd"; then AC_CHECK_FUNCS([kqueue]) fi AX_FUNC_SYSCALL AX_CHECK_SYSCALL_LSEEK AC_CHECK_FUNCS([listxattr llistxattr getxattr lgetxattr setxattr lsetxattr]) AC_CHECK_DECLS([XATTR_NOFOLLOW],,, [[#include ]]) ### Miscellaneous complicated feature checks ## Check for large file support active. AC_SYS_LARGEFILE has already worked ## out how to enable it if necessary, we just use this to report to the user AC_CACHE_CHECK([if we have large file support enabled], [box_cv_have_large_file_support], [AC_TRY_RUN([ $ac_includes_default int main() { return sizeof(off_t)==4; } ], [box_cv_have_large_file_support=yes], [box_cv_have_large_file_support=no], [box_cv_have_large_file_support=no # safe for cross-compile] ) ]) if test "x$box_cv_have_large_file_support" = "xyes"; then AC_DEFINE([HAVE_LARGE_FILE_SUPPORT], [1], [Define to 1 if large files are supported]) fi ## Find out how to do file locking AC_CHECK_FUNCS([flock]) AC_CHECK_DECLS([O_EXLOCK],,, [[#include ]]) AC_CHECK_DECLS([F_SETLK],,, [[#include ]]) case $target_os in mingw32*) ;; winnt*) ;; *) if test "x$ac_cv_func_flock" != "xyes" && \ test "x$ac_cv_have_decl_O_EXLOCK" != "xyes" && \ test "x$ac_cv_have_decl_F_SETLK" != "xyes" then AC_MSG_ERROR([[cannot work out how to do file locking on your platform]]) fi ;; esac ## Get tmpdir temp_directory_name="/tmp" AC_ARG_WITH( [tmp-dir], [AC_HELP_STRING([--with-tmp-dir=DIR], [Directory for temporary files [/tmp]])], [temp_directory_name="$withval"]) AC_DEFINE_UNQUOTED([TEMP_DIRECTORY_NAME], ["$temp_directory_name"], [TMP directory name]) ## Allow linking binaries with static libraries AC_ARG_ENABLE( [static-bin], [AC_HELP_STRING([--enable-static-bin], [Link binaries with static libraries])]) if test "x$enable_static_bin" = "xyes"; then AC_CHECK_LIB([ssl],[SSL_read],,, [crypto]) LIBS="-Wl,-Bstatic $LIBS -Wl,-Bdynamic" fi # override default sysconfdir, for backwards compatibility test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc test "$localstatedir" = '${prefix}/var' && localstatedir=/var ## Kludge to allow makeparcels.pl to use bindir. This is not a good long term ## solution because it prevents use of "make exec_prefix=/some/dir" saved_prefix=$prefix saved_exec_prefix=$exec_prefix test "x$prefix" = xNONE && prefix=$ac_default_prefix test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' eval bindir_expanded=` eval "echo $bindir"` eval sbindir_expanded=` eval "echo $sbindir"` eval sysconfdir_expanded=` eval "echo $sysconfdir"` eval localstatedir_expanded=`eval "echo $localstatedir"` prefix=$saved_prefix exec_prefix=$saved_exec_prefix AC_SUBST([bindir_expanded]) AC_SUBST([sbindir_expanded]) AC_SUBST([sysconfdir_expanded]) AC_SUBST([localstatedir_expanded]) ## Figure out the client parcel directory and substitute it build_dir=`dirname $0` build_dir=`cd $build_dir && pwd` client_parcel_dir=`$PERL infrastructure/parcelpath.pl backup-client $target_os` if test "$build_os" = "cygwin"; then client_parcel_dir=`cygpath -wa $client_parcel_dir | sed -e 's|\\\|/|g'` build_dir=` cygpath -wa $build_dir | sed -e 's|\\\|/|g'` fi AC_SUBST([client_parcel_dir]) AC_SUBST([build_dir]) ## Figure out version and substitute it in box_version=`$PERL infrastructure/printversion.pl` AC_SUBST([box_version]) ### Output files AC_CONFIG_FILES([infrastructure/BoxPlatform.pm contrib/mac_osx/org.boxbackup.bbackupd.plist contrib/mac_osx/org.boxbackup.bbstored.plist contrib/solaris/bbackupd-manifest.xml contrib/solaris/bbstored-manifest.xml lib/common/BoxPortsAndFiles.h test/bbackupd/testfiles/bbackupd.conf test/bbackupd/testfiles/bbackupd-exclude.conf test/bbackupd/testfiles/bbackupd-snapshot.conf test/bbackupd/testfiles/bbackupd-symlink.conf ]) AX_CONFIG_SCRIPTS([bin/bbackupd/bbackupd-config bin/bbackupquery/makedocumentation.pl bin/bbstored/bbstored-certs bin/bbstored/bbstored-config contrib/debian/bbackupd contrib/debian/bbstored contrib/redhat/bbackupd contrib/redhat/bbstored contrib/suse/bbackupd contrib/suse/bbstored contrib/solaris/bbackupd-smf-method contrib/solaris/bbstored-smf-method contrib/windows/installer/boxbackup.mpi infrastructure/makebuildenv.pl infrastructure/makeparcels.pl infrastructure/makedistribution.pl lib/common/makeexception.pl lib/raidfile/raidfile-config lib/server/makeprotocol.pl runtest.pl test/backupstorefix/testfiles/testbackupstorefix.pl test/bbackupd/testfiles/extcheck1.pl test/bbackupd/testfiles/extcheck2.pl test/bbackupd/testfiles/notifyscript.pl test/bbackupd/testfiles/syncallowscript.pl]) # TODO: Need to do contrib/cygwin/install-cygwin-service.pl but location varies AC_OUTPUT # Configure the Box build system echo if ! $PERL ./infrastructure/makebuildenv.pl \ || ! $PERL ./infrastructure/makeparcels.pl; then echo "Making infrastructure failed!" exit 1 fi # Write summary of important info tee config.log.features < .\test\bbackupd\testfiles\bbackupd.conf