Bagikan melalui


Menyediakan Data File

Ketika penyedia pertama kali membuat akar virtualisasi, itu kosong pada sistem lokal. Artinya, tidak ada item di penyimpanan data cadangan yang belum di-cache ke disk. Saat item dibuka, ProjFS meminta informasi dari penyedia untuk memungkinkan tempat penampung untuk item tersebut dibuat dalam sistem file lokal. Saat konten item diakses, ProjFS meminta konten tersebut dari penyedia. Hasilnya adalah bahwa dari perspektif pengguna, file dan direktori virtual tampak mirip dengan file dan direktori normal yang sudah berada di sistem file lokal.

Pembuatan Tempat Penampung

Ketika aplikasi mencoba membuka handel ke file virtual, ProjFS memanggil panggilan balik PRJ_GET_PLACEHOLDER_INFO_CB untuk setiap item jalur yang belum ada di disk. Misalnya, jika aplikasi mencoba membuka C:\virtRoot\dir1\dir2\file.txt, tetapi hanya jalur C:\virtRoot\dir1 yang ada di disk, maka penyedia akan menerima panggilan balik untuk C:\virtRoot\dir1\dir2, lalu untuk C:\virtRoot\dir1\dir2\file.txt.

Saat ProjFS memanggil panggilan balik PRJ_GET_PLACEHOLDER_INFO_CB penyedia, penyedia melakukan tindakan berikut:

  1. Penyedia menentukan apakah nama yang diminta ada di penyimpanan cadangannya. Penyedia harus menggunakan PrjFileNameCompare sebagai rutinitas perbandingan saat mencari penyimpanan backing-nya untuk menentukan apakah nama yang diminta ada di penyimpanan backing. Jika tidak, penyedia mengembalikan HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) dari panggilan balik.

  2. Jika nama yang diminta memang ada di penyimpanan backing, penyedia mengisi struktur PRJ_PLACEHOLDER_INFO dengan metadata sistem file item dan memanggil PrjWritePlaceholderInfo untuk mengirim data ke ProjFS. ProjFS akan menggunakan informasi tersebut untuk membuat tempat penampung di sistem file lokal untuk item tersebut.

    ProjFS akan menggunakan apa pun yang FILE_ATTRIBUTE menandai set penyedia di anggota FileBasicInfo.FileAttributes dari PRJ_PLACEHOLDER_INFO kecuali untuk FILE_ATTRIBUTE_DIRECTORY; ini akan menetapkan nilai yang benar untuk FILE_ATTRIBUTE_DIRECTORY di anggota FileBasicInfo.FileAttributes sesuai dengan nilai yang disediakan dari anggota FileBasicInfo.IsDirectory .

    Jika penyimpanan pendukung mendukung tautan simbolis, penyedia harus menggunakan PrjWritePlaceholderInfo2 untuk mengirim data tempat penampung ke ProjFS. PrjWritePlaceholderInfo2 mendukung input buffer tambahan yang memungkinkan penyedia menentukan bahwa tempat penampung adalah tautan simbolis dan apa targetnya. Sebaliknya bertingkah seperti yang dijelaskan di atas untuk PrjWritePlaceholderInfo. Contoh berikut menggambarkan cara menggunakan PrjWritePlaceholderInfo2 untuk memberikan dukungan untuk tautan simbolis.

    Perhatikan bahwa PrjWritePlaceholderInfo2 didukung pada versi Windows 10 2004. Penyedia harus memeriksa keberadaan PrjWritePlaceholderInfo2, misalnya dengan menggunakan GetProcAddress.

HRESULT
MyGetPlaceholderInfoCallback(
    _In_ const PRJ_CALLBACK_DATA* callbackData
    )
{
    // MyGetItemInfo is a routine the provider might implement to get
    // information from its backing store for a given file path.  The first
    // parameter is an _In_ parameter that supplies the name to look for.
    // If the item exists the routine provides the file information in the
    // remaining parameters, all of which are annotated _Out_:
    // * 2nd parameter: the name as it appears in the backing store
    // * 3rd-9th parameters: basic file info
    // * 10th parameter: if the item is a symbolic link, a pointer to a 
    //   NULL-terminated string identifying the link's target
    //
    // Note that the routine returns the name that is in the backing
    // store.  This is because the input file path may not be in the same
    // case as what is in the backing store.  The provider should create
    // the placeholder with the name it has in the backing store.
    //
    // Note also that this example does not provide anything beyond basic
    // file information and a possible symbolic link target.
    HRESULT hr;
    WCHAR* backingStoreName = NULL;
    WCHAR* symlinkTarget = NULL;
    PRJ_PLACEHOLDER_INFO placeholderInfo = {};
    hr = MyGetItemInfo(callbackData->FilePathName,
                       &backingStoreName,
                       &placeholderInfo.FileBasicInfo.IsDirectory,
                       &placeholderInfo.FileBasicInfo.FileSize,
                       &placeholderInfo.FileBasicInfo.CreationTime,
                       &placeholderInfo.FileBasicInfo.LastAccessTime,
                       &placeholderInfo.FileBasicInfo.LastWriteTime,
                       &placeholderInfo.FileBasicInfo.ChangeTime,
                       &placeholderInfo.FileBasicInfo.FileAttributes,
                       &symlinkTarget);

    if (FAILED(hr))
    {
        // If callbackData->FilePathName doesn't exist in our backing store then
        // MyGetItemInfo should HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND).
        // If this is some other error, e.g. E_OUTOFMEMORY because MyGetItemInfo
        // couldn't allocate space for backingStoreName, we return that.
        return hr;
    }

    // If the file path is for a symbolic link, pass that in with the placeholder info.
    if (symlinkTarget != NULL)
    {
        PRJ_EXTENDED_INFO extraInfo = {};

        extraInfo.InfoType = PRJ_EXT_INFO_SYMLINK;
        extraInfo.Symlink.TargetName = symlinkTarget;

        // If this returns an error we'll want to return that error from the callback.
        hr = PrjWritePlaceholderInfo2(callbackData->NamespaceVirtualizationContext,
                                      backingStoreName,
                                      &placeholderInfo,
                                      sizeof(placeholderInfo),
                                      &extraInfo);
    }
    else
    {
        // If this returns an error we'll want to return that error from the callback.
        hr = PrjWritePlaceholderInfo(callbackData->NamespaceVirtualizationContext,
                                     backingStoreName,
                                     &placeholderInfo,
                                     sizeof(placeholderInfo));
    }

    free(backingStoreName);

    if (symlinkTarget != NULL)
    {
        free(symlinkTarget);
    }

    return hr;
}

Menyediakan Isi File

Ketika ProjFS perlu memastikan bahwa file virtual berisi data, seperti ketika aplikasi mencoba membaca dari file, ProjFS memanggil panggilan balik PRJ_GET_FILE_DATA_CB untuk item tersebut untuk meminta penyedia menyediakan konten file. Penyedia mengambil data file dari penyimpanan cadangannya dan menggunakan PrjWriteFileData untuk mengirim data ke sistem file lokal.

Ketika ProjFS memanggil panggilan balik ini, anggota FilePathName dari parameter callbackData memberikan nama file ketika tempat penampungnya dibuat. Artinya, jika file telah diganti namanya sejak tempat penampungnya dibuat, panggilan balik memasok nama asli (pra-ganti nama), bukan nama (pasca-ganti nama) saat ini. Jika perlu, penyedia dapat menggunakan anggota VersionInfo dari parameter callbackData untuk menentukan data file mana yang diminta.

Untuk informasi selengkapnya tentang bagaimana anggota VersionInfo dari PRJ_CALLBACK_DATA dapat digunakan, lihat dokumentasi untuk PRJ_PLACEHOLDER_VERSION_INFO dan topik Menangani Perubahan Tampilan .

Penyedia diizinkan untuk membagi rentang data yang diminta dalam panggilan balik PRJ_GET_FILE_DATA_CB menjadi beberapa panggilan ke PrjWriteFileData, masing-masing menyediakan sebagian dari rentang yang diminta. Namun, penyedia harus menyediakan seluruh rentang yang diminta sebelum menyelesaikan panggilan balik. Misalnya, jika panggilan balik meminta 10 MB data dari byteOffset 0 dengan panjang 10.485.760, penyedia dapat memilih untuk menyediakan data dalam 10 panggilan ke PrjWriteFileData, masing-masing mengirim 1 MB.

Penyedia juga gratis untuk menyediakan lebih dari rentang yang diminta, hingga panjang file. Rentang yang disediakan penyedia harus mencakup rentang yang diminta. Misalnya, jika panggilan balik meminta 1 MB data dari byteOffset 4096 untuk panjang 1.052.672, dan file memiliki ukuran total 10 MB, penyedia dapat memilih untuk mengembalikan 2 MB data mulai dari offset 0.

Pertimbangan Perataan Buffer

ProjFS menggunakan FILE_OBJECT dari pemanggil yang memerlukan data untuk menulis data ke sistem file lokal. Namun ProjFS tidak dapat mengontrol apakah FILE_OBJECT dibuka untuk I/O yang di-buffer atau tidak dibuffer. Jika FILE_OBJECT dibuka untuk I/O yang tidak dibuffer, baca dan tulis ke file harus mematuhi persyaratan penyelarasan tertentu. Penyedia dapat memenuhi persyaratan penyelarasan tersebut dengan melakukan dua hal:

  1. Gunakan PrjAllocateAlignedBuffer untuk mengalokasikan buffer untuk meneruskan parameter bufferPrjWriteFileData.
  2. Pastikan bahwa parameter byteOffset dan panjangPrjWriteFileData adalah kelipatan bilangan bulat dari persyaratan perataan perangkat penyimpanan (perhatikan bahwa parameter panjang tidak harus memenuhi persyaratan ini jikapanjangbyteOffset + sama dengan akhir file). Penyedia dapat menggunakan PrjGetVirtualizationInstanceInfo untuk mengambil persyaratan penyelarasan perangkat penyimpanan.

ProjFS menyerahkannya kepada penyedia untuk menghitung penyelarasan yang tepat. Ini karena saat memproses panggilan balik PRJ_GET_FILE_DATA_CB , penyedia dapat memilih untuk mengembalikan data yang diminta di beberapa panggilan PrjWriteFileData , masing-masing mengembalikan bagian dari total data yang diminta, sebelum menyelesaikan panggilan balik.

Jika penyedia akan menggunakan satu panggilan ke PrjWriteFileData untuk menulis seluruh file, yaitu dari byteOffset = 0 hingga panjang = ukuran file, atau untuk mengembalikan rentang persis yang diminta dalam panggilan balik PRJ_GET_FILE_DATA_CB , penyedia tidak perlu melakukan perhitungan perataan apa pun. Namun, ia masih harus menggunakan PrjAllocateAlignedBuffer untuk memastikan bahwa buffer memenuhi persyaratan penyelarasan perangkat penyimpanan.

Lihat topik Buffering File untuk informasi selengkapnya tentang I/O yang di-buffer vs. tidak dibuffer.

//  BlockAlignTruncate(): Aligns P on the previous V boundary (V must be != 0).
#define BlockAlignTruncate(P,V) ((P) & (0-((UINT64)(V))))

// This sample illustrates both returning the entire requested range in a
// single call to PrjWriteFileData(), and splitting it up into smaller 
// units.  Note that the provider must return all the requested data before
// completing the PRJ_GET_FILE_DATA_CB callback with S_OK.
HRESULT
MyGetFileDataCallback(
    _In_ const PRJ_CALLBACK_DATA* callbackData,
    _In_ UINT64 byteOffset,
    _In_ UINT32 length
    )
{
    HRESULT hr;

    // For the purposes of this sample our provider has a 1 MB limit to how
    // much data it can return at once (perhaps its backing store imposes such
    // a limit).
    UINT64 writeStartOffset;
    UINT32 writeLength;
    if (length <= 1024*1024)
    {
        // The range requested in the callback is less than 1MB, so we can return
        // the data in a single call, without doing any alignment calculations.
        writeStartOffset = byteOffset;
        writeLength = length;
    }
    else
    {
        // The range requested is more than 1MB.  Retrieve the device alignment
        // and calculate a transfer size that conforms to the device alignment and
        // is <= 1MB.
        PRJ_VIRTUALIZATION_INSTANCE_INFO instanceInfo;
        UINT32 infoSize = sizeof(instanceInfo);
        hr = PrjGetVirtualizationInstanceInfo(callbackData->NamespaceVirtualizationContext,
                                              &infoSize,
                                              &instanceInfo);

        if (FAILED(hr))
        {
            return hr;
        }

        // The first transfer will start at the beginning of the requested range,
        // which is guaranteed to have the correct alignment.
        writeStartOffset = byteOffset;

        // Ensure our transfer size is aligned to the device alignment, and is
        // no larger than 1 MB (note this assumes the device alignment is less
        // than 1 MB).
        UINT64 writeEndOffset = BlockAlignTruncate(writeStartOffset + 1024*1024,
                                                   instanceInfo->WriteAlignment);
        assert(writeEndOffset > 0);
        assert(writeEndOffset > writeStartOffset);

        writeLength = writeEndOffset - writeStartOffset;
    }

    // Allocate a buffer that adheres to the needed memory alignment.
    void* writeBuffer = NULL;
    writeBuffer = PrjAllocateAlignedBuffer(callbackData->NamespaceVirtualizationContext,
                                           writeLength);

    if (writeBuffer == NULL)
    {
        return E_OUTOFMEMORY;
    }

    do
    {
        // MyGetFileDataFromStore is a routine the provider might implement to copy
        // data for the specified file from the provider's backing store to a
        // buffer.  The routine finds the file located at callbackData->FilePathName
        // and copies writeLength bytes of its data, starting at writeStartOffset,
        // to the buffer pointed to by writeBuffer.
        hr = MyGetFileDataFromStore(callbackData->FilePathName,
                                    writeStartOffset,
                                    writeLength,
                                    writeBuffer);

        if (FAILED(hr))
        {
            PrjFreeAlignedBuffer(writeBuffer);
            return hr;
        }

        // Write the data to the file in the local file system.
        hr = PrjWriteFileData(callbackData->NamespaceVirtualizationContext,
                              callbackData->DataStreamId,
                              writeBuffer,
                              writeStartOffset,
                              writeLength);

        if (FAILED(hr))
        {
            PrjFreeAlignedBuffer(writeBuffer);
            return hr;
        }

        // The length parameter to the callback is guaranteed to be either
        // correctly aligned or to result in a write to the end of the file.
        length -= writeLength;
        if (length < writeLength)
        {
            writeLength = length;
        }
    }
    while (writeLength > 0);

    PrjFreeAlignedBuffer(writeBuffer);
    return hr;
}