[33060] | 1 | /* $Id: VDIfVfs2.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
|
---|
| 2 | /** @file
|
---|
[59601] | 3 | * Virtual Disk Image (VDI), I/O interface to IPRT VFS I/O stream glue.
|
---|
[33060] | 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2012-2023 Oracle and/or its affiliates.
|
---|
[33060] | 8 | *
|
---|
[96407] | 9 | * This file is part of VirtualBox base platform packages, as
|
---|
| 10 | * available from https://www.virtualbox.org.
|
---|
| 11 | *
|
---|
| 12 | * This program is free software; you can redistribute it and/or
|
---|
| 13 | * modify it under the terms of the GNU General Public License
|
---|
| 14 | * as published by the Free Software Foundation, in version 3 of the
|
---|
| 15 | * License.
|
---|
| 16 | *
|
---|
| 17 | * This program is distributed in the hope that it will be useful, but
|
---|
| 18 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
| 20 | * General Public License for more details.
|
---|
| 21 | *
|
---|
| 22 | * You should have received a copy of the GNU General Public License
|
---|
| 23 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
| 24 | *
|
---|
| 25 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
[33060] | 26 | */
|
---|
| 27 |
|
---|
| 28 |
|
---|
[57372] | 29 | /*********************************************************************************************************************************
|
---|
| 30 | * Header Files *
|
---|
| 31 | *********************************************************************************************************************************/
|
---|
[59601] | 32 | #include <iprt/types.h>
|
---|
| 33 | #include <iprt/assert.h>
|
---|
| 34 | #include <iprt/mem.h>
|
---|
| 35 | #include <iprt/err.h>
|
---|
[33060] | 36 | #include <iprt/asm.h>
|
---|
[59601] | 37 | #include <iprt/string.h>
|
---|
| 38 | #include <iprt/file.h>
|
---|
| 39 | #include <iprt/sg.h>
|
---|
| 40 | #include <iprt/vfslowlevel.h>
|
---|
| 41 | #include <iprt/poll.h>
|
---|
[33567] | 42 | #include <VBox/vd.h>
|
---|
[59601] | 43 | #include <VBox/vd-ifs-internal.h>
|
---|
[33060] | 44 |
|
---|
[59601] | 45 | #include <VBox/log.h>
|
---|
[50200] | 46 |
|
---|
| 47 |
|
---|
[57372] | 48 | /*********************************************************************************************************************************
|
---|
| 49 | * Structures and Typedefs *
|
---|
| 50 | *********************************************************************************************************************************/
|
---|
[59601] | 51 | /**
|
---|
| 52 | * Extended VD I/O interface structure that vdIfFromVfs_xxx uses.
|
---|
| 53 | *
|
---|
| 54 | * It's passed as pvUser to each call.
|
---|
| 55 | */
|
---|
| 56 | typedef struct VDIFFROMVFS
|
---|
[33060] | 57 | {
|
---|
[59601] | 58 | VDINTERFACEIO CoreIo;
|
---|
[33060] | 59 |
|
---|
[59601] | 60 | /** Magic. */
|
---|
| 61 | uint32_t u32Magic;
|
---|
| 62 | /** The stream access mode (RTFILE_O_ACCESS_MASK), possibly others. */
|
---|
| 63 | uint32_t fAccessMode;
|
---|
| 64 | /** The I/O stream. This is NIL after it's been closed. */
|
---|
| 65 | RTVFSIOSTREAM hVfsIos;
|
---|
[33060] | 66 | /** Completion callback. */
|
---|
[59601] | 67 | PFNVDCOMPLETED pfnCompleted;
|
---|
| 68 | /** User parameter for the completion callback. */
|
---|
| 69 | void *pvCompletedUser;
|
---|
| 70 | /** Set if hVfsIos has been opened. */
|
---|
| 71 | bool fOpened;
|
---|
| 72 | } VDIFFROMVFS;
|
---|
| 73 | /** Magic value for VDIFFROMVFS::u32Magic. */
|
---|
| 74 | #define VDIFFROMVFS_MAGIC UINT32_C(0x11223344)
|
---|
[33060] | 75 |
|
---|
[59601] | 76 | /** Pointer to the instance data for the vdIfFromVfs_ methods. */
|
---|
| 77 | typedef struct VDIFFROMVFS *PVDIFFROMVFS;
|
---|
[50200] | 78 |
|
---|
[59601] | 79 |
|
---|
| 80 | typedef struct FILESTORAGEINTERNAL
|
---|
[33060] | 81 | {
|
---|
[59601] | 82 | /** File handle. */
|
---|
| 83 | RTFILE file;
|
---|
| 84 | } FILESTORAGEINTERNAL, *PFILESTORAGEINTERNAL;
|
---|
[33060] | 85 |
|
---|
| 86 |
|
---|
[57372] | 87 | /*********************************************************************************************************************************
|
---|
| 88 | * Defined Constants And Macros *
|
---|
| 89 | *********************************************************************************************************************************/
|
---|
| 90 |
|
---|
[36015] | 91 | #define STATUS_WAIT UINT32_C(0)
|
---|
| 92 | #define STATUS_WRITE UINT32_C(1)
|
---|
| 93 | #define STATUS_WRITING UINT32_C(2)
|
---|
| 94 | #define STATUS_READ UINT32_C(3)
|
---|
| 95 | #define STATUS_READING UINT32_C(4)
|
---|
| 96 | #define STATUS_END UINT32_C(5)
|
---|
[33060] | 97 |
|
---|
| 98 | /* Enable for getting some flow history. */
|
---|
| 99 | #if 0
|
---|
| 100 | # define DEBUG_PRINT_FLOW() RTPrintf("%s\n", __FUNCTION__)
|
---|
| 101 | #else
|
---|
[45367] | 102 | # define DEBUG_PRINT_FLOW() do {} while (0)
|
---|
[33060] | 103 | #endif
|
---|
| 104 |
|
---|
| 105 |
|
---|
[57372] | 106 | /*********************************************************************************************************************************
|
---|
| 107 | * Internal Functions *
|
---|
| 108 | *********************************************************************************************************************************/
|
---|
[47516] | 109 |
|
---|
[57372] | 110 |
|
---|
[50200] | 111 | /** @name VDINTERFACEIO stubs returning not-implemented.
|
---|
| 112 | * @{
|
---|
| 113 | */
|
---|
| 114 |
|
---|
| 115 | /** @interface_method_impl{VDINTERFACEIO,pfnDelete} */
|
---|
| 116 | static DECLCALLBACK(int) notImpl_Delete(void *pvUser, const char *pcszFilename)
|
---|
| 117 | {
|
---|
| 118 | NOREF(pvUser); NOREF(pcszFilename);
|
---|
[59601] | 119 | Log(("%s\n", __FUNCTION__));
|
---|
[67182] | 120 | AssertFailed();
|
---|
[50200] | 121 | return VERR_NOT_IMPLEMENTED;
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | /** @interface_method_impl{VDINTERFACEIO,pfnMove} */
|
---|
| 125 | static DECLCALLBACK(int) notImpl_Move(void *pvUser, const char *pcszSrc, const char *pcszDst, unsigned fMove)
|
---|
| 126 | {
|
---|
| 127 | NOREF(pvUser); NOREF(pcszSrc); NOREF(pcszDst); NOREF(fMove);
|
---|
[59601] | 128 | Log(("%s\n", __FUNCTION__));
|
---|
[67182] | 129 | AssertFailed();
|
---|
[50200] | 130 | return VERR_NOT_IMPLEMENTED;
|
---|
| 131 | }
|
---|
| 132 |
|
---|
| 133 | /** @interface_method_impl{VDINTERFACEIO,pfnGetFreeSpace} */
|
---|
| 134 | static DECLCALLBACK(int) notImpl_GetFreeSpace(void *pvUser, const char *pcszFilename, int64_t *pcbFreeSpace)
|
---|
| 135 | {
|
---|
| 136 | NOREF(pvUser); NOREF(pcszFilename); NOREF(pcbFreeSpace);
|
---|
[59601] | 137 | Log(("%s\n", __FUNCTION__));
|
---|
[67182] | 138 | AssertFailed();
|
---|
[50200] | 139 | return VERR_NOT_IMPLEMENTED;
|
---|
| 140 | }
|
---|
| 141 |
|
---|
| 142 | /** @interface_method_impl{VDINTERFACEIO,pfnGetModificationTime} */
|
---|
| 143 | static DECLCALLBACK(int) notImpl_GetModificationTime(void *pvUser, const char *pcszFilename, PRTTIMESPEC pModificationTime)
|
---|
| 144 | {
|
---|
| 145 | NOREF(pvUser); NOREF(pcszFilename); NOREF(pModificationTime);
|
---|
[59601] | 146 | Log(("%s\n", __FUNCTION__));
|
---|
[67182] | 147 | AssertFailed();
|
---|
[50200] | 148 | return VERR_NOT_IMPLEMENTED;
|
---|
| 149 | }
|
---|
| 150 |
|
---|
| 151 | /** @interface_method_impl{VDINTERFACEIO,pfnSetSize} */
|
---|
| 152 | static DECLCALLBACK(int) notImpl_SetSize(void *pvUser, void *pvStorage, uint64_t cb)
|
---|
| 153 | {
|
---|
| 154 | NOREF(pvUser); NOREF(pvStorage); NOREF(cb);
|
---|
[59601] | 155 | Log(("%s\n", __FUNCTION__));
|
---|
[67182] | 156 | AssertFailed();
|
---|
[50200] | 157 | return VERR_NOT_IMPLEMENTED;
|
---|
| 158 | }
|
---|
| 159 |
|
---|
[62873] | 160 | #if 0 /* unused */
|
---|
[50200] | 161 | /** @interface_method_impl{VDINTERFACEIO,pfnWriteSync} */
|
---|
[51092] | 162 | static DECLCALLBACK(int) notImpl_WriteSync(void *pvUser, void *pvStorage, uint64_t off, const void *pvBuf,
|
---|
| 163 | size_t cbWrite, size_t *pcbWritten)
|
---|
[50200] | 164 | {
|
---|
[62873] | 165 | RT_NOREF6(pvUser, pvStorage, off, pvBuf, cbWrite, pcbWritten)
|
---|
[59601] | 166 | Log(("%s\n", __FUNCTION__));
|
---|
[50200] | 167 | return VERR_NOT_IMPLEMENTED;
|
---|
| 168 | }
|
---|
[62873] | 169 | #endif
|
---|
[50200] | 170 |
|
---|
| 171 | /** @interface_method_impl{VDINTERFACEIO,pfnFlushSync} */
|
---|
| 172 | static DECLCALLBACK(int) notImpl_FlushSync(void *pvUser, void *pvStorage)
|
---|
| 173 | {
|
---|
| 174 | NOREF(pvUser); NOREF(pvStorage);
|
---|
[59601] | 175 | Log(("%s\n", __FUNCTION__));
|
---|
[67182] | 176 | AssertFailed();
|
---|
[50200] | 177 | return VERR_NOT_IMPLEMENTED;
|
---|
| 178 | }
|
---|
| 179 |
|
---|
| 180 | /** @} */
|
---|
| 181 |
|
---|
| 182 |
|
---|
[59601] | 183 | /** @interface_method_impl{VDINTERFACEIO,pfnOpen} */
|
---|
| 184 | static DECLCALLBACK(int) vdIfFromVfs_Open(void *pvUser, const char *pszLocation, uint32_t fOpen,
|
---|
[64272] | 185 | PFNVDCOMPLETED pfnCompleted, void **ppvStorage)
|
---|
[33060] | 186 | {
|
---|
[62729] | 187 | RT_NOREF1(pszLocation);
|
---|
[59601] | 188 | PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
|
---|
[33060] | 189 |
|
---|
[50204] | 190 | /*
|
---|
[50200] | 191 | * Validate input.
|
---|
| 192 | */
|
---|
[64272] | 193 | AssertPtrReturn(ppvStorage, VERR_INVALID_POINTER);
|
---|
[50200] | 194 | AssertPtrNullReturn(pfnCompleted, VERR_INVALID_PARAMETER);
|
---|
| 195 |
|
---|
| 196 | /*
|
---|
[59601] | 197 | * We ignore the name, assuming the caller is opening the stream/file we're .
|
---|
| 198 | * serving. Thus, after close, all open calls fail.
|
---|
[50200] | 199 | */
|
---|
[59601] | 200 | AssertReturn(!pThis->fOpened, VERR_FILE_NOT_FOUND);
|
---|
| 201 | AssertReturn(pThis->hVfsIos != NIL_RTVFSIOSTREAM, VERR_FILE_NOT_FOUND); /* paranoia */
|
---|
| 202 | AssertMsgReturn((pThis->fAccessMode & fOpen & RTFILE_O_ACCESS_MASK) == (fOpen & RTFILE_O_ACCESS_MASK),
|
---|
| 203 | ("fAccessMode=%#x fOpen=%#x\n", pThis->fAccessMode, fOpen), VERR_ACCESS_DENIED);
|
---|
[50200] | 204 |
|
---|
[59601] | 205 | pThis->fAccessMode = fOpen & RTFILE_O_ACCESS_MASK;
|
---|
| 206 | pThis->fOpened = true;
|
---|
| 207 | pThis->pfnCompleted = pfnCompleted;
|
---|
| 208 | pThis->pvCompletedUser = pvUser;
|
---|
[50200] | 209 |
|
---|
[64272] | 210 | *ppvStorage = pThis->hVfsIos;
|
---|
[59601] | 211 | return VINF_SUCCESS;
|
---|
[50200] | 212 | }
|
---|
| 213 |
|
---|
| 214 | /** @interface_method_impl{VDINTERFACEIO,pfnClose} */
|
---|
[59601] | 215 | static DECLCALLBACK(int) vdIfFromVfs_Close(void *pvUser, void *pvStorage)
|
---|
[50200] | 216 | {
|
---|
[59601] | 217 | PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
|
---|
| 218 | AssertPtrReturn(pThis, VERR_INVALID_POINTER);
|
---|
[59610] | 219 | AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
|
---|
[59601] | 220 | AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
|
---|
[50200] | 221 |
|
---|
[62729] | 222 | RTVfsIoStrmRelease(pThis->hVfsIos);
|
---|
[59601] | 223 | pThis->hVfsIos = NIL_RTVFSIOSTREAM;
|
---|
[50200] | 224 |
|
---|
[59601] | 225 | return VINF_SUCCESS;
|
---|
[50200] | 226 | }
|
---|
| 227 |
|
---|
| 228 |
|
---|
| 229 | /** @interface_method_impl{VDINTERFACEIO,pfnGetSize} */
|
---|
[59601] | 230 | static DECLCALLBACK(int) vdIfFromVfs_GetSize(void *pvUser, void *pvStorage, uint64_t *pcb)
|
---|
[50200] | 231 | {
|
---|
[59601] | 232 | PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
|
---|
| 233 | AssertPtrReturn(pThis, VERR_INVALID_POINTER);
|
---|
[59610] | 234 | AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
|
---|
[59601] | 235 | AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
|
---|
[50200] | 236 |
|
---|
| 237 | RTFSOBJINFO ObjInfo;
|
---|
[59601] | 238 | int rc = RTVfsIoStrmQueryInfo(pThis->hVfsIos, &ObjInfo, RTFSOBJATTRADD_NOTHING);
|
---|
[50200] | 239 | if (RT_SUCCESS(rc))
|
---|
| 240 | *pcb = ObjInfo.cbObject;
|
---|
| 241 | return rc;
|
---|
| 242 | }
|
---|
| 243 |
|
---|
[58132] | 244 | /** @interface_method_impl{VDINTERFACEIO,pfnReadSync} */
|
---|
[59601] | 245 | static DECLCALLBACK(int) vdIfFromVfs_ReadSync(void *pvUser, void *pvStorage, uint64_t off, void *pvBuf,
|
---|
| 246 | size_t cbToRead, size_t *pcbRead)
|
---|
[50200] | 247 | {
|
---|
[59601] | 248 | PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
|
---|
| 249 | AssertPtrReturn(pThis, VERR_INVALID_POINTER);
|
---|
[59610] | 250 | AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
|
---|
[59601] | 251 | AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
|
---|
[50200] | 252 | AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
|
---|
[59601] | 253 | AssertReturn(pThis->fAccessMode & RTFILE_O_READ, VERR_ACCESS_DENIED);
|
---|
[50200] | 254 |
|
---|
[59601] | 255 | return RTVfsIoStrmReadAt(pThis->hVfsIos, off, pvBuf, cbToRead, true /*fBlocking*/, pcbRead);
|
---|
[50200] | 256 | }
|
---|
| 257 |
|
---|
| 258 |
|
---|
[59601] | 259 | /** @interface_method_impl{VDINTERFACEIO,pfnWriteSync} */
|
---|
| 260 | static DECLCALLBACK(int) vdIfFromVfs_WriteSync(void *pvUser, void *pvStorage, uint64_t off, void const *pvBuf,
|
---|
| 261 | size_t cbToWrite, size_t *pcbWritten)
|
---|
[50200] | 262 | {
|
---|
[59601] | 263 | PVDIFFROMVFS pThis = (PVDIFFROMVFS)pvUser;
|
---|
| 264 | AssertPtrReturn(pThis, VERR_INVALID_POINTER);
|
---|
[59610] | 265 | AssertReturn(pThis->hVfsIos == (RTVFSIOSTREAM)pvStorage, VERR_INVALID_HANDLE);
|
---|
[59601] | 266 | AssertReturn(pThis->fOpened, VERR_INVALID_HANDLE);
|
---|
| 267 | AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
|
---|
| 268 | AssertReturn(pThis->fAccessMode & RTFILE_O_WRITE, VERR_ACCESS_DENIED);
|
---|
[56307] | 269 |
|
---|
[59601] | 270 | return RTVfsIoStrmWriteAt(pThis->hVfsIos, off, pvBuf, cbToWrite, true /*fBlocking*/, pcbWritten);
|
---|
[50200] | 271 | }
|
---|
| 272 |
|
---|
| 273 |
|
---|
[59601] | 274 | VBOXDDU_DECL(int) VDIfCreateFromVfsStream(RTVFSIOSTREAM hVfsIos, uint32_t fAccessMode, PVDINTERFACEIO *ppIoIf)
|
---|
[50200] | 275 | {
|
---|
[59601] | 276 | /*
|
---|
| 277 | * Validate input.
|
---|
| 278 | */
|
---|
| 279 | AssertPtrReturn(ppIoIf, VERR_INVALID_POINTER);
|
---|
| 280 | *ppIoIf = NULL;
|
---|
| 281 | AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
|
---|
| 282 | AssertReturn(fAccessMode & RTFILE_O_ACCESS_MASK, VERR_INVALID_FLAGS);
|
---|
[50200] | 283 |
|
---|
[59601] | 284 | uint32_t cRefs = RTVfsIoStrmRetain(hVfsIos);
|
---|
| 285 | AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
|
---|
[50200] | 286 |
|
---|
[59601] | 287 | /*
|
---|
| 288 | * Allocate and init a callback + instance data structure.
|
---|
| 289 | */
|
---|
| 290 | int rc;
|
---|
| 291 | PVDIFFROMVFS pThis = (PVDIFFROMVFS)RTMemAllocZ(sizeof(*pThis));
|
---|
| 292 | if (pThis)
|
---|
[59555] | 293 | {
|
---|
[59601] | 294 | pThis->CoreIo.pfnOpen = vdIfFromVfs_Open;
|
---|
| 295 | pThis->CoreIo.pfnClose = vdIfFromVfs_Close;
|
---|
| 296 | pThis->CoreIo.pfnDelete = notImpl_Delete;
|
---|
| 297 | pThis->CoreIo.pfnMove = notImpl_Move;
|
---|
| 298 | pThis->CoreIo.pfnGetFreeSpace = notImpl_GetFreeSpace;
|
---|
| 299 | pThis->CoreIo.pfnGetModificationTime = notImpl_GetModificationTime;
|
---|
| 300 | pThis->CoreIo.pfnGetSize = vdIfFromVfs_GetSize;
|
---|
| 301 | pThis->CoreIo.pfnSetSize = notImpl_SetSize;
|
---|
| 302 | pThis->CoreIo.pfnReadSync = vdIfFromVfs_ReadSync;
|
---|
| 303 | pThis->CoreIo.pfnWriteSync = vdIfFromVfs_WriteSync;
|
---|
| 304 | pThis->CoreIo.pfnFlushSync = notImpl_FlushSync;
|
---|
[50200] | 305 |
|
---|
[59601] | 306 | pThis->hVfsIos = hVfsIos;
|
---|
| 307 | pThis->fAccessMode = fAccessMode;
|
---|
| 308 | pThis->fOpened = false;
|
---|
| 309 | pThis->u32Magic = VDIFFROMVFS_MAGIC;
|
---|
[59555] | 310 |
|
---|
[59601] | 311 | PVDINTERFACE pFakeList = NULL;
|
---|
| 312 | rc = VDInterfaceAdd(&pThis->CoreIo.Core, "FromVfsStream", VDINTERFACETYPE_IO, pThis, sizeof(pThis->CoreIo), &pFakeList);
|
---|
| 313 | if (RT_SUCCESS(rc))
|
---|
[59555] | 314 | {
|
---|
[59601] | 315 | *ppIoIf = &pThis->CoreIo;
|
---|
| 316 | return VINF_SUCCESS;
|
---|
[59555] | 317 | }
|
---|
| 318 |
|
---|
[59601] | 319 | RTMemFree(pThis);
|
---|
[50200] | 320 | }
|
---|
[59601] | 321 | else
|
---|
| 322 | rc = VERR_NO_MEMORY;
|
---|
| 323 | RTVfsIoStrmRelease(hVfsIos);
|
---|
[33060] | 324 | return rc;
|
---|
| 325 | }
|
---|
| 326 |
|
---|
| 327 |
|
---|
[59601] | 328 | VBOXDDU_DECL(int) VDIfDestroyFromVfsStream(PVDINTERFACEIO pIoIf)
|
---|
[33060] | 329 | {
|
---|
[59601] | 330 | if (pIoIf)
|
---|
[33060] | 331 | {
|
---|
[59601] | 332 | PVDIFFROMVFS pThis = (PVDIFFROMVFS)pIoIf;
|
---|
| 333 | AssertPtrReturn(pThis, VERR_INVALID_POINTER);
|
---|
| 334 | AssertReturn(pThis->u32Magic == VDIFFROMVFS_MAGIC, VERR_INVALID_MAGIC);
|
---|
[33060] | 335 |
|
---|
[59601] | 336 | if (pThis->hVfsIos != NIL_RTVFSIOSTREAM)
|
---|
[33060] | 337 | {
|
---|
[59601] | 338 | RTVfsIoStrmRelease(pThis->hVfsIos);
|
---|
| 339 | pThis->hVfsIos = NIL_RTVFSIOSTREAM;
|
---|
[33060] | 340 | }
|
---|
[59601] | 341 | pThis->u32Magic = ~VDIFFROMVFS_MAGIC;
|
---|
[83043] | 342 | RTMemFree(pThis);
|
---|
[33060] | 343 | }
|
---|
| 344 | return VINF_SUCCESS;
|
---|
| 345 | }
|
---|
| 346 |
|
---|