VirtualBox

source: vbox/trunk/src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp@ 85416

Last change on this file since 85416 was 85372, checked in by vboxsync, 4 years ago

DnD: Build fix.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.5 KB
Line 
1/* $Id: DnDTransferObject.cpp 85372 2020-07-17 10:08:26Z vboxsync $ */
2/** @file
3 * DnD - Transfer object implemenation for handling creation/reading/writing to files and directories on host or guest side.
4 */
5
6/*
7 * Copyright (C) 2014-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_GUEST_DND
23#include <VBox/GuestHost/DragAndDrop.h>
24
25#include <iprt/dir.h>
26#include <iprt/err.h>
27#include <iprt/file.h>
28#include <iprt/fs.h>
29#include <iprt/path.h>
30#include <iprt/string.h>
31#include <iprt/uri.h>
32
33#include <VBox/log.h>
34
35
36/*********************************************************************************************************************************
37* Prototypes *
38*********************************************************************************************************************************/
39static void dndTransferObjectCloseInternal(PDNDTRANSFEROBJECT pObj);
40static int dndTransferObjectQueryInfoInternal(PDNDTRANSFEROBJECT pObj);
41
42
43/**
44 * Initializes the object with an expected object type and file path.
45 *
46 * @returns VBox status code.
47 * @param pObj DnD transfer object to initialize.
48 * @param enmType Type we expect this object to be.
49 * @param pcszPathSrcAbs Absolute source (local) path of file this object represents. Can be empty (e.g. for root stuff).
50 * @param pcszPathDst Relative path of file this object represents at the destination.
51 * Together with \a pcszPathSrcAbs this represents the complete absolute local path.
52 */
53int DnDTransferObjectInit(PDNDTRANSFEROBJECT pObj, DNDTRANSFEROBJTYPE enmType, const char *pcszPathSrcAbs, const char *pcszPathDst)
54{
55 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
56 AssertReturn(pObj->enmType == DNDTRANSFEROBJTYPE_UNKNOWN, VERR_WRONG_ORDER); /* Already initialized? */
57 /* pcszPathSrcAbs can be empty. */
58 AssertPtrReturn(pcszPathDst, VERR_INVALID_POINTER);
59
60 switch (enmType)
61 {
62 case DNDTRANSFEROBJTYPE_FILE:
63 {
64 pObj->u.File.hFile = NIL_RTFILE;
65 break;
66 }
67
68 case DNDTRANSFEROBJTYPE_DIRECTORY:
69 {
70 pObj->u.Dir.hDir = NIL_RTDIR;
71 break;
72 }
73
74 default:
75 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
76 break; /* Never reached */
77 }
78
79 int rc = DnDPathValidate(pcszPathDst, false /* Does not need to exist */);
80 if (RT_FAILURE(rc))
81 return rc;
82
83 char szPath[RTPATH_MAX + 1];
84
85 /* Save the index (in characters) where the first destination segment starts. */
86 if ( pcszPathSrcAbs
87 && RTStrNLen(pcszPathSrcAbs, RTSTR_MAX))
88 {
89 rc = DnDPathValidate(pcszPathSrcAbs, false /* Does not need to exist */);
90 if (RT_FAILURE(rc))
91 return rc;
92
93 rc = RTStrCopy(szPath, sizeof(szPath), pcszPathSrcAbs);
94 if (RT_SUCCESS(rc))
95 rc = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)) == 0 ? VERR_BUFFER_OVERFLOW : VINF_SUCCESS;
96
97 /* Save the index (in characters) where the destination part starts. */
98 pObj->idxDst = (uint16_t)RTStrNLen(szPath, RTPATH_MAX);
99 AssertReturn(pObj->idxDst <= RTPATH_MAX, VERR_INVALID_PARAMETER);
100 }
101 else
102 {
103 szPath[0] = '\0'; /* Init empty string. */
104 pObj->idxDst = 0;
105 }
106
107 if (RT_FAILURE(rc))
108 return rc;
109
110 /* Append the destination part. */
111 rc = RTPathAppend(szPath, sizeof(szPath), pcszPathDst);
112 if ( RT_SUCCESS(rc)
113 && enmType == DNDTRANSFEROBJTYPE_DIRECTORY)
114 rc = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)) == 0 ? VERR_BUFFER_OVERFLOW : VINF_SUCCESS;
115
116 if (RT_FAILURE(rc))
117 return rc;
118
119 pObj->pszPath = RTStrDup(szPath);
120 if (!pObj->pszPath)
121 return VERR_NO_MEMORY;
122
123 /* Convert paths into transport format. */
124 rc = DnDPathConvert(pObj->pszPath, strlen(pObj->pszPath), DNDPATHCONVERT_FLAGS_TRANSPORT);
125 if (RT_FAILURE(rc))
126 {
127 RTStrFree(pObj->pszPath);
128 pObj->pszPath = NULL;
129 return rc;
130 }
131
132 LogFlowFunc(("enmType=%RU32, pcszPathSrcAbs=%s, pcszPathDst=%s -> pszPath=%s\n",
133 enmType, pcszPathSrcAbs, pcszPathDst, pObj->pszPath));
134
135 pObj->enmType = enmType;
136
137 return VINF_SUCCESS;
138}
139
140/**
141 * Destroys a DnD transfer object.
142 *
143 * @param pObj DnD transfer object to destroy.
144 */
145void DnDTransferObjectDestroy(PDNDTRANSFEROBJECT pObj)
146{
147 if (!pObj)
148 return;
149
150 DnDTransferObjectReset(pObj);
151}
152
153/**
154 * Closes the object's internal handles (to files / ...).
155 *
156 * @param pObj DnD transfer object to close internally.
157 */
158static void dndTransferObjectCloseInternal(PDNDTRANSFEROBJECT pObj)
159{
160 AssertPtrReturnVoid(pObj);
161
162 int rc;
163
164 LogRel2(("DnD: Closing '%s'\n", pObj->pszPath));
165
166 switch (pObj->enmType)
167 {
168 case DNDTRANSFEROBJTYPE_FILE:
169 {
170 if (RTFileIsValid(pObj->u.File.hFile))
171 {
172 rc = RTFileClose(pObj->u.File.hFile);
173 if (RT_SUCCESS(rc))
174 {
175 pObj->u.File.hFile = NIL_RTFILE;
176 RT_ZERO(pObj->u.File.objInfo);
177 }
178 else
179 LogRel(("DnD: Closing file '%s' failed with %Rrc\n", pObj->pszPath, rc));
180 }
181 break;
182 }
183
184 case DNDTRANSFEROBJTYPE_DIRECTORY:
185 {
186 if (RTDirIsValid(pObj->u.Dir.hDir))
187 {
188 rc = RTDirClose(pObj->u.Dir.hDir);
189 if (RT_SUCCESS(rc))
190 {
191 pObj->u.Dir.hDir = NIL_RTDIR;
192 RT_ZERO(pObj->u.Dir.objInfo);
193 }
194 else
195 LogRel(("DnD: Closing directory '%s' failed with %Rrc\n", pObj->pszPath, rc));
196 }
197 break;
198 }
199
200 default:
201 break;
202 }
203
204 /** @todo Return rc. */
205}
206
207/**
208 * Closes the object.
209 * This also closes the internal handles associated with the object (to files / ...).
210 *
211 * @param pObj DnD transfer object to close.
212 */
213void DnDTransferObjectClose(PDNDTRANSFEROBJECT pObj)
214{
215 AssertPtrReturnVoid(pObj);
216
217 dndTransferObjectCloseInternal(pObj);
218}
219
220/**
221 * Returns the absolute source path of the object.
222 *
223 * @return Absolute source path of the object.
224 * @param pObj DnD transfer object to get source path for.
225 */
226const char *DnDTransferObjectGetSourcePath(PDNDTRANSFEROBJECT pObj)
227{
228 AssertPtrReturn(pObj, NULL);
229 return pObj->pszPath;
230}
231
232/**
233 * Returns the (relative) destination path of the object, in transport style.
234 *
235 * @return Relative destination path of the object, or NULL if not set.
236 * @param pObj DnD transfer object to get destination path for.
237 */
238const char *DnDTransferObjectGetDestPath(PDNDTRANSFEROBJECT pObj)
239{
240 AssertPtrReturn(pObj, NULL);
241
242 if (!pObj->pszPath)
243 return NULL;
244
245 AssertReturn(strlen(pObj->pszPath) >= pObj->idxDst, NULL);
246
247 return &pObj->pszPath[pObj->idxDst];
248}
249
250/**
251 * Returns the (relative) destination path of the object, extended version.
252 *
253 * @return VBox status code, or VERR_NOT_FOUND if not initialized yet.
254 * @param pObj DnD transfer object to get destination path for.
255 * @param enmStyle Which path style to return.
256 * @param pszBuf Where to store the path.
257 * @param cbBuf Size (in bytes) where to store the path.
258 */
259int DnDTransferObjectGetDestPathEx(PDNDTRANSFEROBJECT pObj, DNDTRANSFEROBJPATHSTYLE enmStyle, char *pszBuf, size_t cbBuf)
260{
261 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
262 AssertPtrReturn(pszBuf, VERR_INVALID_POINTER);
263 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
264
265 if (!pObj->pszPath)
266 return VERR_NOT_FOUND;
267
268 AssertReturn(strlen(pObj->pszPath) >= pObj->idxDst, VERR_INTERNAL_ERROR);
269
270 int rc = RTStrCopy(pszBuf, cbBuf, &pObj->pszPath[pObj->idxDst]);
271 if ( RT_SUCCESS(rc)
272 && enmStyle == DNDTRANSFEROBJPATHSTYLE_DOS)
273 rc = DnDPathConvert(pszBuf, cbBuf, DNDPATHCONVERT_FLAGS_TO_DOS);
274
275 return rc;
276}
277
278/**
279 * Returns the directory / file mode of the object.
280 *
281 * @return File / directory mode.
282 * @param pObj DnD transfer object to get directory / file mode for.
283 */
284RTFMODE DnDTransferObjectGetMode(PDNDTRANSFEROBJECT pObj)
285{
286 AssertPtrReturn(pObj, 0);
287
288 switch (pObj->enmType)
289 {
290 case DNDTRANSFEROBJTYPE_FILE:
291 return pObj->u.File.objInfo.Attr.fMode;
292
293 case DNDTRANSFEROBJTYPE_DIRECTORY:
294 return pObj->u.Dir.objInfo.Attr.fMode;
295
296 default:
297 break;
298 }
299
300 return 0;
301}
302
303/**
304 * Returns the bytes already processed (read / written).
305 *
306 * Note: Only applies if the object is of type DnDTransferObjectType_File.
307 *
308 * @return Bytes already processed (read / written).
309 * @param pObj DnD transfer object to get processed bytes for.
310 */
311uint64_t DnDTransferObjectGetProcessed(PDNDTRANSFEROBJECT pObj)
312{
313 if (pObj->enmType == DNDTRANSFEROBJTYPE_FILE)
314 return pObj->u.File.cbProcessed;
315
316 return 0;
317}
318
319/**
320 * Returns the file's logical size (in bytes).
321 *
322 * Note: Only applies if the object is of type DnDTransferObjectType_File.
323 *
324 * @return The file's logical size (in bytes).
325 * @param pObj DnD transfer object to get size for.
326 */
327uint64_t DnDTransferObjectGetSize(PDNDTRANSFEROBJECT pObj)
328{
329 if (pObj->enmType == DNDTRANSFEROBJTYPE_FILE)
330 return pObj->u.File.cbToProcess;
331
332 return 0;
333}
334
335/**
336 * Returns the object's type.
337 *
338 * @return The object's type.
339 * @param pObj DnD transfer object to get type for.
340 */
341DNDTRANSFEROBJTYPE DnDTransferObjectGetType(PDNDTRANSFEROBJECT pObj)
342{
343 return pObj->enmType;
344}
345
346/**
347 * Returns whether the processing of the object is complete or not.
348 * For file objects this means that all bytes have been processed.
349 *
350 * @return True if complete, False if not.
351 * @param pObj DnD transfer object to get completion status for.
352 */
353bool DnDTransferObjectIsComplete(PDNDTRANSFEROBJECT pObj)
354{
355 bool fComplete;
356
357 switch (pObj->enmType)
358 {
359 case DNDTRANSFEROBJTYPE_FILE:
360 Assert(pObj->u.File.cbProcessed <= pObj->u.File.cbToProcess);
361 fComplete = pObj->u.File.cbProcessed == pObj->u.File.cbToProcess;
362 break;
363
364 case DNDTRANSFEROBJTYPE_DIRECTORY:
365 fComplete = true;
366 break;
367
368 default:
369 fComplete = true;
370 break;
371 }
372
373 return fComplete;
374}
375
376/**
377 * Returns whether the object is in an open state or not.
378 * @param pObj DnD transfer object to get open status for.
379 */
380bool DnDTransferObjectIsOpen(PDNDTRANSFEROBJECT pObj)
381{
382 switch (pObj->enmType)
383 {
384 case DNDTRANSFEROBJTYPE_FILE: return RTFileIsValid(pObj->u.File.hFile);
385 case DNDTRANSFEROBJTYPE_DIRECTORY: return RTDirIsValid(pObj->u.Dir.hDir);
386 default: break;
387 }
388
389 return false;
390}
391
392/**
393 * Open the object with a specific file type, and, depending on the type, specifying additional parameters.
394 *
395 * @return IPRT status code.
396 * @param pObj DnD transfer object to open.
397 * @param fOpen Open mode to use; only valid for file objects.
398 * @param fMode File mode to set; only valid for file objects. Depends on fOpen and and can be 0.
399 * @param fFlags Additional DnD transfer object flags.
400 */
401int DnDTransferObjectOpen(PDNDTRANSFEROBJECT pObj, uint64_t fOpen, RTFMODE fMode, DNDTRANSFEROBJECTFLAGS fFlags)
402{
403 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
404 AssertReturn(fOpen, VERR_INVALID_FLAGS);
405 /* fMode is optional. */
406 AssertReturn(!(fFlags & ~DNDTRANSFEROBJECT_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
407 RT_NOREF1(fFlags);
408
409 int rc = VINF_SUCCESS;
410
411 LogFlowFunc(("pszPath=%s, fOpen=0x%x, fMode=0x%x, fFlags=0x%x\n", pObj->pszPath, fOpen, fMode, fFlags));
412
413 switch (pObj->enmType)
414 {
415 case DNDTRANSFEROBJTYPE_FILE:
416 {
417 LogRel2(("DnD: Opening file '%s'\n", pObj->pszPath));
418
419 /*
420 * Open files on the source with RTFILE_O_DENY_WRITE to prevent races
421 * where the OS writes to the file while the destination side transfers
422 * it over.
423 */
424 rc = RTFileOpen(&pObj->u.File.hFile, pObj->pszPath, fOpen);
425 if (RT_SUCCESS(rc))
426 {
427 if ( (fOpen & RTFILE_O_WRITE) /* Only set the file mode on write. */
428 && fMode /* Some file mode to set specified? */)
429 {
430 rc = RTFileSetMode(pObj->u.File.hFile, fMode);
431 if (RT_FAILURE(rc))
432 LogRel(("DnD: Setting mode %#x for file '%s' failed with %Rrc\n", fMode, pObj->pszPath, rc));
433 }
434 else if (fOpen & RTFILE_O_READ)
435 {
436 rc = dndTransferObjectQueryInfoInternal(pObj);
437 }
438 }
439 else
440 LogRel(("DnD: Opening file '%s' failed with %Rrc\n", pObj->pszPath, rc));
441
442 if (RT_SUCCESS(rc))
443 {
444 LogFlowFunc(("File cbObject=%RU64, fMode=0x%x\n",
445 pObj->u.File.objInfo.cbObject, pObj->u.File.objInfo.Attr.fMode));
446 pObj->u.File.cbToProcess = pObj->u.File.objInfo.cbObject;
447 pObj->u.File.cbProcessed = 0;
448 }
449
450 break;
451 }
452
453 case DNDTRANSFEROBJTYPE_DIRECTORY:
454 {
455 LogRel2(("DnD: Opening directory '%s'\n", pObj->pszPath));
456
457 rc = RTDirOpen(&pObj->u.Dir.hDir, pObj->pszPath);
458 if (RT_SUCCESS(rc))
459 {
460 rc = dndTransferObjectQueryInfoInternal(pObj);
461 }
462 else
463 LogRel(("DnD: Opening directory '%s' failed with %Rrc\n", pObj->pszPath, rc));
464 break;
465 }
466
467 default:
468 rc = VERR_NOT_IMPLEMENTED;
469 break;
470 }
471
472 LogFlowFuncLeaveRC(rc);
473 return rc;
474}
475
476/**
477 * Queries information about the object using a specific view, internal version.
478 *
479 * @return IPRT status code.
480 * @param pObj DnD transfer object to query info for.
481 */
482static int dndTransferObjectQueryInfoInternal(PDNDTRANSFEROBJECT pObj)
483{
484 int rc;
485
486 switch (pObj->enmType)
487 {
488 case DNDTRANSFEROBJTYPE_FILE:
489 AssertMsgReturn(RTFileIsValid(pObj->u.File.hFile), ("Object has invalid file handle\n"), VERR_INVALID_STATE);
490 rc = RTFileQueryInfo(pObj->u.File.hFile, &pObj->u.File.objInfo, RTFSOBJATTRADD_NOTHING);
491 break;
492
493 case DNDTRANSFEROBJTYPE_DIRECTORY:
494 AssertMsgReturn(RTDirIsValid(pObj->u.Dir.hDir), ("Object has invalid directory handle\n"), VERR_INVALID_STATE);
495 rc = RTDirQueryInfo(pObj->u.Dir.hDir, &pObj->u.Dir.objInfo, RTFSOBJATTRADD_NOTHING);
496 break;
497
498 default:
499 rc = VERR_NOT_IMPLEMENTED;
500 break;
501 }
502
503 if (RT_FAILURE(rc))
504 LogRel(("DnD: Querying information for '%s' failed with %Rrc\n", pObj->pszPath, rc));
505
506 return rc;
507}
508
509/**
510 * Queries information about the object using a specific view.
511 *
512 * @return IPRT status code.
513 * @param pObj DnD transfer object to query info for.
514 */
515int DnDTransferObjectQueryInfo(PDNDTRANSFEROBJECT pObj)
516{
517 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
518 return dndTransferObjectQueryInfoInternal(pObj);
519}
520
521/**
522 * Reads data from the object. Only applies to files objects.
523 *
524 * @return IPRT status code.
525 * @param pObj DnD transfer object to read data from.
526 * @param pvBuf Buffer where to store the read data.
527 * @param cbBuf Size (in bytes) of the buffer.
528 * @param pcbRead Pointer where to store how many bytes were read. Optional.
529 */
530int DnDTransferObjectRead(PDNDTRANSFEROBJECT pObj, void *pvBuf, size_t cbBuf, uint32_t *pcbRead)
531{
532 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
533 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
534 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
535 /* pcbRead is optional. */
536
537 size_t cbRead = 0;
538
539 int rc;
540 switch (pObj->enmType)
541 {
542 case DNDTRANSFEROBJTYPE_FILE:
543 {
544 rc = RTFileRead(pObj->u.File.hFile, pvBuf, cbBuf, &cbRead);
545 if (RT_SUCCESS(rc))
546 {
547 pObj->u.File.cbProcessed += cbRead;
548 Assert(pObj->u.File.cbProcessed <= pObj->u.File.cbToProcess);
549
550 /* End of file reached or error occurred? */
551 if ( pObj->u.File.cbToProcess
552 && pObj->u.File.cbProcessed == pObj->u.File.cbToProcess)
553 {
554 rc = VINF_EOF;
555 }
556 }
557 else
558 LogRel(("DnD: Reading from file '%s' failed with %Rrc\n", pObj->pszPath, rc));
559 break;
560 }
561
562 case DNDTRANSFEROBJTYPE_DIRECTORY:
563 {
564 rc = VINF_SUCCESS;
565 break;
566 }
567
568 default:
569 rc = VERR_NOT_IMPLEMENTED;
570 break;
571 }
572
573 if (RT_SUCCESS(rc))
574 {
575 if (pcbRead)
576 *pcbRead = (uint32_t)cbRead;
577 }
578
579 LogFlowFunc(("Returning cbRead=%zu, rc=%Rrc\n", cbRead, rc));
580 return rc;
581}
582
583/**
584 * Resets the object's state and closes all related handles.
585 *
586 * @param pObj DnD transfer object to reset.
587 */
588void DnDTransferObjectReset(PDNDTRANSFEROBJECT pObj)
589{
590 AssertPtrReturnVoid(pObj);
591
592 LogFlowFuncEnter();
593
594 dndTransferObjectCloseInternal(pObj);
595
596 pObj->enmType = DNDTRANSFEROBJTYPE_UNKNOWN;
597 pObj->idxDst = 0;
598
599 RTStrFree(pObj->pszPath);
600 pObj->pszPath = NULL;
601
602 RT_ZERO(pObj->u);
603}
604
605/**
606 * Sets the bytes to process by the object.
607 *
608 * Note: Only applies if the object is of type DnDTransferObjectType_File.
609 *
610 * @return IPRT return code.
611 * @param pObj DnD transfer object to set size for.
612 * @param cbSize Size (in bytes) to process.
613 */
614int DnDTransferObjectSetSize(PDNDTRANSFEROBJECT pObj, uint64_t cbSize)
615{
616 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
617 AssertReturn(pObj->enmType == DNDTRANSFEROBJTYPE_FILE, VERR_INVALID_PARAMETER);
618
619 /** @todo Implement sparse file support here. */
620
621 pObj->u.File.cbToProcess = cbSize;
622 return VINF_SUCCESS;
623}
624
625/**
626 * Writes data to an object. Only applies to file objects.
627 *
628 * @return IPRT status code.
629 * @param pObj DnD transfer object to write to.
630 * @param pvBuf Buffer of data to write.
631 * @param cbBuf Size (in bytes) of data to write.
632 * @param pcbWritten Pointer where to store how many bytes were written. Optional.
633 */
634int DnDTransferObjectWrite(PDNDTRANSFEROBJECT pObj, const void *pvBuf, size_t cbBuf, uint32_t *pcbWritten)
635{
636 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
637 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
638 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
639 /* pcbWritten is optional. */
640
641 size_t cbWritten = 0;
642
643 int rc;
644 switch (pObj->enmType)
645 {
646 case DNDTRANSFEROBJTYPE_FILE:
647 {
648 rc = RTFileWrite(pObj->u.File.hFile, pvBuf, cbBuf, &cbWritten);
649 if (RT_SUCCESS(rc))
650 {
651 pObj->u.File.cbProcessed += cbWritten;
652 }
653 else
654 LogRel(("DnD: Writing to file '%s' failed with %Rrc\n", pObj->pszPath, rc));
655 break;
656 }
657
658 case DNDTRANSFEROBJTYPE_DIRECTORY:
659 {
660 rc = VINF_SUCCESS;
661 break;
662 }
663
664 default:
665 rc = VERR_NOT_IMPLEMENTED;
666 break;
667 }
668
669 if (RT_SUCCESS(rc))
670 {
671 if (pcbWritten)
672 *pcbWritten = (uint32_t)cbWritten;
673 }
674
675 LogFlowFunc(("Returning cbWritten=%zu, rc=%Rrc\n", cbWritten, rc));
676 return rc;
677}
678
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette