Envoi de données de fichier

Lorsqu’un fournisseur crée pour la première fois une racine de virtualisation, elle est vide sur le système local. Autrement dit, aucun des éléments du magasin de données de stockage n’a encore été mis en cache sur le disque. Lorsque les éléments sont ouverts, ProjFS demande des informations au fournisseur pour permettre la création d’espaces réservés pour ces éléments dans le système de fichiers local. Lorsque le contenu de l’élément est accessible, ProjFS demande ce contenu auprès du fournisseur. Le résultat est que du point de vue de l’utilisateur, les fichiers et répertoires virtualisés semblent similaires aux fichiers et répertoires normaux qui résident déjà sur le système de fichiers local.

Création d’espace réservé

Lorsqu’une application tente d’ouvrir un handle dans un fichier virtualisé, ProjFS appelle le rappel PRJ_GET_PLACEHOLDER_INFO_CB pour chaque élément du chemin d’accès qui n’existe pas encore sur le disque. Par exemple, si une application tente d’ouvrir C:\virtRoot\dir1\dir2\file.txt, mais que seul le chemin C:\virtRoot\dir1 existe sur le disque, le fournisseur reçoit un rappel pour C:\virtRoot\dir1\dir2, puis pour C:\virtRoot\dir1\dir2\file.txt.

Lorsque ProjFS appelle le rappel PRJ_GET_PLACEHOLDER_INFO_CB du fournisseur, le fournisseur effectue les actions suivantes :

  1. Le fournisseur détermine si le nom demandé existe dans son magasin de stockage. Le fournisseur doit utiliser PrjFileNameCompare comme routine de comparaison lors de la recherche dans son magasin de stockage pour déterminer si le nom demandé existe dans le magasin de stockage. Si ce n’est pas le cas, le fournisseur retourne HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) à partir du rappel.

  2. Si le nom demandé existe dans le magasin de stockage, le fournisseur remplit une structure de PRJ_PLACEHOLDER_INFO avec les métadonnées du système de fichiers de l’élément et appelle PrjWritePlaceholderInfo pour envoyer les données à ProjFS. ProjFS utilisera ces informations pour créer un espace réservé dans le système de fichiers local pour l’élément.

    ProjFS utilisera les indicateurs FILE_ATTRIBUTE définis par le fournisseur dans le membre FileBasicInfo.FileAttributes de PRJ_PLACEHOLDER_INFO à l’exception de FILE_ATTRIBUTE_DIRECTORY ; il définit la valeur correcte pour FILE_ATTRIBUTE_DIRECTORY dans le membre FileBasicInfo.FileAttributes en fonction de la valeur fournie du membre FileBasicInfo.IsDirectory .

    Si le magasin de stockage prend en charge les liens symboliques, le fournisseur doit utiliser PrjWritePlaceholderInfo2 pour envoyer les données d’espace réservé à ProjFS. PrjWritePlaceholderInfo2 prend en charge une entrée de mémoire tampon supplémentaire qui permet au fournisseur de spécifier que l’espace réservé est un lien symbolique et quelle est sa cible. Sinon, il se comporte comme décrit ci-dessus pour PrjWritePlaceholderInfo. L’exemple suivant montre comment utiliser PrjWritePlaceholderInfo2 pour prendre en charge les liens symboliques.

    Notez que PrjWritePlaceholderInfo2 est pris en charge à partir de Windows 10, version 2004. Un fournisseur doit rechercher l’existence de PrjWritePlaceholderInfo2, pour instance à l’aide de 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;
}

Fournir le contenu du fichier

Lorsque ProjFS doit s’assurer qu’un fichier virtualisé contient des données, par exemple lorsqu’une application tente de lire à partir du fichier, ProjFS appelle le rappel PRJ_GET_FILE_DATA_CB de cet élément pour demander au fournisseur de fournir le contenu du fichier. Le fournisseur récupère les données du fichier à partir de son magasin de stockage et utilise PrjWriteFileData pour envoyer les données au système de fichiers local.

Lorsque ProjFS appelle ce rappel, le membre FilePathName du paramètre callbackData fournit le nom du fichier lors de la création de son espace réservé. Autrement dit, si le fichier a été renommé depuis la création de son espace réservé, le rappel fournit le nom d’origine (pré-renommage), et non le nom actuel (après le renommage). Si nécessaire, le fournisseur peut utiliser le membre VersionInfo du paramètre callbackData pour déterminer les données du fichier qui sont demandées.

Pour plus d’informations sur la façon dont le membre VersionInfo de PRJ_CALLBACK_DATA peut être utilisé, consultez la documentation pour PRJ_PLACEHOLDER_VERSION_INFO et la rubrique Gestion des modifications de l’affichage .

Le fournisseur est autorisé à fractionner la plage de données demandée dans le PRJ_GET_FILE_DATA_CB rappel en plusieurs appels à PrjWriteFileData, chacun fournissant une partie de la plage demandée. Toutefois, le fournisseur doit fournir la totalité de la plage demandée avant d’effectuer le rappel. Par exemple, si le rappel demande 10 Mo de données à byteOffset 0 pour une longueur de 10 485 760, le fournisseur peut choisir de fournir les données en 10 appels à PrjWriteFileData, chacun en envoyant 1 Mo.

Le fournisseur est également libre de fournir plus que la plage demandée, jusqu’à la longueur du fichier. La plage que le fournisseur fournit doit couvrir la plage demandée. Par exemple, si le rappel demande 1 Mo de données à byteOffset 4096 pour une longueur de 1 052 672 et que le fichier a une taille totale de 10 Mo, le fournisseur peut choisir de retourner 2 Mo de données à partir du décalage 0.

Considérations relatives à l’alignement de la mémoire tampon

ProjFS utilise le FILE_OBJECT de l’appelant qui a besoin des données pour écrire les données dans le système de fichiers local. Toutefois, ProjFS ne peut pas contrôler si cette FILE_OBJECT a été ouverte pour les E/S mises en mémoire tampon ou sans mise en mémoire tampon. Si le FILE_OBJECT a été ouvert pour les E/S sans débogage, les lectures et écritures dans le fichier doivent respecter certaines exigences d’alignement. Le fournisseur peut répondre à ces exigences d’alignement en effectuant deux opérations :

  1. Utilisez PrjAllocateAlignedBuffer pour allouer la mémoire tampon pour passer le paramètre de mémoire tampon de PrjWriteFileData.
  2. Vérifiez que les paramètres byteOffset et length de PrjWriteFileData sont des multiples entiers de l’exigence d’alignement du périphérique de stockage (notez que le paramètre length n’a pas besoin de respecter cette exigence si lalongueurbyteOffset + est égale à la fin du fichier). Le fournisseur peut utiliser PrjGetVirtualizationInstanceInfo pour récupérer les exigences d’alignement du périphérique de stockage.

ProjFS laisse au fournisseur le devoir de calculer l’alignement approprié. En effet, lors du traitement d’un rappel de PRJ_GET_FILE_DATA_CB , le fournisseur peut choisir de retourner les données demandées sur plusieurs appels PrjWriteFileData , chacun retournant une partie du total des données demandées, avant d’effectuer le rappel.

Si le fournisseur va utiliser un seul appel à PrjWriteFileData pour écrire l’intégralité du fichier, c’est-à-dire de byteOffset = 0 à longueur = taille du fichier, ou pour retourner la plage exacte demandée dans le rappel PRJ_GET_FILE_DATA_CB , le fournisseur n’a pas besoin d’effectuer de calculs d’alignement. Toutefois, il doit toujours utiliser PrjAllocateAlignedBuffer pour s’assurer que la mémoire tampon répond aux exigences d’alignement du périphérique de stockage.

Consultez la rubrique Mise en mémoire tampon des fichiers pour plus d’informations sur les E/S mises en mémoire tampon par rapport aux E/S non mises en mémoire tampon.

//  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;
}