/** @file
  FUSE_RENAME2 wrapper for the Virtio Filesystem device.

  Copyright (C) 2020, Red Hat, Inc.

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Library/BaseLib.h> // AsciiStrSize()

#include "VirtioFsDxe.h"

/**
  Rename a regular file or a directory, by sending the FUSE_RENAME2 request to
  the Virtio Filesystem device. If the new filename exists, the request will
  fail.

  The function may only be called after VirtioFsFuseInitSession() returns
  successfully and before VirtioFsUninit() is called.

  @param[in,out] VirtioFs     The Virtio Filesystem device to send the
                              FUSE_RENAME2 request to. On output, the FUSE
                              request counter "VirtioFs->RequestId" will have
                              been incremented.

  @param[in] OldParentNodeId  The inode number of the directory in which
                              OldName should be removed.

  @param[in] OldName          The single-component filename to remove in the
                              directory identified by OldParentNodeId.

  @param[in] NewParentNodeId  The inode number of the directory in which
                              NewName should be created, such that on
                              successful return, (NewParentNodeId, NewName)
                              refer to the same inode as (OldParentNodeId,
                              OldName) did on entry.

  @param[in] NewName          The single-component filename to create in the
                              directory identified by NewParentNodeId.

  @retval EFI_SUCCESS  The file or directory has been renamed.

  @return              The "errno" value mapped to an EFI_STATUS code, if the
                       Virtio Filesystem device explicitly reported an error.

  @return              Error codes propagated from VirtioFsSgListsValidate(),
                       VirtioFsFuseNewRequest(), VirtioFsSgListsSubmit(),
                       VirtioFsFuseCheckResponse().
**/
EFI_STATUS
VirtioFsFuseRename (
  IN OUT VIRTIO_FS  *VirtioFs,
  IN     UINT64     OldParentNodeId,
  IN     CHAR8      *OldName,
  IN     UINT64     NewParentNodeId,
  IN     CHAR8      *NewName
  )
{
  VIRTIO_FS_FUSE_REQUEST          CommonReq;
  VIRTIO_FS_FUSE_RENAME2_REQUEST  Rename2Req;
  VIRTIO_FS_IO_VECTOR             ReqIoVec[4];
  VIRTIO_FS_SCATTER_GATHER_LIST   ReqSgList;
  VIRTIO_FS_FUSE_RESPONSE         CommonResp;
  VIRTIO_FS_IO_VECTOR             RespIoVec[1];
  VIRTIO_FS_SCATTER_GATHER_LIST   RespSgList;
  EFI_STATUS                      Status;

  //
  // Set up the scatter-gather lists.
  //
  ReqIoVec[0].Buffer = &CommonReq;
  ReqIoVec[0].Size   = sizeof CommonReq;
  ReqIoVec[1].Buffer = &Rename2Req;
  ReqIoVec[1].Size   = sizeof Rename2Req;
  ReqIoVec[2].Buffer = OldName;
  ReqIoVec[2].Size   = AsciiStrSize (OldName);
  ReqIoVec[3].Buffer = NewName;
  ReqIoVec[3].Size   = AsciiStrSize (NewName);
  ReqSgList.IoVec    = ReqIoVec;
  ReqSgList.NumVec   = ARRAY_SIZE (ReqIoVec);

  RespIoVec[0].Buffer = &CommonResp;
  RespIoVec[0].Size   = sizeof CommonResp;
  RespSgList.IoVec    = RespIoVec;
  RespSgList.NumVec   = ARRAY_SIZE (RespIoVec);

  //
  // Validate the scatter-gather lists; calculate the total transfer sizes.
  //
  Status = VirtioFsSgListsValidate (VirtioFs, &ReqSgList, &RespSgList);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Populate the common request header.
  //
  Status = VirtioFsFuseNewRequest (
             VirtioFs,
             &CommonReq,
             ReqSgList.TotalSize,
             VirtioFsFuseOpRename2,
             OldParentNodeId
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Populate the FUSE_RENAME2-specific fields.
  //
  Rename2Req.NewDir  = NewParentNodeId;
  Rename2Req.Flags   = VIRTIO_FS_FUSE_RENAME2_REQ_F_NOREPLACE;
  Rename2Req.Padding = 0;

  //
  // Submit the request.
  //
  Status = VirtioFsSgListsSubmit (VirtioFs, &ReqSgList, &RespSgList);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Verify the response (all response buffers are fixed size).
  //
  Status = VirtioFsFuseCheckResponse (&RespSgList, CommonReq.Unique, NULL);
  if (Status == EFI_DEVICE_ERROR) {
    DEBUG ((
      DEBUG_ERROR,
      "%a: Label=\"%s\" OldParentNodeId=%Lu OldName=\"%a\" "
      "NewParentNodeId=%Lu NewName=\"%a\" Errno=%d\n",
      __func__,
      VirtioFs->Label,
      OldParentNodeId,
      OldName,
      NewParentNodeId,
      NewName,
      CommonResp.Error
      ));
    Status = VirtioFsErrnoToEfiStatus (CommonResp.Error);
  }

  return Status;
}
