/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2011-2016 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * **************************************************************************/ #include "action_header.h" #include "../../urbackupcommon/os_functions.h" #include "../../Interface/File.h" #include "backups.h" #include #include "../../common/data.h" #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES #include "../../common/miniz.h" #ifndef _WIN32 #define _fdopen fdopen #define _close close #include #else #include #include #endif namespace { struct MiniZFileInfo { uint64 file_offset; int64 last_writetime; THREAD_ID tid; }; size_t my_mz_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { MiniZFileInfo* fileInfo = reinterpret_cast(pOpaque); if(fileInfo->file_offset!=file_ofs) { Server->Log("Streaming ZIP file failed at file offset " + convert(file_ofs) + " bufsize " + convert(n)+ " stream offset "+convert(fileInfo->file_offset), LL_ERROR); return 0; } fileInfo->file_offset=file_ofs+n; bool b=Server->WriteRaw(fileInfo->tid, reinterpret_cast(pBuf), n, false); if(b) fileInfo->last_writetime = Server->getTimeMS(); return b?n:0; } mz_bool my_mz_needs_keepalive(void *pOpaque) { MiniZFileInfo* fileInfo = reinterpret_cast(pOpaque); return (Server->getTimeMS() - fileInfo->last_writetime > 1000) ? 1 : 0; } bool my_miniz_init(mz_zip_archive *pZip, MiniZFileInfo* fileInfo) { pZip->m_pWrite = my_mz_write_func; pZip->m_pIO_opaque = fileInfo; pZip->m_pNeeds_keepalive = my_mz_needs_keepalive; if (!mz_zip_writer_init_v2(pZip, 0, MZ_ZIP_FLAG_CASE_SENSITIVE)) return false; return true; } bool add_dir(mz_zip_archive& zip_archive, const std::string& archivefoldername, const std::string& folderbase, const std::string& foldername, const std::string& start_foldername, const std::string& hashfolderbase, const std::string& hashfoldername, const std::string& filter, bool token_authentication, const std::vector &backup_tokens, const std::vector &tokens, bool skip_special) { bool has_error=false; const std::vector files = getFiles(os_file_prefix(foldername), &has_error); if (has_error) { Server->Log("Error while adding files to ZIP file. Error listing files in folder \"" + foldername+"\". " + os_last_error_str(), LL_ERROR); return false; } for(size_t i=0;iLog("Error opening symlink target \""+filename+"\" of symlink at \"" + filename_old + "\"", LL_INFO); continue; } if (is_dir_link) { metadataname = hashfolderbase + os_file_sep() + symlink_target + os_file_sep() + metadata_dir_fn; next_hashfoldername = hashfolderbase + os_file_sep() + symlink_target; } else { metadataname = hashfolderbase + os_file_sep() + symlink_target; } } else { Server->Log("Error getting symlink target of \"" + filename + "\". "+os_last_error_str(), LL_ERROR); continue; } } bool has_metadata = false; FileMetadata metadata; if(token_authentication && ( !read_metadata(metadataname, metadata) || !backupaccess::checkFileToken(backup_tokens, tokens, metadata) ) ) { continue; } else if(!token_authentication && !metadataname.empty()) { has_metadata = read_metadata(metadataname, metadata); } else { has_metadata = true; if (!token_authentication) { metadata.last_modified = file.last_modified; #ifdef _WIN32 metadata.created = file.created; #endif metadata.accessed = file.accessed; } } time_t* last_modified=NULL; time_t last_modified_wt; CWData extra_data_local; CWData extra_data_central; if(has_metadata) { #ifdef _WIN32 last_modified_wt=static_cast(metadata.last_modified); #else last_modified_wt=static_cast(metadata.last_modified); #endif last_modified=&last_modified_wt; if (metadata.created > 0) { //NTFS extra field CWData ntfs_extra; ntfs_extra.addUShort(0x000a); ntfs_extra.addUShort(4 + 2 + 2 + 8 + 8 + 8); ntfs_extra.addUInt(0); ntfs_extra.addUShort(0x0001); ntfs_extra.addUShort(3 * 8); //TODO: Get higher resolution NTFS timestamps from metadata and use it here ntfs_extra.addInt64(os_to_windows_filetime(metadata.last_modified)); ntfs_extra.addInt64(os_to_windows_filetime(metadata.accessed)); ntfs_extra.addInt64(os_to_windows_filetime(metadata.created)); extra_data_local.addBuffer(ntfs_extra.getDataPtr(), ntfs_extra.getDataSize()); extra_data_central.addBuffer(ntfs_extra.getDataPtr(), ntfs_extra.getDataSize()); } unsigned char flags = 0 << 1 | 1 << 1; unsigned short local_size = 1 + sizeof(_u32) * 2; if (metadata.created > 0) { flags |= 1 << 2; local_size += sizeof(_u32); } //Extended Timestamp Extra Field extra_data_local.addUShort(0x5455); extra_data_local.addUShort(local_size); extra_data_local.addUChar(flags); extra_data_local.addUInt(static_cast<_u32>(metadata.last_modified)); extra_data_local.addUInt(static_cast<_u32>(metadata.accessed)); if (metadata.created>0) { extra_data_local.addUInt(static_cast<_u32>(metadata.created)); } extra_data_central.addUShort(0x5455); extra_data_central.addUShort(1 + sizeof(_u32)); extra_data_central.addUChar(flags); extra_data_central.addUInt(static_cast<_u32>(metadata.last_modified)); } //TODO: ZIP has extensions for NTFS/Unix/MacOS attributes, symbolic links, NTFS ACL, ... use them std::string os_err; mz_bool rc; if(file.isdir) { rc = mz_zip_writer_add_mem_ex_v2(&zip_archive, (archivename + "/").c_str(), NULL, 0, NULL, 0, MZ_DEFAULT_LEVEL|MZ_ZIP_FLAG_UTF8_FILENAME, 0, 0, last_modified, extra_data_local.getDataPtr(), extra_data_local.getDataSize(), extra_data_central.getDataPtr(), extra_data_central.getDataSize()); if (rc == MZ_FALSE) { os_err = os_last_error_str(); } } else { std::auto_ptr add_file(Server->openFile(os_file_prefix(filename), MODE_READ_SEQUENTIAL)); if (add_file.get() == NULL) { Server->Log("Error opening file \"" + filename + "\" for ZIP file download. " + os_last_error_str(), LL_ERROR); return false; } int64 fsize = add_file->Size(); #ifndef _WIN32 int fd = add_file->getOsHandle(true); #else int fd =_open_osfhandle(reinterpret_cast(add_file->getOsHandle(true)), _O_RDONLY); if (fd == -1) { Server->Log("Error opening file fd for \"" + filename + "\" for ZIP file download." + os_last_error_str(), LL_ERROR); return false; } #endif add_file.reset(); FILE* file = _fdopen(fd, "r"); if (file != NULL) { rc = mz_zip_writer_add_cfile(&zip_archive, archivename.c_str(), file, fsize, last_modified, NULL, 0, MZ_DEFAULT_LEVEL|MZ_ZIP_FLAG_UTF8_FILENAME, extra_data_local.getDataPtr(), extra_data_local.getDataSize(), extra_data_central.getDataPtr(), extra_data_central.getDataSize()); if (rc == MZ_FALSE) { os_err = os_last_error_str(); } fclose(file); } else { Server->Log("Error opening FILE handle for \"" + filename + "\" for ZIP file download." + os_last_error_str(), LL_ERROR); _close(fd); return false; } } if(rc==MZ_FALSE) { mz_zip_error err = mz_zip_get_last_error(&zip_archive); Server->Log("Error while adding file \""+filename+"\" to ZIP file. Error: "+mz_zip_get_error_string(err)+ (os_err.empty() ? "" : (". OS error: "+os_err)), LL_ERROR); return false; } if(file.isdir) { bool symlink_loop = false; bool symlink_outside = true; std::string orig_filename = foldername + os_file_sep() + file.name; if (is_dir_link) { if (next(orig_filename + os_file_sep(), 0, filename + os_file_sep()) ) { symlink_loop = true; } if (next(filename + os_file_sep(), 0, start_foldername + os_file_sep())) { symlink_outside = false; } } if (!symlink_loop && symlink_outside) { if (!add_dir(zip_archive, archivename, folderbase, filename, start_foldername, hashfolderbase, next_hashfoldername, filter, token_authentication, backup_tokens, tokens, false)) { return false; } } else { if(symlink_loop) Server->Log("Not following looping symbolic link at \"" + orig_filename + "\" to \""+filename+"\"", LL_INFO); else if(!symlink_outside) Server->Log("Not following symbolic link at \"" + orig_filename + "\" to \""+filename+"\" because its contents are already in the ZIP file", LL_INFO); } } } return true; } } bool create_zip_to_output(const std::string& folderbase, const std::string& foldername, const std::string& hashfolderbase, const std::string& hashfoldername, const std::string& filter, bool token_authentication, const std::vector &backup_tokens, const std::vector &tokens, bool skip_hashes) { mz_zip_archive zip_archive; memset(&zip_archive, 0, sizeof(zip_archive)); MiniZFileInfo file_info = {}; file_info.tid=Server->getThreadID(); file_info.last_writetime = Server->getTimeMS(); if(!my_miniz_init(&zip_archive, &file_info)) { Server->Log("Error while initializing ZIP archive", LL_ERROR); return false; } if(!add_dir(zip_archive, "", folderbase, foldername, foldername, hashfolderbase, hashfoldername, filter, token_authentication, backup_tokens, tokens, skip_hashes)) { Server->Log("Error while adding files and folders to ZIP archive", LL_ERROR); return false; } if(!mz_zip_writer_finalize_archive(&zip_archive)) { Server->Log("Error while finalizing ZIP archive", LL_ERROR); return false; } if(!mz_zip_writer_end(&zip_archive)) { Server->Log("Error while ending ZIP archive writer", LL_ERROR); return false; } return true; }