/// <summary> /// Fills in the magic /// </summary> /// <remarks> /// This method must be called at the very end of the stream construction. /// Afterwards, no further stream modifications should occur. /// </remarks> public void FixUpHeader(Stream stream, FileEnvelopeId correlationId) { Contract.Requires(stream != null); Contract.Requires(correlationId.IsValid); var length = stream.Position; // Truncate, just in case file already existed but was bigger stream.SetLength(stream.Position); stream.Position = m_name.Length + Eol.Length + correlationId.Value.Length; using (var writer = new BinaryWriter(stream, CharUtilities.Utf8NoBomNoThrow, leaveOpen: true)) { WriteHeaderFixUp( writer, CleanEof, Version, m_version, length, ComputeMagic(length, correlationId)); } stream.Position = length; }
internal void Save(Stream stream, string fileName) { ExceptionUtilities.HandleRecoverableIOException( () => { // We don't have anything in particular to correlate this file to, // so we are simply creating a unique correlation id that is used as part // of the header consistency check. FileEnvelopeId correlationId = FileEnvelopeId.Create(); s_fileEnvelope.WriteHeader(stream, correlationId); using (BuildXLWriter writer = new BuildXLWriter(debug: false, stream: stream, leaveOpen: true, logStats: false)) { m_pathTable.StringTable.Serialize(writer); m_pathTable.Serialize(writer); writer.Write(m_pathMapping.Count); foreach (var kvp in m_pathMapping) { writer.Write(kvp.Key); writer.Write(kvp.Value); } } s_fileEnvelope.FixUpHeader(stream, correlationId); return((object)null); }, ex => { throw new BuildXLException(I($"Failed to write to '{fileName}'"), ex); }); }
private long ComputeMagic(long length, FileEnvelopeId id) { // HashCodeHelper performs a stable hash code computation. // We take into account the other header fields, and the file length. return(HashCodeHelper.Combine( HashCodeHelper.GetOrdinalHashCode64(m_name), HashCodeHelper.GetOrdinalHashCode64(id.Value), Version | (long)(((ulong)(uint)m_version) << 32), length)); }
/// <summary> /// Checks whether actual and expected ids match /// </summary> /// <exception cref="BuildXLException">Thrown when the file header is corrupted.</exception> public static void CheckCorrelationIds(FileEnvelopeId persistedCorrelationId, FileEnvelopeId expectedCorrelationId) { Contract.Requires(persistedCorrelationId.IsValid); Contract.Requires(expectedCorrelationId.IsValid); if (persistedCorrelationId.Value != expectedCorrelationId.Value) { throw new BuildXLException("Correlation ids don't match"); } }
/// <summary> /// Writes the header, leaving space for some details to be fixed up later /// </summary> /// <remarks> /// This method must be called at the very beginning of the stream construction. /// If stream writing was successful, then <see cref="FixUpHeader"/> must be called at the very end of the stream construction. /// </remarks> public void WriteHeader(Stream stream, FileEnvelopeId correlationId) { Contract.Requires(stream != null); Contract.Requires(correlationId.IsValid); if (stream.Position != 0) { throw new BuildXLException("File beginning mismatch"); } using (var writer = new BinaryWriter(stream, CharUtilities.Utf8NoBomNoThrow, leaveOpen: true)) { writer.Write(m_name.ToCharArray()); writer.Write(Eol.ToCharArray()); writer.Write(correlationId.Value.ToCharArray()); // Things will need get fixed up starting from here. WriteHeaderFixUp(writer, DirtyEof, 0, 0, 0, 0); } }
/// <summary> /// Reads the file header from the stream. /// </summary> /// <returns>Persisted correlation id</returns> /// <exception cref="BuildXLException">Thrown when the file header is incomplete, outdated, or corrupted.</exception> public FileEnvelopeId ReadHeader(Stream stream) { Contract.Requires(stream != null); Contract.Ensures(Contract.Result <FileEnvelopeId>().IsValid); if (stream.Position != 0) { throw new BuildXLException("File beginning mismatch"); } try { using (var reader = new BinaryReader(stream, CharUtilities.Utf8NoBomNoThrow, leaveOpen: true)) { string persistedName = SafeReadRawString(reader, m_name.Length); if (persistedName != m_name) { throw new BuildXLException("Wrong name"); } string persistedEol = SafeReadRawString(reader, Eol.Length); if (persistedEol != Eol) { throw new BuildXLException("Wrong end of line marker"); } var buffer = new char[MaxIdentifierLength]; int bufferLength = 0; string persistedEof; while (true) { char c = SafeReadChar(reader); if (c == CleanEof[0] || c == DirtyEof[0]) { Contract.Assume(CleanEof.Length == DirtyEof.Length); persistedEof = c + SafeReadRawString(reader, CleanEof.Length - 1); break; } if (bufferLength == MaxIdentifierLength) { throw new BuildXLException("Name too long"); } buffer[bufferLength++] = c; } var persistedIdString = new string(buffer, 0, bufferLength); if (!IsValidIdentifier(persistedIdString)) { throw new BuildXLException("Illegal name"); } var persistedId = new FileEnvelopeId(persistedIdString); if (persistedEof == DirtyEof) { throw new BuildXLException("Dirty file!"); } if (persistedEof != CleanEof) { throw new BuildXLException("Wrong end of file marker"); } var persistedClassVersion = reader.ReadInt32(); if (persistedClassVersion != Version) { throw new BuildXLException("Wrong class version"); } var persistedInstanceVersion = reader.ReadInt32(); if (persistedInstanceVersion != m_version) { throw new BuildXLException("Wrong instance version"); } var persistedLength = reader.ReadInt64(); var length = reader.BaseStream.Length; if (persistedLength != length) { throw new BuildXLException("Wrong length"); } var persistedMagic = reader.ReadInt64(); var actualMagic = ComputeMagic(length, persistedId); if (persistedMagic != actualMagic) { throw new BuildXLException("Wrong magic number"); } return(persistedId); } } catch (IOException ex) { throw new BuildXLException("Error reading file header", ex); } }
/// <summary> /// Reads the file header from the stream. /// </summary> /// <param name="stream">stream from which to read</param> /// <param name="ignoreChecksum">when set, ignores errors and keeps reading all fields written by FileEnvelope's serialization</param> /// <returns></returns> public Possible <FileEnvelopeId> TryReadHeader(Stream stream, bool ignoreChecksum) { Contract.Requires(stream != null); Contract.Requires(stream.Position == 0); string firstError = null; void setError(string err) { if (firstError == null) { firstError = err; } } Possible <FileEnvelopeId> getErrorResult() => new Failure <string>(firstError); Possible <FileEnvelopeId> error(string err) => new Failure <string>(err); try { using (var reader = new BinaryReader(stream, CharUtilities.Utf8NoBomNoThrow, leaveOpen: true)) { string persistedName = SafeReadRawString(reader, m_name.Length); if (persistedName != m_name) { return(error($"Wrong name Persisted:'{persistedName}', Expected:'{m_name}'")); } string persistedEol = SafeReadRawString(reader, Eol.Length); if (persistedEol != Eol) { return(error("Wrong end of line marker")); } var buffer = new char[MaxIdentifierLength]; int bufferLength = 0; string persistedEof; while (true) { char c = SafeReadChar(reader); if (c == CleanEof[0] || c == DirtyEof[0]) { Contract.Assume(CleanEof.Length == DirtyEof.Length); persistedEof = c + SafeReadRawString(reader, CleanEof.Length - 1); break; } if (bufferLength == MaxIdentifierLength) { return(error("Name too long")); } buffer[bufferLength++] = c; } var persistedIdString = new string(buffer, 0, bufferLength); if (!IsValidIdentifier(persistedIdString)) { return(error("Illegal name")); } var persistedId = new FileEnvelopeId(persistedIdString); if (persistedEof == DirtyEof) { setError("Dirty file!"); if (!ignoreChecksum) { return(getErrorResult()); } } if (persistedEof != CleanEof) { setError("Wrong end of file marker"); if (!ignoreChecksum) { return(getErrorResult()); } } var persistedClassVersion = reader.ReadInt32(); if (persistedClassVersion != Version) { setError("Wrong class version"); if (!ignoreChecksum) { return(getErrorResult()); } } var persistedInstanceVersion = reader.ReadInt32(); if (persistedInstanceVersion != m_version) { setError("Wrong instance version"); if (!ignoreChecksum) { return(getErrorResult()); } } var persistedLength = reader.ReadInt64(); var length = reader.BaseStream.Length; if (persistedLength != length) { setError("Wrong length"); if (!ignoreChecksum) { return(getErrorResult()); } } var persistedMagic = reader.ReadInt64(); if (persistedMagic != ComputeMagic(length, persistedId)) { setError("Wrong magic number"); if (!ignoreChecksum) { return(getErrorResult()); } } return(firstError != null ? getErrorResult() : persistedId); } } catch (IOException ex) { return(new Failure <string>("Error reading file header", new Failure <Exception>(ex))); } }