/// <summary> /// Try to link to an existing central name file. /// If the central name file exist, and the link limit is not exceeded, the destination file is linked to the central name file, and the return value is true. /// In this case, the backup of the destination file is considered finished. /// If the central name file does not exist, or linking fails due to an exceeded link limit, the return value is false. /// </summary> /// <returns>True if linking of the destination file to the existing central name file succeeded;, otherwise, false.</returns> /// <exception cref="System.IO.IOException">Error creating hard link from {_centralNameFileName} to {_destinationFileName}, ErrorCode: {hlr}</exception> public bool TryToLinkToExistingNameFile() { var fileInfoCentralNameFileName = new FileInfo(_centralNameFileName); // Compare the FileInfo of the central name file with the FileInfo of the source file // only if both match, we can link the destination file name to the centralNameFile if (fileInfoCentralNameFileName.Exists && FileUtilities.AreMetaDataMatching(fileInfoCentralNameFileName, _sourceFile)) { var hlr = FileUtilities.CreateHardLink(_centralNameFileName, _destinationFileName); if (hlr == 0) { return(true); } else { if (hlr != FileUtilities.ERROR_TOO_MANY_LINKS) // ignore TooManyLinks error, instead let the content file be created anew { throw new System.IO.IOException($"Error creating hard link from {_centralNameFileName} to {_destinationFileName}, ErrorCode: {hlr}"); } } } return(false); }
public void Write() { try { if (File.Exists(_centralStorageFileName)) // central storage file already exists { // make a hardlink from the central storage file to the source file // but first we have to delete the source file if (_sourceFile.IsReadOnly) { _sourceFile.IsReadOnly = false; } _sourceFile.Delete(); var hlr = FileUtilities.CreateHardLink(_centralStorageFileName, _sourceFile.FullName); if (hlr != 0) { if (hlr == FileUtilities.ERROR_TOO_MANY_LINKS) { // if the hardlink limit is exceeded, we need to make a full copy of the source file // effectively replacing the central storage file var tempFileName = FileUtilities.FileRenameToTemporaryFileInSameFolder(_centralStorageFileName); File.Copy(tempFileName, _centralStorageFileName); File.Delete(tempFileName); // now, try to create the hardlink again, now there should be no hardlink limit error any more var hlr2 = FileUtilities.CreateHardLink(_centralStorageFileName, _sourceFile.FullName); if (hlr2 != 0) { throw new System.IO.IOException($"Error create hard link from {_sourceFile.FullName} to {_centralStorageFileName}, ErrorCode: {hlr2}"); } } else { throw new System.IO.IOException($"Error create hard link from {_sourceFile.FullName} to {_centralStorageFileName}, ErrorCode: {hlr}"); } } } else // Central storage file does not exist yet { if (!Directory.Exists(_centralStorageFolder)) { Directory.CreateDirectory(_centralStorageFolder); } // create a hard link from the existing (old) backup file to the central storage file int hlr = FileUtilities.CreateHardLink(_sourceFile.FullName, _centralStorageFileName); if (hlr != 0) { if (hlr == FileUtilities.ERROR_TOO_MANY_LINKS) { // if the hardlink limit is exceeded, we need to make a full copy instead of a // hard link File.Copy(_sourceFile.FullName, _centralStorageFileName); } else { throw new System.IO.IOException($"Error create hard link from {_sourceFile.FullName} to {_centralStorageFileName}"); } } } } catch (Exception ex) { OnFailed(ex); } }
/// <summary> /// Writes the backup file, the central content file and the central name file. /// </summary> /// <remarks> /// All three files, the backup file, the central content file and the central name file have the same data content and are (ideally) linked by hard links. /// The name of the backup file is the same as the name of the original file. /// The name of the central content file is determined by the hash of the file content. /// The name of the central name file is determined by the hash of the original file's metadata (length, last write time, attributes, and full name). /// </remarks> public void Write() { try { var backupMode = _parent._backupMode; if (backupMode == BackupMode.Fast && TryToLinkToExistingNameFile()) { NextStation = NextStation.Finished; } else { if (!IsContentFileNameKnown) { NextStation = NextStation.Reader; } else { bool isRepeatRequired = false; do { if (!File.Exists(_centralContentFileName)) { if (!Directory.Exists(_centralContentFolder)) { Directory.CreateDirectory(_centralContentFolder); } if (_bytesInBuffer == (_streamLength + FileUtilities.BufferSpaceForLengthWriteTimeAndFileAttributes) && _buffer is { } buffer) { using (var destStream = new FileStream(_centralContentFileName, FileMode.CreateNew, FileAccess.Write, FileShare.Read)) { destStream.Write(_buffer, FileUtilities.BufferSpaceForLengthWriteTimeAndFileAttributes, _bytesInBuffer - FileUtilities.BufferSpaceForLengthWriteTimeAndFileAttributes); } // Set attributes on the destination file, such as last write time, and attributes. File.SetLastWriteTimeUtc(_centralContentFileName, _sourceFile.LastWriteTimeUtc); File.SetAttributes(_centralContentFileName, _sourceFile.Attributes & FileUtilities.FileAttributeMask); } else { // we do not use the destination stream here File.Copy(_sourceFile.FullName, _centralContentFileName); // note: when copying, attributes and LastWriteTime is already set. } } { isRepeatRequired = false; // Central storage file now exists, so create a hard link to it in the backup directory var hlr = FileUtilities.CreateHardLink(_centralContentFileName, _destinationFileName); if (hlr != 0) { if (hlr == FileUtilities.ERROR_TOO_MANY_LINKS) // is hard link limit exceeded? { // in order to circumvent the hard link limit, we delete the file from the central storage, and create it anew File.Delete(_centralContentFileName); isRepeatRequired = true; continue; } else { throw new System.IO.IOException($"Error creating hard link {_destinationFileName}, ErrorCode: {hlr}"); } } } // Handle the name file (except when in BackupMode.SecureNoNameFile) if (backupMode == BackupMode.Fast && !File.Exists(_centralNameFileName)) { var folder = Path.GetDirectoryName(_centralNameFileName); if (!Directory.Exists(folder)) { Directory.CreateDirectory(folder); } var hlr = FileUtilities.CreateHardLink(_centralContentFileName, _centralNameFileName); if (hlr != 0) { if (hlr == FileUtilities.ERROR_TOO_MANY_LINKS) { File.Delete(_centralNameFileName); } else { throw new System.IO.IOException($"Error creating hard link {_destinationFileName}, ErrorCode: {hlr}"); } } } } while (isRepeatRequired); _stream?.Close(); _stream?.Dispose(); NextStation = NextStation.Finished; } } }