Пример #1
0
        /// <summary>
        /// Deserializes into an instance of <see cref="VolumeMap"/>.
        /// </summary>
        public static VolumeMap Deserialize(BuildXLReader reader)
        {
            Contract.Requires(reader != null);

            var volumeMap = new VolumeMap();
            int count     = reader.ReadInt32Compact();

            for (int i = 0; i < count; ++i)
            {
                ulong          serialNumber = reader.ReadUInt64();
                bool           isValid      = reader.ReadBoolean();
                VolumeGuidPath path         = isValid ? VolumeGuidPath.Create(reader.ReadString()) : VolumeGuidPath.Invalid;
                volumeMap.m_volumePathsBySerial.Add(serialNumber, path);
            }

            int numJunctionRoots = reader.ReadInt32Compact();

            for (int i = 0; i < numJunctionRoots; ++i)
            {
                string path = reader.ReadString();
                var    id   = FileIdAndVolumeId.Deserialize(reader);
                volumeMap.m_junctionRootFileIds.Add(path, id);
            }

            return(volumeMap);
        }
        /// <inheritdoc />
        public bool TryGetFileHandleAndPathFromFileIdAndVolumeId(FileIdAndVolumeId fileIdAndVolumeId, FileShare fileShare, out SafeFileHandle handle, out string path)
        {
            path = null;

            FileAccessor.OpenFileByIdResult openResult = m_fileAccessor.TryOpenFileById(
                fileIdAndVolumeId.VolumeSerialNumber,
                fileIdAndVolumeId.FileId,
                FileDesiredAccess.GenericRead,
                fileShare,
                FileFlagsAndAttributes.None,
                out handle);

            switch (openResult)
            {
            case FileAccessor.OpenFileByIdResult.FailedToOpenVolume:
            case FileAccessor.OpenFileByIdResult.FailedToFindFile:
            case FileAccessor.OpenFileByIdResult.FailedToAccessExistentFile:
                Contract.Assert(handle == null);
                return(false);

            case FileAccessor.OpenFileByIdResult.Succeeded:
                Contract.Assert(handle != null && !handle.IsInvalid);
                path = FileUtilities.GetFinalPathNameByHandle(handle, volumeGuidPath: false);
                return(true);

            default:
                throw Contract.AssertFailure("Unhandled OpenFileByIdResult");
            }
        }
Пример #3
0
        private static void ReadDictionary(BuildXLReader reader, Dictionary <string, FileIdAndVolumeId> dict)
        {
            int count = reader.ReadInt32Compact();

            for (int i = 0; i < count; ++i)
            {
                string path = reader.ReadString();
                var    id   = FileIdAndVolumeId.Deserialize(reader);
                dict.Add(path, id);
            }
        }
Пример #4
0
        private void MarkEntryAccessed(FileIdAndVolumeId id, Entry entry)
        {
            if (entry.TimeToLive == EntryTimeToLive)
            {
                return; // TTL is already at max; don't bother poking the dictionary.
            }

            Entry newEntry = entry.WithTimeToLive(EntryTimeToLive);

            // We use TryUpdate here since it is possible that a new entry (also with TTL at m_entryTimeToLive)
            // was recorded with a new USN. No retries are needed since all changes after load set TTL at m_entryTimeToLive.
            Analysis.IgnoreResult(m_entries.TryUpdate(id, newEntry, comparisonValue: entry));
        }
            /// <nodoc />
            public IncorrectFileContentEntry(
                string path,
                FileIdAndVolumeId fileIdAndVolumeId,
                Usn usn,
                ContentHash expectedHash,
                ContentHash actualHash)
            {
                Contract.Requires(expectedHash != actualHash);
                Contract.Requires(!string.IsNullOrEmpty(path));

                Path = path;
                FileIdAndVolumeId = fileIdAndVolumeId;
                Usn          = usn;
                ExpectedHash = expectedHash;
                ActualHash   = actualHash;
            }
        /// <inheritdoc />
        public bool TryGetFileHandleAndPathFromFileIdAndVolumeId(FileIdAndVolumeId fileIdAndVolumeId, FileShare fileShare, out SafeFileHandle handle, out string path)
        {
            // Accessing hidden files in MacOs.
            // Ref: http://www.westwind.com/reference/os-x/invisibles.html
            // Another alternative is using fcntl with F_GETPATH that takes a handle and returns the concrete OS path owned by the handle.
            path = I($"/.vol/{fileIdAndVolumeId.VolumeSerialNumber}/{fileIdAndVolumeId.FileId.Low}");

            OpenFileResult result = FileUtilities.TryCreateOrOpenFile(
                path,
                FileDesiredAccess.GenericRead,
                fileShare,
                FileMode.Open,
                FileFlagsAndAttributes.None,
                out handle);

            return(result.Succeeded);
        }
Пример #7
0
        public void FileIdAndVolumeIdEquality()
        {
            var baseValue = new FileIdAndVolumeId(789, new FileId(123, 456));

            StructTester.TestEquality(
                baseValue: baseValue,
                equalValue: baseValue,
                notEqualValues: new[]
            {
                new FileIdAndVolumeId(790, new FileId(123, 456)),
                new FileIdAndVolumeId(789, new FileId(124, 456)),
                new FileIdAndVolumeId(789, new FileId(123, 457)),
            },
                eq: (a, b) => a == b,
                neq: (a, b) => a != b,
                skipHashCodeForNotEqualValues: false);
        }
Пример #8
0
        private static LoadResult TryLoadInternal(string fileContentTablePath, byte entryTimeToLive)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(fileContentTablePath));
            Contract.Requires(entryTimeToLive > 0);

            if (!FileUtilities.FileExistsNoFollow(fileContentTablePath))
            {
                return(LoadResult.FileNotFound(fileContentTablePath));
            }

            Stopwatch sw = Stopwatch.StartNew();

            try
            {
                using (FileStream stream = FileUtilities.CreateFileStream(
                           fileContentTablePath,
                           FileMode.Open,
                           FileAccess.Read,
                           FileShare.Read | FileShare.Delete,
                           // Ok to evict the file from standby since the file will be overwritten and never reread from disk after this point.
                           FileOptions.SequentialScan))
                {
                    try
                    {
                        Analysis.IgnoreResult(s_fileEnvelope.ReadHeader(stream));
                    }
                    catch (BuildXLException ex)
                    {
                        return(LoadResult.InvalidFormat(fileContentTablePath, ex.LogEventMessage, sw.ElapsedMilliseconds));
                    }

                    using (var reader = new BuildXLReader(debug: false, stream: stream, leaveOpen: true))
                    {
                        var loadedTable = new FileContentTable();

                        uint numberOfEntries = reader.ReadUInt32();
                        int  hashLength      = ContentHashingUtilities.HashInfo.ByteLength;
                        var  hashBuffer      = new byte[hashLength];

                        for (uint i = 0; i < numberOfEntries; i++)
                        {
                            // Key: Volume and file ID
                            var fileIdAndVolumeId = FileIdAndVolumeId.Deserialize(reader);

                            // Entry: USN, hash, length, time to live.
                            Usn usn = new Usn(reader.ReadUInt64());

                            int hashBytesRead = 0;
                            while (hashBytesRead != hashLength)
                            {
                                int thisRead = reader.Read(hashBuffer, hashBytesRead, hashLength - hashBytesRead);
                                if (thisRead == 0)
                                {
                                    return(LoadResult.InvalidFormat(fileContentTablePath, "Unexpected end of stream", sw.ElapsedMilliseconds));
                                }

                                hashBytesRead += thisRead;
                                Contract.Assert(hashBytesRead <= hashLength);
                            }

                            long length = reader.ReadInt64();

                            byte thisEntryTimeToLive = reader.ReadByte();
                            if (thisEntryTimeToLive == 0)
                            {
                                return(LoadResult.InvalidFormat(fileContentTablePath, "TTL value must be positive", sw.ElapsedMilliseconds));
                            }

                            thisEntryTimeToLive = Math.Min(thisEntryTimeToLive, entryTimeToLive);
                            Contract.Assert(thisEntryTimeToLive > 0);

                            // We've loaded this entry just now and so clearly haven't used it yet. Tentatively decrement the TTL
                            // for the in-memory table; if the table is saved again without using this entry, the TTL will stay at this
                            // lower value.
                            thisEntryTimeToLive--;

                            var  observedVersionAndHash = new Entry(usn, ContentHashingUtilities.CreateFrom(hashBuffer), length, thisEntryTimeToLive);
                            bool added = loadedTable.m_entries.TryAdd(fileIdAndVolumeId, observedVersionAndHash);
                            Contract.Assume(added);
                        }

                        loadedTable.Counters.AddToCounter(FileContentTableCounters.NumEntries, loadedTable.Count);

                        return(LoadResult.Success(fileContentTablePath, loadedTable, sw.ElapsedMilliseconds));
                    }
                }
            }
            catch (Exception ex)
            {
                return(LoadResult.Exception(fileContentTablePath, ex, sw.ElapsedMilliseconds));
            }
        }
Пример #9
0
        /// <summary>
        /// Records a <see cref="ContentHash" /> for the given file handle. This hash mapping will be persisted to disk if the
        /// table is saved with <see cref="SaveAsync" />. The given file handle should be opened with at most Read sharing
        /// (having the handle should ensure the file is not being written).
        /// This returns a <see cref="VersionedFileIdentityAndContentInfo"/>:
        /// - The identity has the kind <see cref="VersionedFileIdentity.IdentityKind.StrongUsn"/> if a USN-based identity was successfully established;
        ///   the identity may have kind <see cref="VersionedFileIdentity.IdentityKind.Anonymous"/> if such an identity was unavailable.
        /// - Regardless, the contained <see cref="FileContentInfo"/> contains the actual length of the stream corresponding to <paramref name="hash"/>.
        /// </summary>
        /// <remarks>
        /// An overload taking a file path is intentionally not provided. This should be called after hashing or writing a file,
        /// but before closing the handle. This way, there is no race between establishing the file's hash, some unrelated writer,
        /// and recording its file version (e.g., USN) to hash mapping.
        /// Note that this results in a small amount of I/O (e.g., on Windows, a file open and USN query), but never hashes the file or reads its contents.
        /// The <paramref name="strict"/> corresponds to the <c>flush</c> parameter of <see cref="VersionedFileIdentity.TryEstablishStrong"/>
        /// </remarks>
        public VersionedFileIdentity RecordContentHash(
            string path,
            SafeFileHandle handle,
            ContentHash hash,
            long length,
            bool?strict = default)
        {
            Contract.Requires(handle != null);
            Contract.Requires(!string.IsNullOrWhiteSpace(path));

            using (Counters.StartStopwatch(FileContentTableCounters.RecordContentHashDuration))
            {
                // TODO: The contract below looks very nice but breaks tons of UT
                // Fix the tests and enable the contract.
                // Contract.Requires(FileContentInfo.IsValidLength(length, hash));
                // Here we write a new change journal record for this file to get a 'strong' identity. This means that the USN -> hash table
                // only ever contains USNs whose records have the 'close' reason set. Recording USNs without that
                // reason set would not be correct; it would be possible that multiple separate changes (e.g. writes)
                // were represented with the same USN, and so intermediate USNs do not necessarily correspond to exactly
                // one snapshot of a file. See http://msdn.microsoft.com/en-us/library/windows/desktop/aa363803(v=vs.85).aspx
                Possible <VersionedFileIdentity, Failure <VersionedFileIdentity.IdentityUnavailabilityReason> > possibleVersionedIdentity =
                    TryEstablishStrongIdentity(handle, flush: strict == true);

                if (!possibleVersionedIdentity.Succeeded)
                {
                    if (Interlocked.CompareExchange(ref m_changeJournalWarningLogged, 1, 0) == 0)
                    {
                        Tracing.Logger.Log.StorageFileContentTableIgnoringFileSinceVersionedFileIdentityIsNotSupported(
                            Events.StaticContext,
                            path,
                            possibleVersionedIdentity.Failure.DescribeIncludingInnerFailures());
                    }

                    return(VersionedFileIdentity.Anonymous);
                }

                VersionedFileIdentity identity = possibleVersionedIdentity.Result;

                var newEntry = new Entry(identity.Usn, hash, length, EntryTimeToLive);

                // We allow concurrent update attempts with different observed USNs.
                // This is useful and relevant for two reasons:
                // - Querying a 'strong' identity (TryEstablishStrongIdentity) generates a new CLOSE record every time.
                // - Creating hardlinks generates 'hardlink change' records.
                // So, concurrently creating and recording (or even just recording) different links is possible, and
                // keeping the last stored entry (rather than highest-USN entry) can introduce false positives.
                var fileIdAndVolumeId = new FileIdAndVolumeId(identity.VolumeSerialNumber, identity.FileId);

                m_entries.AddOrUpdate(
                    new FileIdAndVolumeId(identity.VolumeSerialNumber, identity.FileId),
                    newEntry,
                    updateValueFactory: (key, existingEntry) =>
                {
                    if (existingEntry.Usn > newEntry.Usn)
                    {
                        return(existingEntry);
                    }

                    if (newEntry.Hash == existingEntry.Hash)
                    {
                        Counters.IncrementCounter(FileContentTableCounters.NumUsnMismatch);
                        Tracing.Logger.Log.StorageUsnMismatchButContentMatch(
                            Events.StaticContext,
                            path,
                            existingEntry.Usn.Value,
                            newEntry.Usn.Value,
                            existingEntry.Hash.ToHex());
                    }
                    else
                    {
                        // Stale USN.
                        Counters.IncrementCounter(FileContentTableCounters.NumContentMismatch);
                    }

                    return(newEntry);
                });

                Tracing.Logger.Log.StorageRecordNewKnownUsn(
                    Events.StaticContext,
                    path,
                    identity.FileId.High,
                    identity.FileId.Low,
                    identity.VolumeSerialNumber,
                    identity.Usn.Value,
                    hash.ToHex());

                return(identity);
            }
        }
Пример #10
0
        /// <summary>
        /// Retrieves an already-known <see cref="ContentHash" /> for the given file handle. If no such hash is available (such as
        /// if the file has been modified since a hash was last recorded), null is returned instead.
        /// </summary>
        /// <remarks>
        /// Note that this results in a small amount of I/O (e.g., on Windows, a file open and USN query), but never hashes the file or reads its contents.
        /// </remarks>
        public VersionedFileIdentityAndContentInfo?TryGetKnownContentHash(string path, SafeFileHandle handle)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(path));
            Contract.Requires(handle != null);

            using (Counters.StartStopwatch(FileContentTableCounters.GetContentHashDuration))
            {
                Possible <VersionedFileIdentity, Failure <VersionedFileIdentity.IdentityUnavailabilityReason> > possibleVersionedIdentity =
                    TryQueryWeakIdentity(handle);

                if (!possibleVersionedIdentity.Succeeded)
                {
                    // We fail quietly for disabled journals on the query side; instead attempting to record a hash will fail.
                    Contract.Assume(
                        possibleVersionedIdentity.Failure.Content == VersionedFileIdentity.IdentityUnavailabilityReason.NotSupported);
                    Tracing.Logger.Log.StorageVersionedFileIdentityNotSupportedMiss(Events.StaticContext, path);
                    return(null);
                }

                VersionedFileIdentity identity = possibleVersionedIdentity.Result;
                var fileIdInfo = new FileIdAndVolumeId(identity.VolumeSerialNumber, identity.FileId);

                // We have a valid identity, but that identity is 'weak' and may correspond to an intermediate record (one without 'close' set).
                // We cannot discard such records here since we can't obtain a real 'Reason' field for a file's current USN record.
                // But we do know that any intermediate record will be a miss below, since we only record 'close' records (strong identities)
                // (see RecordContentHashAsync).
                Entry knownEntry;
                bool  foundEntry = m_entries.TryGetValue(fileIdInfo, out knownEntry);

                if (!foundEntry)
                {
                    Counters.IncrementCounter(FileContentTableCounters.NumFileIdMismatch);
                    Tracing.Logger.Log.StorageUnknownFileMiss(
                        Events.StaticContext,
                        path,
                        identity.FileId.High,
                        identity.FileId.Low,
                        identity.VolumeSerialNumber,
                        identity.Usn.Value);

                    return(null);
                }

                var staleUsn = identity.Usn != knownEntry.Usn;

                if (staleUsn)
                {
                    Tracing.Logger.Log.StorageUnknownUsnMiss(
                        Events.StaticContext,
                        path,
                        identity.FileId.High,
                        identity.FileId.Low,
                        identity.VolumeSerialNumber,
                        readUsn: identity.Usn.Value,
                        knownUsn: knownEntry.Usn.Value,
                        knownContentHash: knownEntry.Hash.ToHex());
                    return(null);
                }

                MarkEntryAccessed(fileIdInfo, knownEntry);
                Counters.IncrementCounter(FileContentTableCounters.NumHit);
                Tracing.Logger.Log.StorageKnownUsnHit(
                    Events.StaticContext,
                    path,
                    identity.FileId.High,
                    identity.FileId.Low,
                    identity.VolumeSerialNumber,
                    usn: knownEntry.Usn.Value,
                    contentHash: knownEntry.Hash.ToHex());

                // Note that we return a 'strong' version of the weak identity; since we matched an entry in the table, we know that the USN
                // actually corresponds to a strong identity (see RecordContentHashAsync).
                return(new VersionedFileIdentityAndContentInfo(
                           new VersionedFileIdentity(
                               identity.VolumeSerialNumber,
                               identity.FileId,
                               identity.Usn,
                               VersionedFileIdentity.IdentityKind.StrongUsn),
                           new FileContentInfo(knownEntry.Hash, knownEntry.Length)));
            }
        }
Пример #11
0
 /// <summary>
 /// Creates an instance of <see cref="ChangedFileIdInfo" />.
 /// </summary>
 public ChangedFileIdInfo(FileIdAndVolumeId fileIdAndVolumeId, UsnRecord usnRecord)
 {
     FileIdAndVolumeId = fileIdAndVolumeId;
     UsnRecord         = usnRecord;
 }
Пример #12
0
        public void TestEngineStateFileContentTableReuse()
        {
            SetupHelloWorld();
            SetUpConfig();
            EngineState lastEngineState = RunEngine("First build");
            var         firstFCT        = lastEngineState.FileContentTable;

            XAssert.IsNotNull(firstFCT);

            FileIdAndVolumeId inFileIdentity  = GetIdentity(GetFullPath(InputFilename));
            FileIdAndVolumeId outFileIdentity = GetIdentity(GetFullPath(OutputFilename));
            Usn inFileUsn  = Usn.Zero;
            Usn outFileUsn = Usn.Zero;
            ISet <FileIdAndVolumeId> ids = new HashSet <FileIdAndVolumeId>();

            XAssert.IsTrue(FileContentTableAccessorFactory.TryCreate(out var accesor, out string error));
            firstFCT.VisitKnownFiles(accesor, FileShare.ReadWrite | FileShare.Delete,
                                     (fileIdAndVolumeId, fileHandle, path, knownUsn, knownHash) =>
            {
                if (fileIdAndVolumeId == inFileIdentity)
                {
                    inFileUsn = knownUsn;
                }
                else if (fileIdAndVolumeId == outFileIdentity)
                {
                    outFileUsn = knownUsn;
                }
                ids.Add(fileIdAndVolumeId);
                return(true);
            });

            XAssert.AreNotEqual(Usn.Zero, inFileUsn);
            XAssert.AreNotEqual(Usn.Zero, outFileUsn);

            // Run engine again
            FreshSetUp("change some stuff");
            lastEngineState = RunEngine("Second build", engineState: lastEngineState);
            var secondFCT = lastEngineState.FileContentTable;

            XAssert.AreNotSame(firstFCT, secondFCT);                    // The FCT gets updated at the end of the run

            outFileIdentity = GetIdentity(GetFullPath(OutputFilename)); // Output file changed
            bool visitedInput  = false;
            bool visitedOutput = false;

            secondFCT.VisitKnownFiles(accesor, FileShare.ReadWrite | FileShare.Delete,
                                      (fileIdAndVolumeId, fileHandle, path, knownUsn, knownHash) =>
            {
                if (fileIdAndVolumeId == inFileIdentity)
                {
                    XAssert.IsTrue(ids.Contains(fileIdAndVolumeId));
                    XAssert.IsTrue(inFileUsn < knownUsn);       // We modified the file
                    inFileUsn    = knownUsn;
                    visitedInput = true;
                }
                else if (fileIdAndVolumeId == outFileIdentity)
                {
                    XAssert.IsFalse(ids.Contains(fileIdAndVolumeId));
                    XAssert.IsTrue(outFileUsn < knownUsn);      // New output file
                    outFileUsn    = knownUsn;
                    visitedOutput = true;
                }
                else
                {
                    XAssert.IsTrue(ids.Contains(fileIdAndVolumeId));     // Other entries are still there
                }
                return(true);
            });

            XAssert.IsTrue(visitedInput);
            XAssert.IsTrue(visitedOutput);
            XAssert.IsTrue(inFileUsn < outFileUsn);
            XAssert.AreEqual(firstFCT.Count + 1, secondFCT.Count); // There's a new entry because the new output file has a different fileId
        }
Пример #13
0
 /// <summary>
 /// Creates an instance of <see cref="ChangedFileIdInfo" />.
 /// </summary>
 public ChangedFileIdInfo(FileIdAndVolumeId fileIdAndVolumeId, UsnRecord usnRecord, Usn?lastTrackedUsn = default)
 {
     FileIdAndVolumeId = fileIdAndVolumeId;
     UsnRecord         = usnRecord;
     LastTrackedUsn    = lastTrackedUsn;
 }