SPDX-License-Identifier: BSD-2-Clause-Patent\r
**/\r
\r
+#include <Library/BaseLib.h> // StrLen()\r
+#include <Library/BaseMemoryLib.h> // CopyMem()\r
+#include <Library/MemoryAllocationLib.h> // AllocatePool()\r
+#include <Library/TimeBaseLib.h> // EpochToEfiTime()\r
#include <Library/VirtioLib.h> // Virtio10WriteFeatures()\r
\r
#include "VirtioFsDxe.h"\r
to send.\r
\r
@param[in] NodeId The inode number of the file that the request refers\r
- to.\r
+ to. When Opcode is VirtioFsFuseOpInit, NodeId is\r
+ ignored by the Virtio Filesystem device.\r
\r
@retval EFI_INVALID_PARAMETER RequestSize is smaller than\r
sizeof(VIRTIO_FS_FUSE_REQUEST).\r
IN OUT VIRTIO_FS *VirtioFs,\r
OUT VIRTIO_FS_FUSE_REQUEST *Request,\r
IN UINT32 RequestSize,\r
- IN UINT32 Opcode,\r
+ IN VIRTIO_FS_FUSE_OPCODE Opcode,\r
IN UINT64 NodeId\r
)\r
{\r
\r
return EFI_SUCCESS;\r
}\r
+\r
+/**\r
+ An ad-hoc function for mapping FUSE (well, Linux) "errno" values to\r
+ EFI_STATUS.\r
+\r
+ @param[in] Errno The "VIRTIO_FS_FUSE_RESPONSE.Error" value, returned by the\r
+ Virtio Filesystem device. The value is expected to be\r
+ negative.\r
+\r
+ @return An EFI_STATUS error code that's deemed a passable\r
+ mapping for the Errno value.\r
+\r
+ @retval EFI_DEVICE_ERROR Fallback EFI_STATUS code for unrecognized Errno\r
+ values.\r
+**/\r
+EFI_STATUS\r
+VirtioFsErrnoToEfiStatus (\r
+ IN INT32 Errno\r
+ )\r
+{\r
+ switch (Errno) {\r
+ case -1: // EPERM Operation not permitted\r
+ return EFI_SECURITY_VIOLATION;\r
+\r
+ case -2: // ENOENT No such file or directory\r
+ case -3: // ESRCH No such process\r
+ case -6: // ENXIO No such device or address\r
+ case -10: // ECHILD No child processes\r
+ case -19: // ENODEV No such device\r
+ case -49: // EUNATCH Protocol driver not attached\r
+ case -65: // ENOPKG Package not installed\r
+ case -79: // ELIBACC Can not access a needed shared library\r
+ case -126: // ENOKEY Required key not available\r
+ return EFI_NOT_FOUND;\r
+\r
+ case -4: // EINTR Interrupted system call\r
+ case -11: // EAGAIN, EWOULDBLOCK Resource temporarily unavailable\r
+ case -16: // EBUSY Device or resource busy\r
+ case -26: // ETXTBSY Text file busy\r
+ case -35: // EDEADLK, EDEADLOCK Resource deadlock avoided\r
+ case -39: // ENOTEMPTY Directory not empty\r
+ case -42: // ENOMSG No message of desired type\r
+ case -61: // ENODATA No data available\r
+ case -85: // ERESTART Interrupted system call should be restarted\r
+ return EFI_NOT_READY;\r
+\r
+ case -5: // EIO Input/output error\r
+ case -45: // EL2NSYNC Level 2 not synchronized\r
+ case -46: // EL3HLT Level 3 halted\r
+ case -47: // EL3RST Level 3 reset\r
+ case -51: // EL2HLT Level 2 halted\r
+ case -121: // EREMOTEIO Remote I/O error\r
+ case -133: // EHWPOISON Memory page has hardware error\r
+ return EFI_DEVICE_ERROR;\r
+\r
+ case -7: // E2BIG Argument list too long\r
+ case -36: // ENAMETOOLONG File name too long\r
+ case -90: // EMSGSIZE Message too long\r
+ return EFI_BAD_BUFFER_SIZE;\r
+\r
+ case -8: // ENOEXEC Exec format error\r
+ case -15: // ENOTBLK Block device required\r
+ case -18: // EXDEV Invalid cross-device link\r
+ case -20: // ENOTDIR Not a directory\r
+ case -21: // EISDIR Is a directory\r
+ case -25: // ENOTTY Inappropriate ioctl for device\r
+ case -27: // EFBIG File too large\r
+ case -29: // ESPIPE Illegal seek\r
+ case -38: // ENOSYS Function not implemented\r
+ case -59: // EBFONT Bad font file format\r
+ case -60: // ENOSTR Device not a stream\r
+ case -83: // ELIBEXEC Cannot exec a shared library directly\r
+ case -88: // ENOTSOCK Socket operation on non-socket\r
+ case -91: // EPROTOTYPE Protocol wrong type for socket\r
+ case -92: // ENOPROTOOPT Protocol not available\r
+ case -93: // EPROTONOSUPPORT Protocol not supported\r
+ case -94: // ESOCKTNOSUPPORT Socket type not supported\r
+ case -95: // ENOTSUP, EOPNOTSUPP Operation not supported\r
+ case -96: // EPFNOSUPPORT Protocol family not supported\r
+ case -97: // EAFNOSUPPORT Address family not supported by protocol\r
+ case -99: // EADDRNOTAVAIL Cannot assign requested address\r
+ case -118: // ENOTNAM Not a XENIX named type file\r
+ case -120: // EISNAM Is a named type file\r
+ case -124: // EMEDIUMTYPE Wrong medium type\r
+ return EFI_UNSUPPORTED;\r
+\r
+ case -9: // EBADF Bad file descriptor\r
+ case -14: // EFAULT Bad address\r
+ case -44: // ECHRNG Channel number out of range\r
+ case -48: // ELNRNG Link number out of range\r
+ case -53: // EBADR Invalid request descriptor\r
+ case -56: // EBADRQC Invalid request code\r
+ case -57: // EBADSLT Invalid slot\r
+ case -76: // ENOTUNIQ Name not unique on network\r
+ case -84: // EILSEQ Invalid or incomplete multibyte or wide character\r
+ return EFI_NO_MAPPING;\r
+\r
+ case -12: // ENOMEM Cannot allocate memory\r
+ case -23: // ENFILE Too many open files in system\r
+ case -24: // EMFILE Too many open files\r
+ case -31: // EMLINK Too many links\r
+ case -37: // ENOLCK No locks available\r
+ case -40: // ELOOP Too many levels of symbolic links\r
+ case -50: // ENOCSI No CSI structure available\r
+ case -55: // ENOANO No anode\r
+ case -63: // ENOSR Out of streams resources\r
+ case -82: // ELIBMAX Attempting to link in too many shared libraries\r
+ case -87: // EUSERS Too many users\r
+ case -105: // ENOBUFS No buffer space available\r
+ case -109: // ETOOMANYREFS Too many references: cannot splice\r
+ case -119: // ENAVAIL No XENIX semaphores available\r
+ case -122: // EDQUOT Disk quota exceeded\r
+ return EFI_OUT_OF_RESOURCES;\r
+\r
+ case -13: // EACCES Permission denied\r
+ return EFI_ACCESS_DENIED;\r
+\r
+ case -17: // EEXIST File exists\r
+ case -98: // EADDRINUSE Address already in use\r
+ case -106: // EISCONN Transport endpoint is already connected\r
+ case -114: // EALREADY Operation already in progress\r
+ case -115: // EINPROGRESS Operation now in progress\r
+ return EFI_ALREADY_STARTED;\r
+\r
+ case -22: // EINVAL Invalid argument\r
+ case -33: // EDOM Numerical argument out of domain\r
+ return EFI_INVALID_PARAMETER;\r
+\r
+ case -28: // ENOSPC No space left on device\r
+ case -54: // EXFULL Exchange full\r
+ return EFI_VOLUME_FULL;\r
+\r
+ case -30: // EROFS Read-only file system\r
+ return EFI_WRITE_PROTECTED;\r
+\r
+ case -32: // EPIPE Broken pipe\r
+ case -43: // EIDRM Identifier removed\r
+ case -67: // ENOLINK Link has been severed\r
+ case -68: // EADV Advertise error\r
+ case -69: // ESRMNT Srmount error\r
+ case -70: // ECOMM Communication error on send\r
+ case -73: // EDOTDOT RFS specific error\r
+ case -78: // EREMCHG Remote address changed\r
+ case -86: // ESTRPIPE Streams pipe error\r
+ case -102: // ENETRESET Network dropped connection on reset\r
+ case -103: // ECONNABORTED Software caused connection abort\r
+ case -104: // ECONNRESET Connection reset by peer\r
+ case -116: // ESTALE Stale file handle\r
+ case -125: // ECANCELED Operation canceled\r
+ case -128: // EKEYREVOKED Key has been revoked\r
+ case -129: // EKEYREJECTED Key was rejected by service\r
+ case -130: // EOWNERDEAD Owner died\r
+ case -131: // ENOTRECOVERABLE State not recoverable\r
+ return EFI_ABORTED;\r
+\r
+ case -34: // ERANGE Numerical result out of range\r
+ case -75: // EOVERFLOW Value too large for defined data type\r
+ return EFI_BUFFER_TOO_SMALL;\r
+\r
+ case -52: // EBADE Invalid exchange\r
+ case -108: // ESHUTDOWN Cannot send after transport endpoint shutdown\r
+ case -111: // ECONNREFUSED Connection refused\r
+ return EFI_END_OF_FILE;\r
+\r
+ case -62: // ETIME Timer expired\r
+ case -110: // ETIMEDOUT Connection timed out\r
+ case -127: // EKEYEXPIRED Key has expired\r
+ return EFI_TIMEOUT;\r
+\r
+ case -64: // ENONET Machine is not on the network\r
+ case -66: // EREMOTE Object is remote\r
+ case -72: // EMULTIHOP Multihop attempted\r
+ case -100: // ENETDOWN Network is down\r
+ case -101: // ENETUNREACH Network is unreachable\r
+ case -112: // EHOSTDOWN Host is down\r
+ case -113: // EHOSTUNREACH No route to host\r
+ case -123: // ENOMEDIUM No medium found\r
+ case -132: // ERFKILL Operation not possible due to RF-kill\r
+ return EFI_NO_MEDIA;\r
+\r
+ case -71: // EPROTO Protocol error\r
+ return EFI_PROTOCOL_ERROR;\r
+\r
+ case -74: // EBADMSG Bad message\r
+ case -77: // EBADFD File descriptor in bad state\r
+ case -80: // ELIBBAD Accessing a corrupted shared library\r
+ case -81: // ELIBSCN .lib section in a.out corrupted\r
+ case -117: // EUCLEAN Structure needs cleaning\r
+ return EFI_VOLUME_CORRUPTED;\r
+\r
+ case -89: // EDESTADDRREQ Destination address required\r
+ case -107: // ENOTCONN Transport endpoint is not connected\r
+ return EFI_NOT_STARTED;\r
+\r
+ default:\r
+ break;\r
+ }\r
+\r
+ return EFI_DEVICE_ERROR;\r
+}\r
+\r
+//\r
+// Parser states for canonicalizing a POSIX pathname.\r
+//\r
+typedef enum {\r
+ ParserInit, // just starting\r
+ ParserEnd, // finished\r
+ ParserSlash, // slash(es) seen\r
+ ParserDot, // one dot seen since last slash\r
+ ParserDotDot, // two dots seen since last slash\r
+ ParserNormal, // a different sequence seen\r
+} PARSER_STATE;\r
+\r
+/**\r
+ Strip the trailing slash from the parser's output buffer, unless the trailing\r
+ slash stands for the root directory.\r
+\r
+ @param[in] Buffer The parser's output buffer. Only used for\r
+ sanity-checking.\r
+\r
+ @param[in,out] Position On entry, points at the next character to produce\r
+ (i.e., right past the end of the output written by\r
+ the parser thus far). The last character in the\r
+ parser's output buffer is a slash. On return, the\r
+ slash is stripped, by decrementing Position by one.\r
+ If this action would remove the slash character\r
+ standing for the root directory, then the function\r
+ has no effect.\r
+**/\r
+STATIC\r
+VOID\r
+ParserStripSlash (\r
+ IN CHAR8 *Buffer,\r
+ IN OUT UINTN *Position\r
+ )\r
+{\r
+ ASSERT (*Position >= 1);\r
+ ASSERT (Buffer[*Position - 1] == '/');\r
+ if (*Position == 1) {\r
+ return;\r
+ }\r
+ (*Position)--;\r
+}\r
+\r
+/**\r
+ Produce one character in the parser's output buffer.\r
+\r
+ @param[out] Buffer The parser's output buffer. On return, Char8 will\r
+ have been written.\r
+\r
+ @param[in,out] Position On entry, points at the next character to produce\r
+ (i.e., right past the end of the output written by\r
+ the parser thus far). On return, Position is\r
+ incremented by one.\r
+\r
+ @param[in] Size Total allocated size of the parser's output buffer.\r
+ Used for sanity-checking.\r
+\r
+ @param[in] Char8 The character to place in the output buffer.\r
+**/\r
+STATIC\r
+VOID\r
+ParserCopy (\r
+ OUT CHAR8 *Buffer,\r
+ IN OUT UINTN *Position,\r
+ IN UINTN Size,\r
+ IN CHAR8 Char8\r
+ )\r
+{\r
+ ASSERT (*Position < Size);\r
+ Buffer[(*Position)++] = Char8;\r
+}\r
+\r
+/**\r
+ Rewind the last single-dot in the parser's output buffer.\r
+\r
+ @param[in] Buffer The parser's output buffer. Only used for\r
+ sanity-checking.\r
+\r
+ @param[in,out] Position On entry, points at the next character to produce\r
+ (i.e., right past the end of the output written by\r
+ the parser thus far); the parser's output buffer\r
+ ends with the characters ('/', '.'). On return, the\r
+ dot is rewound by decrementing Position by one; a\r
+ slash character will reside at the new end of the\r
+ parser's output buffer.\r
+**/\r
+STATIC\r
+VOID\r
+ParserRewindDot (\r
+ IN CHAR8 *Buffer,\r
+ IN OUT UINTN *Position\r
+ )\r
+{\r
+ ASSERT (*Position >= 2);\r
+ ASSERT (Buffer[*Position - 1] == '.');\r
+ ASSERT (Buffer[*Position - 2] == '/');\r
+ (*Position)--;\r
+}\r
+\r
+/**\r
+ Rewind the last dot-dot in the parser's output buffer.\r
+\r
+ @param[in] Buffer The parser's output buffer. Only used for\r
+ sanity-checking.\r
+\r
+ @param[in,out] Position On entry, points at the next character to produce\r
+ (i.e., right past the end of the output written by\r
+ the parser thus far); the parser's output buffer\r
+ ends with the characters ('/', '.', '.'). On return,\r
+ the ('.', '.') pair is rewound unconditionally, by\r
+ decrementing Position by two; a slash character\r
+ resides at the new end of the parser's output\r
+ buffer.\r
+\r
+ If this slash character stands for the root\r
+ directory, then RootEscape is set to TRUE.\r
+\r
+ Otherwise (i.e., if this slash character is not the\r
+ one standing for the root directory), then the slash\r
+ character, and the pathname component preceding it,\r
+ are removed by decrementing Position further. In\r
+ this case, the slash character preceding the removed\r
+ pathname component will reside at the new end of the\r
+ parser's output buffer.\r
+\r
+ @param[out] RootEscape Set to TRUE on output if the dot-dot component tries\r
+ to escape the root directory, as described above.\r
+ Otherwise, RootEscape is not modified.\r
+**/\r
+STATIC\r
+VOID\r
+ParserRewindDotDot (\r
+ IN CHAR8 *Buffer,\r
+ IN OUT UINTN *Position,\r
+ OUT BOOLEAN *RootEscape\r
+\r
+ )\r
+{\r
+ ASSERT (*Position >= 3);\r
+ ASSERT (Buffer[*Position - 1] == '.');\r
+ ASSERT (Buffer[*Position - 2] == '.');\r
+ ASSERT (Buffer[*Position - 3] == '/');\r
+ (*Position) -= 2;\r
+\r
+ if (*Position == 1) {\r
+ //\r
+ // Root directory slash reached; don't try to climb higher.\r
+ //\r
+ *RootEscape = TRUE;\r
+ return;\r
+ }\r
+\r
+ //\r
+ // Skip slash.\r
+ //\r
+ (*Position)--;\r
+ //\r
+ // Scan until next slash to the left.\r
+ //\r
+ do {\r
+ ASSERT (*Position > 0);\r
+ (*Position)--;\r
+ } while (Buffer[*Position] != '/');\r
+ (*Position)++;\r
+}\r
+\r
+/**\r
+ Append the UEFI-style RhsPath16 to the POSIX-style, canonical format\r
+ LhsPath8. Output the POSIX-style, canonical format result in ResultPath, as a\r
+ dynamically allocated string.\r
+\r
+ Canonicalization (aka sanitization) establishes the following properties:\r
+ - ResultPath is absolute (starts with "/"),\r
+ - dot (.) and dot-dot (..) components are resolved/eliminated in ResultPath,\r
+ with the usual semantics,\r
+ - ResultPath uses forward slashes,\r
+ - sequences of slashes are squashed in ResultPath,\r
+ - the printable ASCII character set covers ResultPath,\r
+ - CHAR8 encoding is used in ResultPath,\r
+ - no trailing slash present in ResultPath except for the standalone root\r
+ directory,\r
+ - the length of ResultPath is at most VIRTIO_FS_MAX_PATHNAME_LENGTH.\r
+\r
+ Any dot-dot in RhsPath16 that would remove the root directory is dropped, and\r
+ reported through RootEscape, without failing the function call.\r
+\r
+ @param[in] LhsPath8 Identifies the base directory. The caller is\r
+ responsible for ensuring that LhsPath8 conform to\r
+ the above canonical pathname format on entry.\r
+\r
+ @param[in] RhsPath16 Identifies the desired file with a UEFI-style CHAR16\r
+ pathname. If RhsPath16 starts with a backslash, then\r
+ RhsPath16 is considered absolute, and LhsPath8 is\r
+ ignored; RhsPath16 is sanitized in isolation, for\r
+ producing ResultPath8. Otherwise (i.e., if RhsPath16\r
+ is relative), RhsPath16 is transliterated to CHAR8,\r
+ and naively appended to LhsPath8. The resultant\r
+ fused pathname is then sanitized, to produce\r
+ ResultPath8.\r
+\r
+ @param[out] ResultPath8 The POSIX-style, canonical format pathname that\r
+ leads to the file desired by the caller. After use,\r
+ the caller is responsible for freeing ResultPath8.\r
+\r
+ @param[out] RootEscape Set to TRUE if at least one dot-dot component in\r
+ RhsPath16 attempted to escape the root directory;\r
+ set to FALSE otherwise.\r
+\r
+ @retval EFI_SUCCESS ResultPath8 has been produced. RootEscape has\r
+ been output.\r
+\r
+ @retval EFI_INVALID_PARAMETER RhsPath16 is zero-length.\r
+\r
+ @retval EFI_INVALID_PARAMETER RhsPath16 failed the\r
+ VIRTIO_FS_MAX_PATHNAME_LENGTH check.\r
+\r
+ @retval EFI_OUT_OF_RESOURCES Memory allocation failed.\r
+\r
+ @retval EFI_OUT_OF_RESOURCES ResultPath8 would have failed the\r
+ VIRTIO_FS_MAX_PATHNAME_LENGTH check.\r
+\r
+ @retval EFI_UNSUPPORTED RhsPath16 contains a character that either\r
+ falls outside of the printable ASCII set, or\r
+ is a forward slash.\r
+**/\r
+EFI_STATUS\r
+VirtioFsAppendPath (\r
+ IN CHAR8 *LhsPath8,\r
+ IN CHAR16 *RhsPath16,\r
+ OUT CHAR8 **ResultPath8,\r
+ OUT BOOLEAN *RootEscape\r
+ )\r
+{\r
+ UINTN RhsLen;\r
+ CHAR8 *RhsPath8;\r
+ UINTN Idx;\r
+ EFI_STATUS Status;\r
+ UINTN SizeToSanitize;\r
+ CHAR8 *BufferToSanitize;\r
+ CHAR8 *SanitizedBuffer;\r
+ PARSER_STATE State;\r
+ UINTN SanitizedPosition;\r
+\r
+ //\r
+ // Appending an empty pathname is not allowed.\r
+ //\r
+ RhsLen = StrLen (RhsPath16);\r
+ if (RhsLen == 0) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ //\r
+ // Enforce length restriction on RhsPath16.\r
+ //\r
+ if (RhsLen > VIRTIO_FS_MAX_PATHNAME_LENGTH) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ //\r
+ // Transliterate RhsPath16 to RhsPath8 by:\r
+ // - rejecting RhsPath16 if a character outside of printable ASCII is seen,\r
+ // - rejecting RhsPath16 if a forward slash is seen,\r
+ // - replacing backslashes with forward slashes,\r
+ // - casting the characters from CHAR16 to CHAR8.\r
+ //\r
+ RhsPath8 = AllocatePool (RhsLen + 1);\r
+ if (RhsPath8 == NULL) {\r
+ return EFI_OUT_OF_RESOURCES;\r
+ }\r
+ for (Idx = 0; RhsPath16[Idx] != L'\0'; Idx++) {\r
+ if (RhsPath16[Idx] < 0x20 || RhsPath16[Idx] > 0x7E ||\r
+ RhsPath16[Idx] == L'/') {\r
+ Status = EFI_UNSUPPORTED;\r
+ goto FreeRhsPath8;\r
+ }\r
+ RhsPath8[Idx] = (CHAR8)((RhsPath16[Idx] == L'\\') ? L'/' : RhsPath16[Idx]);\r
+ }\r
+ RhsPath8[Idx++] = '\0';\r
+\r
+ //\r
+ // Now prepare the input for the canonicalization (squashing of sequences of\r
+ // forward slashes, and eliminating . (dot) and .. (dot-dot) pathname\r
+ // components).\r
+ //\r
+ // The sanitized path can never be longer than the naive concatenation of the\r
+ // left hand side and right hand side paths, so we'll use the catenated size\r
+ // for allocating the sanitized output too.\r
+ //\r
+ if (RhsPath8[0] == '/') {\r
+ //\r
+ // If the right hand side path is absolute, then it is not appended to the\r
+ // left hand side path -- it *replaces* the left hand side path.\r
+ //\r
+ SizeToSanitize = RhsLen + 1;\r
+ BufferToSanitize = RhsPath8;\r
+ } else {\r
+ //\r
+ // If the right hand side path is relative, then it is appended (naively)\r
+ // to the left hand side.\r
+ //\r
+ UINTN LhsLen;\r
+\r
+ LhsLen = AsciiStrLen (LhsPath8);\r
+ SizeToSanitize = LhsLen + 1 + RhsLen + 1;\r
+ BufferToSanitize = AllocatePool (SizeToSanitize);\r
+ if (BufferToSanitize == NULL) {\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto FreeRhsPath8;\r
+ }\r
+ CopyMem (BufferToSanitize, LhsPath8, LhsLen);\r
+ BufferToSanitize[LhsLen] = '/';\r
+ CopyMem (BufferToSanitize + LhsLen + 1, RhsPath8, RhsLen + 1);\r
+ }\r
+\r
+ //\r
+ // Allocate the output buffer.\r
+ //\r
+ SanitizedBuffer = AllocatePool (SizeToSanitize);\r
+ if (SanitizedBuffer == NULL) {\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto FreeBufferToSanitize;\r
+ }\r
+\r
+ //\r
+ // State machine for parsing the input and producing the canonical output\r
+ // follows.\r
+ //\r
+ *RootEscape = FALSE;\r
+ Idx = 0;\r
+ State = ParserInit;\r
+ SanitizedPosition = 0;\r
+ do {\r
+ CHAR8 Chr8;\r
+\r
+ ASSERT (Idx < SizeToSanitize);\r
+ Chr8 = BufferToSanitize[Idx++];\r
+\r
+ switch (State) {\r
+ case ParserInit: // just starting\r
+ ASSERT (Chr8 == '/');\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserSlash;\r
+ break;\r
+\r
+ case ParserSlash: // slash(es) seen\r
+ switch (Chr8) {\r
+ case '\0':\r
+ ParserStripSlash (SanitizedBuffer, &SanitizedPosition);\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserEnd;\r
+ break;\r
+ case '/':\r
+ //\r
+ // skip & stay in same state\r
+ //\r
+ break;\r
+ case '.':\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserDot;\r
+ break;\r
+ default:\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserNormal;\r
+ break;\r
+ }\r
+ break;\r
+\r
+ case ParserDot: // one dot seen since last slash\r
+ switch (Chr8) {\r
+ case '\0':\r
+ ParserRewindDot (SanitizedBuffer, &SanitizedPosition);\r
+ ParserStripSlash (SanitizedBuffer, &SanitizedPosition);\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserEnd;\r
+ break;\r
+ case '/':\r
+ ParserRewindDot (SanitizedBuffer, &SanitizedPosition);\r
+ State = ParserSlash;\r
+ break;\r
+ case '.':\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserDotDot;\r
+ break;\r
+ default:\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserNormal;\r
+ break;\r
+ }\r
+ break;\r
+\r
+ case ParserDotDot: // two dots seen since last slash\r
+ switch (Chr8) {\r
+ case '\0':\r
+ ParserRewindDotDot (SanitizedBuffer, &SanitizedPosition, RootEscape);\r
+ ParserStripSlash (SanitizedBuffer, &SanitizedPosition);\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserEnd;\r
+ break;\r
+ case '/':\r
+ ParserRewindDotDot (SanitizedBuffer, &SanitizedPosition, RootEscape);\r
+ State = ParserSlash;\r
+ break;\r
+ case '.':\r
+ //\r
+ // fall through\r
+ //\r
+ default:\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserNormal;\r
+ break;\r
+ }\r
+ break;\r
+\r
+ case ParserNormal: // a different sequence seen\r
+ switch (Chr8) {\r
+ case '\0':\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserEnd;\r
+ break;\r
+ case '/':\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ State = ParserSlash;\r
+ break;\r
+ case '.':\r
+ //\r
+ // fall through\r
+ //\r
+ default:\r
+ //\r
+ // copy and stay in same state\r
+ //\r
+ ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+ break;\r
+ }\r
+ break;\r
+\r
+ default:\r
+ ASSERT (FALSE);\r
+ break;\r
+ }\r
+ } while (State != ParserEnd);\r
+\r
+ //\r
+ // Ensure length invariant on ResultPath8.\r
+ //\r
+ ASSERT (SanitizedPosition >= 2);\r
+ if (SanitizedPosition - 1 > VIRTIO_FS_MAX_PATHNAME_LENGTH) {\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto FreeSanitizedBuffer;\r
+ }\r
+\r
+ *ResultPath8 = SanitizedBuffer;\r
+ SanitizedBuffer = NULL;\r
+ Status = EFI_SUCCESS;\r
+ //\r
+ // Fall through.\r
+ //\r
+FreeSanitizedBuffer:\r
+ if (SanitizedBuffer != NULL) {\r
+ FreePool (SanitizedBuffer);\r
+ }\r
+\r
+FreeBufferToSanitize:\r
+ if (RhsPath8[0] != '/') {\r
+ FreePool (BufferToSanitize);\r
+ }\r
+\r
+FreeRhsPath8:\r
+ FreePool (RhsPath8);\r
+ return Status;\r
+}\r
+\r
+/**\r
+ For a given canonical pathname (as defined at VirtioFsAppendPath()), look up\r
+ the NodeId of the most specific parent directory, plus output a pointer to\r
+ the last pathname component (which is therefore a direct child of said parent\r
+ directory).\r
+\r
+ The function may only be called after VirtioFsFuseInitSession() returns\r
+ successfully and before VirtioFsUninit() is called.\r
+\r
+ @param[in,out] VirtioFs The Virtio Filesystem device to send FUSE_LOOKUP\r
+ and FUSE_FORGET requests to. On output, the FUSE\r
+ request counter "VirtioFs->RequestId" will have\r
+ been incremented several times.\r
+\r
+ @param[in,out] Path The canonical pathname (as defined in the\r
+ description of VirtioFsAppendPath()) to split.\r
+ Path is modified in-place temporarily; however, on\r
+ return (successful or otherwise), Path reassumes\r
+ its original contents.\r
+\r
+ @param[out] DirNodeId The NodeId of the most specific parent directory\r
+ identified by Path. The caller is responsible for\r
+ sending a FUSE_FORGET request to the Virtio\r
+ Filesystem device for DirNodeId -- unless\r
+ DirNodeId equals VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID\r
+ --, when DirNodeId's use ends.\r
+\r
+ @param[out] LastComponent A pointer into Path, pointing at the start of the\r
+ last pathname component.\r
+\r
+ @retval EFI_SUCCESS Splitting successful.\r
+\r
+ @retval EFI_INVALID_PARAMETER Path is "/".\r
+\r
+ @retval EFI_ACCESS_DENIED One of the components on Path before the last\r
+ is not a directory.\r
+\r
+ @return Error codes propagated from\r
+ VirtioFsFuseLookup() and\r
+ VirtioFsFuseAttrToEfiFileInfo().\r
+**/\r
+EFI_STATUS\r
+VirtioFsLookupMostSpecificParentDir (\r
+ IN OUT VIRTIO_FS *VirtioFs,\r
+ IN OUT CHAR8 *Path,\r
+ OUT UINT64 *DirNodeId,\r
+ OUT CHAR8 **LastComponent\r
+ )\r
+{\r
+ UINT64 ParentDirNodeId;\r
+ CHAR8 *Slash;\r
+ EFI_STATUS Status;\r
+ UINT64 NextDirNodeId;\r
+\r
+ if (AsciiStrCmp (Path, "/") == 0) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ ParentDirNodeId = VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID;\r
+ Slash = Path;\r
+ for (;;) {\r
+ CHAR8 *NextSlash;\r
+ VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;\r
+ EFI_FILE_INFO FileInfo;\r
+\r
+ //\r
+ // Find the slash (if any) that terminates the next pathname component.\r
+ //\r
+ NextSlash = AsciiStrStr (Slash + 1, "/");\r
+ if (NextSlash == NULL) {\r
+ break;\r
+ }\r
+\r
+ //\r
+ // Temporarily replace the found slash character with a NUL in-place, for\r
+ // easy construction of the single-component filename that we need to look\r
+ // up.\r
+ //\r
+ *NextSlash = '\0';\r
+ Status = VirtioFsFuseLookup (VirtioFs, ParentDirNodeId, Slash + 1,\r
+ &NextDirNodeId, &FuseAttr);\r
+ *NextSlash = '/';\r
+\r
+ //\r
+ // We're done with the directory inode that was the basis for the lookup.\r
+ //\r
+ if (ParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {\r
+ VirtioFsFuseForget (VirtioFs, ParentDirNodeId);\r
+ }\r
+\r
+ //\r
+ // If we couldn't look up the next *non-final* pathname component, bail.\r
+ //\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+\r
+ //\r
+ // Lookup successful; now check if the next (non-final) component is a\r
+ // directory. If not, bail.\r
+ //\r
+ Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo);\r
+ if (EFI_ERROR (Status)) {\r
+ goto ForgetNextDirNodeId;\r
+ }\r
+ if ((FileInfo.Attribute & EFI_FILE_DIRECTORY) == 0) {\r
+ Status = EFI_ACCESS_DENIED;\r
+ goto ForgetNextDirNodeId;\r
+ }\r
+\r
+ //\r
+ // Advance.\r
+ //\r
+ ParentDirNodeId = NextDirNodeId;\r
+ Slash = NextSlash;\r
+ }\r
+\r
+ //\r
+ // ParentDirNodeId corresponds to the last containing directory. The\r
+ // remaining single-component filename represents a direct child under that\r
+ // directory. Said filename starts at (Slash + 1).\r
+ //\r
+ *DirNodeId = ParentDirNodeId;\r
+ *LastComponent = Slash + 1;\r
+ return EFI_SUCCESS;\r
+\r
+ForgetNextDirNodeId:\r
+ VirtioFsFuseForget (VirtioFs, NextDirNodeId);\r
+ return Status;\r
+}\r
+\r
+/**\r
+ Convert select fields of a VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE object to\r
+ corresponding fields in EFI_FILE_INFO.\r
+\r
+ @param[in] FuseAttr The VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE object to\r
+ convert the relevant fields from.\r
+\r
+ @param[out] FileInfo The EFI_FILE_INFO structure to modify. Importantly, the\r
+ FileInfo->Size and FileInfo->FileName fields are not\r
+ overwritten.\r
+\r
+ @retval EFI_SUCCESS Conversion successful.\r
+\r
+ @retval EFI_UNSUPPORTED The allocated size of the file is inexpressible in\r
+ EFI_FILE_INFO.\r
+\r
+ @retval EFI_UNSUPPORTED One of the file access times is inexpressible in\r
+ EFI_FILE_INFO.\r
+\r
+ @retval EFI_UNSUPPORTED The file type is inexpressible in EFI_FILE_INFO.\r
+\r
+ @retval EFI_UNSUPPORTED The file is a regular file that has multiple names\r
+ on the host side (i.e., its hard link count is\r
+ greater than one).\r
+**/\r
+EFI_STATUS\r
+VirtioFsFuseAttrToEfiFileInfo (\r
+ IN VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE *FuseAttr,\r
+ OUT EFI_FILE_INFO *FileInfo\r
+ )\r
+{\r
+ UINT64 EpochTime[3];\r
+ EFI_TIME *ConvertedTime[ARRAY_SIZE (EpochTime)];\r
+ UINTN Idx;\r
+\r
+ FileInfo->FileSize = FuseAttr->Size;\r
+\r
+ //\r
+ // The unit for FuseAttr->Blocks is 512B.\r
+ //\r
+ if (FuseAttr->Blocks >= BIT55) {\r
+ return EFI_UNSUPPORTED;\r
+ }\r
+ FileInfo->PhysicalSize = LShiftU64 (FuseAttr->Blocks, 9);\r
+\r
+ //\r
+ // Convert the timestamps. File creation time is not tracked by the Virtio\r
+ // Filesystem device, so set FileInfo->CreateTime from FuseAttr->Mtime as\r
+ // well.\r
+ //\r
+ EpochTime[0] = FuseAttr->Mtime;\r
+ EpochTime[1] = FuseAttr->Atime;\r
+ EpochTime[2] = FuseAttr->Mtime;\r
+ ConvertedTime[0] = &FileInfo->CreateTime;\r
+ ConvertedTime[1] = &FileInfo->LastAccessTime;\r
+ ConvertedTime[2] = &FileInfo->ModificationTime;\r
+\r
+ for (Idx = 0; Idx < ARRAY_SIZE (EpochTime); Idx++) {\r
+ //\r
+ // EpochToEfiTime() takes a UINTN for seconds.\r
+ //\r
+ if (EpochTime[Idx] > MAX_UINTN) {\r
+ return EFI_UNSUPPORTED;\r
+ }\r
+ //\r
+ // Set the following fields in the converted time: Year, Month, Day, Hour,\r
+ // Minute, Second, Nanosecond.\r
+ //\r
+ EpochToEfiTime ((UINTN)EpochTime[Idx], ConvertedTime[Idx]);\r
+ //\r
+ // The times are all expressed in UTC. Consequently, they are not affected\r
+ // by daylight saving.\r
+ //\r
+ ConvertedTime[Idx]->TimeZone = 0;\r
+ ConvertedTime[Idx]->Daylight = 0;\r
+ //\r
+ // Clear the padding fields.\r
+ //\r
+ ConvertedTime[Idx]->Pad1 = 0;\r
+ ConvertedTime[Idx]->Pad2 = 0;\r
+ }\r
+\r
+ //\r
+ // Set the attributes.\r
+ //\r
+ switch (FuseAttr->Mode & VIRTIO_FS_FUSE_MODE_TYPE_MASK) {\r
+ case VIRTIO_FS_FUSE_MODE_TYPE_DIR:\r
+ FileInfo->Attribute = EFI_FILE_DIRECTORY;\r
+ break;\r
+ case VIRTIO_FS_FUSE_MODE_TYPE_REG:\r
+ FileInfo->Attribute = 0;\r
+ break;\r
+ default:\r
+ //\r
+ // Other file types are not supported.\r
+ //\r
+ return EFI_UNSUPPORTED;\r
+ }\r
+ //\r
+ // Report the regular file or directory as read-only if all classes lack\r
+ // write permission.\r
+ //\r
+ if ((FuseAttr->Mode & (VIRTIO_FS_FUSE_MODE_PERM_WUSR |\r
+ VIRTIO_FS_FUSE_MODE_PERM_WGRP |\r
+ VIRTIO_FS_FUSE_MODE_PERM_WOTH)) == 0) {\r
+ FileInfo->Attribute |= EFI_FILE_READ_ONLY;\r
+ }\r
+\r
+ //\r
+ // A hard link count greater than 1 is not supported for regular files.\r
+ //\r
+ if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) == 0 && FuseAttr->Nlink > 1) {\r
+ return EFI_UNSUPPORTED;\r
+ }\r
+\r
+ return EFI_SUCCESS;\r
+}\r