VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedFolders/vbsf.cpp@ 9734

Last change on this file since 9734 was 9146, checked in by vboxsync, 16 years ago

Backed out r31259.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.3 KB
Line 
1/** @file
2 *
3 * Shared Folders:
4 * VBox Shared Folders.
5 */
6
7/*
8 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
19 * Clara, CA 95054 USA or visit http://www.sun.com if you need
20 * additional information or have any questions.
21 */
22
23#include "mappings.h"
24#include "vbsf.h"
25#include "shflhandle.h"
26
27#include <iprt/alloc.h>
28#include <iprt/assert.h>
29#include <iprt/fs.h>
30#include <iprt/dir.h>
31#include <iprt/file.h>
32#include <iprt/path.h>
33#include <iprt/string.h>
34#include <iprt/uni.h>
35
36#undef LogFlow
37#define LogFlow Log
38
39void vbsfStripLastComponent (char *pszFullPath, uint32_t cbFullPathRoot)
40{
41 RTUNICP cp;
42
43 /* Do not strip root. */
44 char *s = pszFullPath + cbFullPathRoot;
45 char *delimSecondLast = NULL;
46 char *delimLast = NULL;
47
48 LogFlowFunc(("%s -> %s\n", pszFullPath, s));
49
50 for (;;)
51 {
52 cp = RTStrGetCp(s);
53
54 if (cp == RTUNICP_INVALID || cp == 0)
55 {
56 break;
57 }
58
59 if (cp == RTPATH_DELIMITER)
60 {
61 if (delimLast != NULL)
62 {
63 delimSecondLast = delimLast;
64 }
65
66 delimLast = s;
67 }
68
69 s = RTStrNextCp (s);
70 }
71
72 if (cp == 0)
73 {
74 if (delimLast + 1 == s)
75 {
76 if (delimSecondLast)
77 {
78 *delimSecondLast = 0;
79 }
80 else if (delimLast)
81 {
82 *delimLast = 0;
83 }
84 }
85 else
86 {
87 if (delimLast)
88 {
89 *delimLast = 0;
90 }
91 }
92 }
93
94 LogFlowFunc(("%s, %s, %s\n", pszFullPath, delimLast, delimSecondLast));
95}
96
97static int vbsfCorrectCasing(char *pszFullPath, char *pszStartComponent)
98{
99 PRTDIRENTRYEX pDirEntry = NULL;
100 uint32_t cbDirEntry, cbComponent;
101 int rc = VERR_FILE_NOT_FOUND;
102 PRTDIR hSearch = 0;
103 char szWildCard[4];
104
105 Log2(("vbsfCorrectCasing: %s %s\n", pszFullPath, pszStartComponent));
106
107 cbComponent = strlen(pszStartComponent);
108
109 cbDirEntry = 4096;
110 pDirEntry = (PRTDIRENTRYEX)RTMemAlloc(cbDirEntry);
111 if (pDirEntry == 0)
112 {
113 AssertFailed();
114 return VERR_NO_MEMORY;
115 }
116
117 /** @todo this is quite inefficient, especially for directories with many files */
118 Assert(pszFullPath < pszStartComponent-1);
119 Assert(*(pszStartComponent-1) == RTPATH_DELIMITER);
120 *(pszStartComponent-1) = 0;
121 strcpy(pDirEntry->szName, pszFullPath);
122 szWildCard[0] = RTPATH_DELIMITER;
123 szWildCard[1] = '*';
124 szWildCard[2] = 0;
125 strcat(pDirEntry->szName, szWildCard);
126
127 rc = RTDirOpenFiltered (&hSearch, pDirEntry->szName, RTDIRFILTER_WINNT);
128 *(pszStartComponent-1) = RTPATH_DELIMITER;
129 if (VBOX_FAILURE(rc))
130 goto end;
131
132 for(;;)
133 {
134 uint32_t cbDirEntrySize = cbDirEntry;
135
136 rc = RTDirReadEx(hSearch, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING);
137 if (rc == VERR_NO_MORE_FILES)
138 break;
139
140 if (VINF_SUCCESS != rc && rc != VWRN_NO_DIRENT_INFO)
141 {
142 AssertFailed();
143 if (rc != VERR_NO_TRANSLATION)
144 break;
145 else
146 continue;
147 }
148
149 Log2(("vbsfCorrectCasing: found %s\n", &pDirEntry->szName[0]));
150 if ( pDirEntry->cbName == cbComponent
151 && !RTStrICmp(pszStartComponent, &pDirEntry->szName[0]))
152 {
153 Log(("Found original name %s (%s)\n", &pDirEntry->szName[0], pszStartComponent));
154 strcpy(pszStartComponent, &pDirEntry->szName[0]);
155 rc = VINF_SUCCESS;
156 break;
157 }
158 }
159
160end:
161 if (VBOX_FAILURE(rc))
162 Log(("vbsfCorrectCasing %s failed with %d\n", pszStartComponent, rc));
163
164 if (pDirEntry)
165 RTMemFree(pDirEntry);
166
167 if (hSearch)
168 RTDirClose(hSearch);
169 return rc;
170}
171
172static int vbsfBuildFullPath (SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath,
173 uint32_t cbPath, char **ppszFullPath, uint32_t *pcbFullPathRoot, bool fWildCard = false)
174{
175 int rc = VINF_SUCCESS;
176
177 char *pszFullPath = NULL;
178
179 /* Query UCS2 root prefix for the path, cbRoot is the length in bytes including trailing (RTUTF16)0. */
180 uint32_t cbRoot = 0;
181 PCRTUTF16 pwszRoot = vbsfMappingsQueryHostRoot (root, &cbRoot);
182
183 if (!pwszRoot || cbRoot == 0)
184 {
185 Log(("vbsfBuildFullPath: invalid root!\n"));
186 return VERR_INVALID_PARAMETER;
187 }
188
189 if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8))
190 {
191 int rc;
192 char *utf8Root;
193
194 rc = RTUtf16ToUtf8 (pwszRoot, &utf8Root);
195 if (VBOX_SUCCESS (rc))
196 {
197 size_t cbUtf8Root, cbUtf8FullPath;
198 char *utf8FullPath;
199
200 cbUtf8Root = strlen (utf8Root);
201 cbUtf8FullPath = cbUtf8Root + 1 + pPath->u16Length + 1;
202 utf8FullPath = (char *) RTMemAllocZ (cbUtf8FullPath);
203
204 if (!utf8FullPath)
205 {
206 rc = VERR_NO_MEMORY;
207 *ppszFullPath = NULL;
208 Log(("RTMemAllocZ %x failed!!\n", cbUtf8FullPath));
209 }
210 else
211 {
212 memcpy (utf8FullPath, utf8Root, cbUtf8Root);
213 memcpy (utf8FullPath + cbUtf8Root + 1,
214 &pPath->String.utf8[0],
215 pPath->u16Length);
216
217 utf8FullPath[cbUtf8Root] = '/';
218 utf8FullPath[cbUtf8FullPath - 1] = 0;
219 pszFullPath = utf8FullPath;
220
221 if (pcbFullPathRoot)
222 *pcbFullPathRoot = cbUtf8Root; /* Must index the path delimiter. */
223 }
224
225 RTStrFree (utf8Root);
226 }
227 else
228 {
229 Log (("vbsfBuildFullPath: RTUtf16ToUtf8 failed with %Vrc\n", rc));
230 }
231 }
232 else
233 {
234 /* Client sends us UCS2, so convert it to UTF8. */
235 Log(("Root %ls path %.*ls\n", pwszRoot, pPath->u16Length/sizeof(pPath->String.ucs2[0]), pPath->String.ucs2));
236
237 /* Allocate buffer that will be able to contain
238 * the root prefix and the pPath converted to UTF8.
239 * Expect a 2 bytes UCS2 to be converted to 8 bytes UTF8
240 * in worst case.
241 */
242 uint32_t cbFullPath = (cbRoot/sizeof (RTUTF16) + ShflStringLength (pPath)) * 4;
243
244 pszFullPath = (char *)RTMemAllocZ (cbFullPath);
245
246 if (!pszFullPath)
247 {
248 rc = VERR_NO_MEMORY;
249 }
250 else
251 {
252 uint32_t cb = cbFullPath;
253
254 rc = RTUtf16ToUtf8Ex (pwszRoot, RTSTR_MAX, &pszFullPath, cb, NULL);
255 if (VBOX_FAILURE(rc))
256 {
257 AssertFailed();
258 return rc;
259 }
260
261 char *dst = pszFullPath;
262
263 cbRoot = strlen(dst);
264 if (dst[cbRoot - 1] != RTPATH_DELIMITER)
265 {
266 dst[cbRoot] = RTPATH_DELIMITER;
267 cbRoot++;
268 }
269
270 if (pcbFullPathRoot)
271 *pcbFullPathRoot = cbRoot - 1; /* Must index the path delimiter. */
272
273 dst += cbRoot;
274 cb -= cbRoot;
275
276 if (pPath->u16Length)
277 {
278 /* Convert and copy components. */
279 PRTUTF16 src = &pPath->String.ucs2[0];
280
281 /* Correct path delimiters */
282 if (pClient->PathDelimiter != RTPATH_DELIMITER)
283 {
284 LogFlow(("Correct path delimiter in %ls\n", src));
285 while (*src)
286 {
287 if (*src == pClient->PathDelimiter)
288 *src = RTPATH_DELIMITER;
289 src++;
290 }
291 src = &pPath->String.ucs2[0];
292 LogFlow(("Corrected string %ls\n", src));
293 }
294 if (*src == RTPATH_DELIMITER)
295 src++; /* we already appended a delimiter to the first part */
296
297 rc = RTUtf16ToUtf8Ex (src, RTSTR_MAX, &dst, cb, NULL);
298 if (VBOX_FAILURE(rc))
299 {
300 AssertFailed();
301 return rc;
302 }
303
304 uint32_t l = strlen (dst);
305
306 cb -= l;
307 dst += l;
308
309 Assert(cb > 0);
310 }
311
312 /* Nul terminate the string */
313 *dst = 0;
314 }
315 }
316
317 if (VBOX_SUCCESS (rc))
318 {
319 /* When the host file system is case sensitive and the guest expects a case insensitive fs, then problems can occur */
320 if ( vbsfIsHostMappingCaseSensitive (root)
321 && !vbsfIsGuestMappingCaseSensitive(root))
322 {
323 RTFSOBJINFO info;
324 char *pszWildCardComponent = NULL;
325
326 if (fWildCard)
327 {
328 /* strip off the last path component, that contains the wildcard(s) */
329 uint32_t len = strlen(pszFullPath);
330 char *src = pszFullPath + len - 1;
331
332 while(src > pszFullPath)
333 {
334 if (*src == RTPATH_DELIMITER)
335 break;
336 src--;
337 }
338 if (*src == RTPATH_DELIMITER)
339 {
340 bool fHaveWildcards = false;
341 char *temp = src;
342
343 while(*temp)
344 {
345 char uc = *temp;
346 if (uc == '*' || uc == '?' || uc == '>' || uc == '<' || uc == '"')
347 {
348 fHaveWildcards = true;
349 break;
350 }
351 temp++;
352 }
353
354 if (fHaveWildcards)
355 {
356 pszWildCardComponent = src;
357 *pszWildCardComponent = 0;
358 }
359 }
360 }
361
362 /** @todo don't check when creating files or directories; waste of time */
363 rc = RTPathQueryInfo(pszFullPath, &info, RTFSOBJATTRADD_NOTHING);
364 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
365 {
366 uint32_t len = strlen(pszFullPath);
367 char *src = pszFullPath + len - 1;
368
369 Log(("Handle case insenstive guest fs on top of host case sensitive fs for %s\n", pszFullPath));
370
371 /* Find partial path that's valid */
372 while(src > pszFullPath)
373 {
374 if (*src == RTPATH_DELIMITER)
375 {
376 *src = 0;
377 rc = RTPathQueryInfo (pszFullPath, &info, RTFSOBJATTRADD_NOTHING);
378 *src = RTPATH_DELIMITER;
379 if (rc == VINF_SUCCESS)
380 {
381#ifdef DEBUG
382 *src = 0;
383 Log(("Found valid partial path %s\n", pszFullPath));
384 *src = RTPATH_DELIMITER;
385#endif
386 break;
387 }
388 }
389
390 src--;
391 }
392 Assert(*src == RTPATH_DELIMITER && VBOX_SUCCESS(rc));
393 if ( *src == RTPATH_DELIMITER
394 && VBOX_SUCCESS(rc))
395 {
396 src++;
397 for(;;)
398 {
399 char *end = src;
400 bool fEndOfString = true;
401
402 while(*end)
403 {
404 if (*end == RTPATH_DELIMITER)
405 break;
406 end++;
407 }
408
409 if (*end == RTPATH_DELIMITER)
410 {
411 fEndOfString = false;
412 *end = 0;
413 rc = RTPathQueryInfo(src, &info, RTFSOBJATTRADD_NOTHING);
414 Assert(rc == VINF_SUCCESS || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND);
415 }
416 else
417 if (end == src)
418 rc = VINF_SUCCESS; /* trailing delimiter */
419 else
420 rc = VERR_FILE_NOT_FOUND;
421
422 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
423 {
424 /* path component is invalid; try to correct the casing */
425 rc = vbsfCorrectCasing(pszFullPath, src);
426 if (VBOX_FAILURE(rc))
427 {
428 if (!fEndOfString)
429 *end = RTPATH_DELIMITER; /* restore the original full path */
430 break;
431 }
432 }
433
434 if (fEndOfString)
435 break;
436
437 *end = RTPATH_DELIMITER;
438 src = end + 1;
439 }
440 if (VBOX_FAILURE(rc))
441 Log(("Unable to find suitable component rc=%d\n", rc));
442 }
443 else
444 rc = VERR_FILE_NOT_FOUND;
445
446 }
447 if (pszWildCardComponent)
448 *pszWildCardComponent = RTPATH_DELIMITER;
449
450 /* might be a new file so don't fail here! */
451 rc = VINF_SUCCESS;
452 }
453 *ppszFullPath = pszFullPath;
454
455 LogFlow(("vbsfBuildFullPath: %s rc=%Vrc\n", pszFullPath, rc));
456 }
457
458 return rc;
459}
460
461static void vbsfFreeFullPath (char *pszFullPath)
462{
463 RTMemFree (pszFullPath);
464}
465
466/**
467 * Convert shared folder create flags (see include/iprt/shflsvc.h) into iprt create flags.
468 *
469 * @returns iprt status code
470 * @param fShflFlags shared folder create flags
471 * @retval pfOpen iprt create flags
472 */
473static int vbsfConvertFileOpenFlags(unsigned fShflFlags, unsigned *pfOpen)
474{
475 unsigned fOpen = 0;
476 int rc = VINF_SUCCESS;
477
478 switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_RW))
479 {
480 default:
481 case SHFL_CF_ACCESS_NONE:
482 {
483 /** @todo treat this as read access, but theoretically this could be a no access request. */
484 fOpen |= RTFILE_O_READ;
485 Log(("FLAG: SHFL_CF_ACCESS_NONE\n"));
486 break;
487 }
488
489 case SHFL_CF_ACCESS_READ:
490 {
491 fOpen |= RTFILE_O_READ;
492 Log(("FLAG: SHFL_CF_ACCESS_READ\n"));
493 break;
494 }
495
496 case SHFL_CF_ACCESS_WRITE:
497 {
498 fOpen |= RTFILE_O_WRITE;
499 Log(("FLAG: SHFL_CF_ACCESS_WRITE\n"));
500 break;
501 }
502
503 case SHFL_CF_ACCESS_READWRITE:
504 {
505 fOpen |= RTFILE_O_READWRITE;
506 Log(("FLAG: SHFL_CF_ACCESS_READWRITE\n"));
507 break;
508 }
509 }
510
511 /* Sharing mask */
512 switch (BIT_FLAG(fShflFlags, SHFL_CF_ACCESS_MASK_DENY))
513 {
514 default:
515 case SHFL_CF_ACCESS_DENYNONE:
516 fOpen |= RTFILE_O_DENY_NONE;
517 Log(("FLAG: SHFL_CF_ACCESS_DENYNONE\n"));
518 break;
519
520 case SHFL_CF_ACCESS_DENYREAD:
521 fOpen |= RTFILE_O_DENY_READ;
522 Log(("FLAG: SHFL_CF_ACCESS_DENYREAD\n"));
523 break;
524
525 case SHFL_CF_ACCESS_DENYWRITE:
526 fOpen |= RTFILE_O_DENY_WRITE;
527 Log(("FLAG: SHFL_CF_ACCESS_DENYWRITE\n"));
528 break;
529
530 case SHFL_CF_ACCESS_DENYALL:
531 fOpen |= RTFILE_O_DENY_ALL;
532 Log(("FLAG: SHFL_CF_ACCESS_DENYALL\n"));
533 break;
534 }
535
536 /* Open/Create action mask */
537 switch (BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_EXISTS))
538 {
539 case SHFL_CF_ACT_OPEN_IF_EXISTS:
540 if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW))
541 {
542 fOpen |= RTFILE_O_OPEN_CREATE;
543 Log(("FLAGS: SHFL_CF_ACT_OPEN_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n"));
544 }
545 else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW))
546 {
547 fOpen |= RTFILE_O_OPEN;
548 Log(("FLAGS: SHFL_CF_ACT_OPEN_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n"));
549 }
550 else
551 {
552 Log(("FLAGS: invalid open/create action combination\n"));
553 rc = VERR_INVALID_PARAMETER;
554 }
555 break;
556 case SHFL_CF_ACT_FAIL_IF_EXISTS:
557 if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW))
558 {
559 fOpen |= RTFILE_O_CREATE;
560 Log(("FLAGS: SHFL_CF_ACT_FAIL_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n"));
561 }
562 else
563 {
564 Log(("FLAGS: invalid open/create action combination\n"));
565 rc = VERR_INVALID_PARAMETER;
566 }
567 break;
568 case SHFL_CF_ACT_REPLACE_IF_EXISTS:
569 if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW))
570 {
571 fOpen |= RTFILE_O_CREATE_REPLACE;
572 Log(("FLAGS: SHFL_CF_ACT_REPLACE_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n"));
573 }
574 else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW))
575 {
576 fOpen |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE;
577 Log(("FLAGS: SHFL_CF_ACT_REPLACE_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n"));
578 }
579 else
580 {
581 Log(("FLAGS: invalid open/create action combination\n"));
582 rc = VERR_INVALID_PARAMETER;
583 }
584 break;
585 case SHFL_CF_ACT_OVERWRITE_IF_EXISTS:
586 if (SHFL_CF_ACT_CREATE_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW))
587 {
588 fOpen |= RTFILE_O_CREATE_REPLACE;
589 Log(("FLAGS: SHFL_CF_ACT_OVERWRITE_IF_EXISTS and SHFL_CF_ACT_CREATE_IF_NEW\n"));
590 }
591 else if (SHFL_CF_ACT_FAIL_IF_NEW == BIT_FLAG(fShflFlags, SHFL_CF_ACT_MASK_IF_NEW))
592 {
593 fOpen |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE;
594 Log(("FLAGS: SHFL_CF_ACT_OVERWRITE_IF_EXISTS and SHFL_CF_ACT_FAIL_IF_NEW\n"));
595 }
596 else
597 {
598 Log(("FLAGS: invalid open/create action combination\n"));
599 rc = VERR_INVALID_PARAMETER;
600 }
601 break;
602 default:
603 rc = VERR_INVALID_PARAMETER;
604 Log(("FLAG: SHFL_CF_ACT_MASK_IF_EXISTS - invalid parameter\n"));
605 }
606
607 if (RT_SUCCESS(rc))
608 {
609 *pfOpen = fOpen;
610 }
611 return rc;
612}
613
614/**
615 * Open a file or create and open a new one.
616 *
617 * @returns IPRT status code
618 * @param pszPath Path to the file or folder on the host.
619 * @param pParms->CreateFlags Creation or open parameters, see include/VBox/shflsvc.h
620 * @param pParms->Info When a new file is created this specifies the initial parameters.
621 * When a file is created or overwritten, it also specifies the
622 * initial size.
623 * @retval pParms->Result Shared folder status code, see include/VBox/shflsvc.h
624 * @retval pParms->Handle On success the (shared folder) handle of the file opened or
625 * created
626 * @retval pParms->Info On success the parameters of the file opened or created
627 */
628static int vbsfOpenFile (const char *pszPath, SHFLCREATEPARMS *pParms)
629{
630 LogFlow(("vbsfOpenFile: pszPath = %s, pParms = %p\n", pszPath, pParms));
631 Log(("SHFL create flags %08x\n", pParms->CreateFlags));
632
633 SHFLHANDLE handle = SHFL_HANDLE_NIL;
634 SHFLFILEHANDLE *pHandle = 0;
635 /* Open or create a file. */
636 unsigned fOpen = 0;
637 bool fNoError = false;
638
639 int rc = vbsfConvertFileOpenFlags(pParms->CreateFlags, &fOpen);
640 if (RT_SUCCESS(rc))
641 {
642 handle = vbsfAllocFileHandle();
643 }
644 if (SHFL_HANDLE_NIL != handle)
645 {
646 rc = VERR_NO_MEMORY; /* If this fails - rewritten immediately on success. */
647 pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(handle, SHFL_HF_TYPE_FILE);
648 }
649 if (0 != pHandle)
650 {
651 rc = RTFileOpen(&pHandle->file.Handle, pszPath, fOpen);
652 }
653 if (RT_FAILURE (rc))
654 {
655 switch (rc)
656 {
657 case VERR_FILE_NOT_FOUND:
658 pParms->Result = SHFL_FILE_NOT_FOUND;
659
660 /* This actually isn't an error, so correct the rc before return later,
661 because the driver (VBoxSF.sys) expects rc = VINF_SUCCESS and checks the result code. */
662 fNoError = true;
663 break;
664 case VERR_PATH_NOT_FOUND:
665 pParms->Result = SHFL_PATH_NOT_FOUND;
666
667 /* This actually isn't an error, so correct the rc before return later,
668 because the driver (VBoxSF.sys) expects rc = VINF_SUCCESS and checks the result code. */
669 fNoError = true;
670 break;
671 case VERR_ALREADY_EXISTS:
672 RTFSOBJINFO info;
673
674 /** @todo Possible race left here. */
675 if (RT_SUCCESS(RTPathQueryInfo (pszPath, &info, RTFSOBJATTRADD_NOTHING)))
676 {
677 pParms->Info = info;
678 }
679 pParms->Result = SHFL_FILE_EXISTS;
680
681 /* This actually isn't an error, so correct the rc before return later,
682 because the driver (VBoxSF.sys) expects rc = VINF_SUCCESS and checks the result code. */
683 fNoError = true;
684 break;
685 default:
686 pParms->Result = SHFL_NO_RESULT;
687 }
688 }
689
690 if (RT_SUCCESS(rc))
691 {
692 /** @note The shared folder status code is very approximate, as the runtime
693 * does not really provide this information. */
694 pParms->Result = SHFL_FILE_EXISTS; /* We lost the information as to whether it was
695 created when we eliminated the race. */
696 if ( ( SHFL_CF_ACT_REPLACE_IF_EXISTS
697 == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS))
698 || ( SHFL_CF_ACT_OVERWRITE_IF_EXISTS
699 == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS)))
700 {
701 /* For now, we do not treat a failure here as fatal. */
702 /* @todo Also set the size for SHFL_CF_ACT_CREATE_IF_NEW if
703 SHFL_CF_ACT_FAIL_IF_EXISTS is set. */
704 RTFileSetSize(pHandle->file.Handle, pParms->Info.cbObject);
705 pParms->Result = SHFL_FILE_REPLACED;
706 }
707 if ( ( SHFL_CF_ACT_FAIL_IF_EXISTS
708 == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS))
709 || ( SHFL_CF_ACT_CREATE_IF_NEW
710 == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_NEW)))
711 {
712 pParms->Result = SHFL_FILE_CREATED;
713 }
714#if 0
715 /* @todo */
716 /* Set new attributes. */
717 if ( ( SHFL_CF_ACT_REPLACE_IF_EXISTS
718 == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS))
719 || ( SHFL_CF_ACT_CREATE_IF_NEW
720 == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_NEW)))
721 {
722 RTFileSetTimes(pHandle->file.Handle,
723 &pParms->Info.AccessTime,
724 &pParms->Info.ModificationTime,
725 &pParms->Info.ChangeTime,
726 &pParms->Info.BirthTime
727 );
728
729 RTFileSetMode (pHandle->file.Handle, pParms->Info.Attr.fMode);
730 }
731#endif
732 RTFSOBJINFO info;
733
734 /* Get file information */
735 rc = RTFileQueryInfo (pHandle->file.Handle, &info, RTFSOBJATTRADD_NOTHING);
736 if (RT_SUCCESS(rc))
737 {
738 pParms->Info = info;
739 }
740 }
741 if (RT_FAILURE(rc))
742 {
743 if ( (0 != pHandle)
744 && (NIL_RTFILE != pHandle->file.Handle)
745 && (0 != pHandle->file.Handle))
746 {
747 RTFileClose(pHandle->file.Handle);
748 pHandle->file.Handle = NIL_RTFILE;
749 }
750 if (SHFL_HANDLE_NIL != handle)
751 {
752 vbsfFreeFileHandle (handle);
753 }
754 }
755 else
756 {
757 pParms->Handle = handle;
758 }
759
760 /* Report the driver that all is okay, we're done here */
761 if (fNoError)
762 rc = VINF_SUCCESS;
763
764 LogFlow(("vbsfOpenFile: rc = %Vrc\n", rc));
765 return rc;
766}
767
768/**
769 * Open a folder or create and open a new one.
770 *
771 * @returns IPRT status code
772 * @param pszPath Path to the file or folder on the host.
773 * @param pParms->CreateFlags Creation or open parameters, see include/VBox/shflsvc.h
774 * @retval pParms->Result Shared folder status code, see include/VBox/shflsvc.h
775 * @retval pParms->Handle On success the (shared folder) handle of the folder opened or
776 * created
777 * @retval pParms->Info On success the parameters of the folder opened or created
778 *
779 * @note folders are created with fMode = 0777
780 */
781static int vbsfOpenDir (const char *pszPath, SHFLCREATEPARMS *pParms)
782{
783 LogFlow(("vbsfOpenDir: pszPath = %s, pParms = %p\n", pszPath, pParms));
784 Log(("SHFL create flags %08x\n", pParms->CreateFlags));
785
786 int rc = VERR_NO_MEMORY;
787 SHFLHANDLE handle = vbsfAllocDirHandle();
788 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(handle, SHFL_HF_TYPE_DIR);
789 if (0 != pHandle)
790 {
791 rc = VINF_SUCCESS;
792 pParms->Result = SHFL_FILE_EXISTS; /* May be overwritten with SHFL_FILE_CREATED. */
793 /** @todo Can anyone think of a sensible, race-less way to do this? Although
794 I suspect that the race is inherent, due to the API available... */
795 /* Try to create the folder first if "create if new" is specified. If this
796 fails, and "open if exists" is specified, then we ignore the failure and try
797 to open the folder anyway. */
798 if ( SHFL_CF_ACT_CREATE_IF_NEW
799 == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_NEW))
800 {
801 /** @todo render supplied attributes.
802 * bird: The guest should specify this. For windows guests RTFS_DOS_DIRECTORY should suffice. */
803 RTFMODE fMode = 0777;
804
805 pParms->Result = SHFL_FILE_CREATED;
806 rc = RTDirCreate(pszPath, fMode);
807 if (RT_FAILURE (rc))
808 {
809 switch (rc)
810 {
811 case VERR_ALREADY_EXISTS:
812 pParms->Result = SHFL_FILE_EXISTS;
813 break;
814 case VERR_PATH_NOT_FOUND:
815 pParms->Result = SHFL_PATH_NOT_FOUND;
816 break;
817 default:
818 pParms->Result = SHFL_NO_RESULT;
819 }
820 }
821 }
822 if ( RT_SUCCESS(rc)
823 || (SHFL_CF_ACT_OPEN_IF_EXISTS == BIT_FLAG(pParms->CreateFlags, SHFL_CF_ACT_MASK_IF_EXISTS)))
824 {
825 /* Open the directory now */
826 rc = RTDirOpen (&pHandle->dir.Handle, pszPath);
827 if (RT_SUCCESS(rc))
828 {
829 RTFSOBJINFO info;
830
831 rc = RTDirQueryInfo (pHandle->dir.Handle, &info, RTFSOBJATTRADD_NOTHING);
832 if (RT_SUCCESS(rc))
833 {
834 pParms->Info = info;
835 }
836 }
837 else
838 {
839 switch (rc)
840 {
841 case VERR_FILE_NOT_FOUND: /* Does this make sense? */
842 pParms->Result = SHFL_FILE_NOT_FOUND;
843 break;
844 case VERR_PATH_NOT_FOUND:
845 pParms->Result = SHFL_PATH_NOT_FOUND;
846 break;
847 case VERR_ACCESS_DENIED:
848 pParms->Result = SHFL_FILE_EXISTS;
849 break;
850 default:
851 pParms->Result = SHFL_NO_RESULT;
852 }
853 }
854 }
855 }
856 if (RT_FAILURE(rc))
857 {
858 if ( (0 != pHandle)
859 && (0 != pHandle->dir.Handle))
860 {
861 RTDirClose(pHandle->dir.Handle);
862 pHandle->dir.Handle = 0;
863 }
864 if (SHFL_HANDLE_NIL != handle)
865 {
866 vbsfFreeFileHandle (handle);
867 }
868 }
869 else
870 {
871 pParms->Handle = handle;
872 }
873 LogFlow(("vbsfOpenDir: rc = %Vrc\n", rc));
874 return rc;
875}
876
877static int vbsfCloseDir (SHFLFILEHANDLE *pHandle)
878{
879 int rc = VINF_SUCCESS;
880
881 LogFlow(("vbsfCloseDir: Handle = %08X Search Handle = %08X\n",
882 pHandle->dir.Handle, pHandle->dir.SearchHandle));
883
884 RTDirClose (pHandle->dir.Handle);
885
886 if (pHandle->dir.SearchHandle)
887 RTDirClose(pHandle->dir.SearchHandle);
888
889 if (pHandle->dir.pLastValidEntry)
890 {
891 RTMemFree(pHandle->dir.pLastValidEntry);
892 pHandle->dir.pLastValidEntry = NULL;
893 }
894
895 LogFlow(("vbsfCloseDir: rc = %d\n", rc));
896
897 return rc;
898}
899
900
901static int vbsfCloseFile (SHFLFILEHANDLE *pHandle)
902{
903 int rc = VINF_SUCCESS;
904
905 LogFlow(("vbsfCloseFile: Handle = %08X\n",
906 pHandle->file.Handle));
907
908 rc = RTFileClose (pHandle->file.Handle);
909
910 LogFlow(("vbsfCloseFile: rc = %d\n", rc));
911
912 return rc;
913}
914
915/**
916 * Look up file or folder information by host path.
917 *
918 * @returns iprt status code (currently VINF_SUCCESS)
919 * @param pszFullPath The path of the file to be looked up
920 * @retval pParms->Result Status of the operation (success or error)
921 * @retval pParms->Info On success, information returned about the file
922 */
923static int vbsfLookupFile(char *pszPath, SHFLCREATEPARMS *pParms)
924{
925 RTFSOBJINFO info;
926 int rc;
927
928 rc = RTPathQueryInfo (pszPath, &info, RTFSOBJATTRADD_NOTHING);
929 LogFlow(("SHFL_CF_LOOKUP\n"));
930 /* Client just wants to know if the object exists. */
931 switch (rc)
932 {
933 case VINF_SUCCESS:
934 {
935 pParms->Info = info;
936 pParms->Result = SHFL_FILE_EXISTS;
937 break;
938 }
939
940 case VERR_FILE_NOT_FOUND:
941 {
942 pParms->Result = SHFL_FILE_NOT_FOUND;
943 rc = VINF_SUCCESS;
944 break;
945 }
946
947 case VERR_PATH_NOT_FOUND:
948 {
949 pParms->Result = SHFL_PATH_NOT_FOUND;
950 rc = VINF_SUCCESS;
951 break;
952 }
953 }
954 return rc;
955}
956
957/**
958 * Create or open a file or folder. Perform character set and case
959 * conversion on the file name if necessary.
960 *
961 * @returns IPRT status code, but see note below
962 * @param pClient Data structure describing the client accessing the shared
963 * folder
964 * @param root The index of the shared folder in the table of mappings.
965 * The host path of the shared folder is found using this.
966 * @param pPath The path of the file or folder relative to the host path
967 * indexed by root.
968 * @param cbPath Presumably the length of the path in pPath. Actually
969 * ignored, as pPath contains a length parameter.
970 * @param pParms->Info If a new file is created or an old one overwritten, set
971 * these attributes
972 * @retval pParms->Result Shared folder result code, see include/VBox/shflsvc.h
973 * @retval pParms->Handle Shared folder handle to the newly opened file
974 * @retval pParms->Info Attributes of the file or folder opened
975 *
976 * @note This function returns success if a "non-exceptional" error occurred,
977 * such as "no such file". In this case, the caller should check the
978 * pParms->Result return value and whether pParms->Handle is valid.
979 */
980int vbsfCreate (SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, SHFLCREATEPARMS *pParms)
981{
982 int rc = VINF_SUCCESS;
983
984 LogFlow(("vbsfCreate: pClient = %p, pPath = %p, cbPath = %d, pParms = %p CreateFlags=%x\n",
985 pClient, pPath, cbPath, pParms, pParms->CreateFlags));
986
987 /* Check the client access rights to the root. */
988 /** @todo */
989
990 /* Build a host full path for the given path, handle file name case issues (if the guest
991 * expects case-insensitive paths but the host is case-sensitive) and convert ucs2 to utf8 if
992 * necessary.
993 */
994 char *pszFullPath = NULL;
995 uint32_t cbFullPathRoot = 0;
996
997 rc = vbsfBuildFullPath (pClient, root, pPath, cbPath, &pszFullPath, &cbFullPathRoot);
998
999 if (VBOX_SUCCESS (rc))
1000 {
1001 /* Reset return values in case client forgot to do so. */
1002 pParms->Result = SHFL_NO_RESULT;
1003 pParms->Handle = SHFL_HANDLE_NIL;
1004
1005 if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_LOOKUP))
1006 {
1007 rc = vbsfLookupFile(pszFullPath, pParms);
1008 }
1009 else
1010 {
1011 /* Query path information. */
1012 RTFSOBJINFO info;
1013
1014 rc = RTPathQueryInfo (pszFullPath, &info, RTFSOBJATTRADD_NOTHING);
1015 LogFlow(("RTPathQueryInfo returned %Vrc\n", rc));
1016
1017 if (RT_SUCCESS(rc))
1018 {
1019 /* Mark it as a directory in case the caller didn't. */
1020 /**
1021 * @todo I left this in in order not to change the behaviour of the
1022 * function too much. Is it really needed, and should it really be
1023 * here?
1024 */
1025 if (BIT_FLAG(info.Attr.fMode, RTFS_DOS_DIRECTORY))
1026 {
1027 pParms->CreateFlags |= SHFL_CF_DIRECTORY;
1028 }
1029
1030 /**
1031 * @todo This should be in the Windows Guest Additions, as no-one else
1032 * needs it.
1033 */
1034 if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_OPEN_TARGET_DIRECTORY))
1035 {
1036 vbsfStripLastComponent (pszFullPath, cbFullPathRoot);
1037 pParms->CreateFlags &= ~SHFL_CF_ACT_MASK_IF_EXISTS;
1038 pParms->CreateFlags &= ~SHFL_CF_ACT_MASK_IF_NEW;
1039 pParms->CreateFlags |= SHFL_CF_DIRECTORY;
1040 pParms->CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS;
1041 pParms->CreateFlags |= SHFL_CF_ACT_FAIL_IF_NEW;
1042 }
1043 }
1044
1045 rc = VINF_SUCCESS;
1046
1047 /* write access requested? */
1048 if (pParms->CreateFlags & ( SHFL_CF_ACT_REPLACE_IF_EXISTS
1049 | SHFL_CF_ACT_OVERWRITE_IF_EXISTS
1050 | SHFL_CF_ACT_CREATE_IF_NEW
1051 | SHFL_CF_ACCESS_WRITE))
1052 {
1053 /* is the guest allowed to write to this share? */
1054 bool fWritable;
1055 rc = vbsfMappingsQueryWritable (pClient, root, &fWritable);
1056 if (RT_FAILURE(rc) || !fWritable)
1057 rc = VERR_WRITE_PROTECT;
1058 }
1059
1060 if (RT_SUCCESS(rc))
1061 {
1062 if (BIT_FLAG(pParms->CreateFlags, SHFL_CF_DIRECTORY))
1063 {
1064 rc = vbsfOpenDir (pszFullPath, pParms);
1065 }
1066 else
1067 {
1068 rc = vbsfOpenFile (pszFullPath, pParms);
1069 }
1070 }
1071 }
1072
1073 /* free the path string */
1074 vbsfFreeFullPath(pszFullPath);
1075 }
1076
1077 Log(("vbsfCreate: handle = %RX64 rc = %Vrc result=%x\n", (uint64_t)pParms->Handle, rc, pParms->Result));
1078
1079 return rc;
1080}
1081
1082int vbsfClose (SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle)
1083{
1084 int rc = VINF_SUCCESS;
1085
1086 LogFlow(("vbsfClose: pClient = %p, Handle = %RX64\n",
1087 pClient, Handle));
1088
1089 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE);
1090 Assert(pHandle);
1091 if (!pHandle)
1092 return VERR_INVALID_HANDLE;
1093
1094 switch (ShflHandleType (&pHandle->Header))
1095 {
1096 case SHFL_HF_TYPE_DIR:
1097 {
1098 rc = vbsfCloseDir (pHandle);
1099 break;
1100 }
1101 case SHFL_HF_TYPE_FILE:
1102 {
1103 rc = vbsfCloseFile (pHandle);
1104 break;
1105 }
1106 default:
1107 AssertFailed();
1108 break;
1109 }
1110 vbsfFreeFileHandle(Handle);
1111
1112 Log(("vbsfClose: rc = %Rrc\n", rc));
1113
1114 return rc;
1115}
1116
1117int vbsfRead (SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer)
1118{
1119 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_FILE);
1120 size_t count = 0;
1121 int rc;
1122
1123 if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0)
1124 {
1125 AssertFailed();
1126 return VERR_INVALID_PARAMETER;
1127 }
1128
1129 Log(("vbsfRead %RX64 offset %RX64 bytes %x\n", Handle, offset, *pcbBuffer));
1130
1131 if (*pcbBuffer == 0)
1132 return VINF_SUCCESS; /* @todo correct? */
1133
1134
1135 rc = RTFileSeek(pHandle->file.Handle, offset, RTFILE_SEEK_BEGIN, NULL);
1136 if (rc != VINF_SUCCESS)
1137 {
1138 AssertRC(rc);
1139 return rc;
1140 }
1141
1142 rc = RTFileRead(pHandle->file.Handle, pBuffer, *pcbBuffer, &count);
1143 *pcbBuffer = (uint32_t)count;
1144 Log(("RTFileRead returned %Vrc bytes read %x\n", rc, count));
1145 return rc;
1146}
1147
1148int vbsfWrite (SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer)
1149{
1150 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_FILE);
1151 size_t count = 0;
1152 int rc;
1153
1154 if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0)
1155 {
1156 AssertFailed();
1157 return VERR_INVALID_PARAMETER;
1158 }
1159
1160 Log(("vbsfWrite %RX64 offset %RX64 bytes %x\n", Handle, offset, *pcbBuffer));
1161
1162 /* Is the guest allowed to write to this share?
1163 * XXX Actually this check was still done in vbsfCreate() -- RTFILE_O_WRITE cannot be set if vbsfMappingsQueryWritable() failed. */
1164 bool fWritable;
1165 rc = vbsfMappingsQueryWritable (pClient, root, &fWritable);
1166 if (RT_FAILURE(rc) || !fWritable)
1167 return VERR_WRITE_PROTECT;
1168
1169 if (*pcbBuffer == 0)
1170 return VINF_SUCCESS; /** @todo correct? */
1171
1172 rc = RTFileSeek(pHandle->file.Handle, offset, RTFILE_SEEK_BEGIN, NULL);
1173 if (rc != VINF_SUCCESS)
1174 {
1175 AssertRC(rc);
1176 return rc;
1177 }
1178
1179 rc = RTFileWrite(pHandle->file.Handle, pBuffer, *pcbBuffer, &count);
1180 *pcbBuffer = (uint32_t)count;
1181 Log(("RTFileWrite returned %Vrc bytes written %x\n", rc, count));
1182 return rc;
1183}
1184
1185
1186int vbsfFlush(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle)
1187{
1188 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_FILE);
1189 int rc = VINF_SUCCESS;
1190
1191 if (pHandle == 0)
1192 {
1193 AssertFailed();
1194 return VERR_INVALID_HANDLE;
1195 }
1196
1197 Log(("vbsfFlush %RX64\n", Handle));
1198 rc = RTFileFlush(pHandle->file.Handle);
1199 AssertRC(rc);
1200 return rc;
1201}
1202
1203int vbsfDirList(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, SHFLSTRING *pPath, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer,
1204 uint32_t *pIndex, uint32_t *pcFiles)
1205{
1206 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_DIR);
1207 PRTDIRENTRYEX pDirEntry = 0, pDirEntryOrg;
1208 uint32_t cbDirEntry, cbBufferOrg;
1209 int rc = VINF_SUCCESS;
1210 PSHFLDIRINFO pSFDEntry;
1211 PRTUTF16 pwszString;
1212 PRTDIR DirHandle;
1213 bool fUtf8;
1214
1215 fUtf8 = BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8) != 0;
1216
1217 if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0)
1218 {
1219 AssertFailed();
1220 return VERR_INVALID_PARAMETER;
1221 }
1222 Assert(pIndex && *pIndex == 0);
1223 DirHandle = pHandle->dir.Handle;
1224
1225 cbDirEntry = 4096;
1226 pDirEntryOrg = pDirEntry = (PRTDIRENTRYEX)RTMemAlloc(cbDirEntry);
1227 if (pDirEntry == 0)
1228 {
1229 AssertFailed();
1230 return VERR_NO_MEMORY;
1231 }
1232
1233 cbBufferOrg = *pcbBuffer;
1234 *pcbBuffer = 0;
1235 pSFDEntry = (PSHFLDIRINFO)pBuffer;
1236
1237 *pIndex = 1; /* not yet complete */
1238 *pcFiles = 0;
1239
1240 if (pPath)
1241 {
1242 if (pHandle->dir.SearchHandle == 0)
1243 {
1244 /* Build a host full path for the given path
1245 * and convert ucs2 to utf8 if necessary.
1246 */
1247 char *pszFullPath = NULL;
1248
1249 Assert(pHandle->dir.pLastValidEntry == 0);
1250
1251 rc = vbsfBuildFullPath (pClient, root, pPath, pPath->u16Size, &pszFullPath, NULL, true);
1252
1253 if (VBOX_SUCCESS (rc))
1254 {
1255 rc = RTDirOpenFiltered (&pHandle->dir.SearchHandle, pszFullPath, RTDIRFILTER_WINNT);
1256
1257 /* free the path string */
1258 vbsfFreeFullPath(pszFullPath);
1259
1260 if (VBOX_FAILURE (rc))
1261 goto end;
1262 }
1263 else
1264 goto end;
1265 }
1266 Assert(pHandle->dir.SearchHandle);
1267 DirHandle = pHandle->dir.SearchHandle;
1268 }
1269
1270 while(cbBufferOrg)
1271 {
1272 uint32_t cbDirEntrySize = cbDirEntry;
1273 uint32_t cbNeeded;
1274
1275 /* Do we still have a valid last entry for the active search? If so, then return it here */
1276 if (pHandle->dir.pLastValidEntry)
1277 {
1278 pDirEntry = pHandle->dir.pLastValidEntry;
1279 }
1280 else
1281 {
1282 pDirEntry = pDirEntryOrg;
1283
1284 rc = RTDirReadEx(DirHandle, pDirEntry, &cbDirEntrySize, RTFSOBJATTRADD_NOTHING);
1285 if (rc == VERR_NO_MORE_FILES)
1286 {
1287 *pIndex = 0; /* listing completed */
1288 break;
1289 }
1290
1291 if (VINF_SUCCESS != rc && rc != VWRN_NO_DIRENT_INFO)
1292 {
1293 AssertFailed();
1294 if (rc != VERR_NO_TRANSLATION)
1295 break;
1296 else
1297 continue;
1298 }
1299 }
1300
1301 cbNeeded = RT_OFFSETOF (SHFLDIRINFO, name.String);
1302 if (fUtf8)
1303 cbNeeded += pDirEntry->cbName + 1;
1304 else
1305 /* Overestimating, but that's ok */
1306 cbNeeded += (pDirEntry->cbName + 1) * 2;
1307
1308 if (cbBufferOrg < cbNeeded)
1309 {
1310 /* No room, so save this directory entry, or else it's lost forever */
1311 pHandle->dir.pLastValidEntry = pDirEntry;
1312
1313 if (*pcFiles == 0)
1314 {
1315 AssertFailed();
1316 return VINF_BUFFER_OVERFLOW; /* Return directly and don't free pDirEntry */
1317 }
1318 return VINF_SUCCESS; /* Return directly and don't free pDirEntry */
1319 }
1320
1321 pSFDEntry->Info = pDirEntry->Info;
1322 pSFDEntry->cucShortName = 0;
1323
1324 if (fUtf8)
1325 {
1326 void *src, *dst;
1327
1328 src = &pDirEntry->szName[0];
1329 dst = &pSFDEntry->name.String.utf8[0];
1330
1331 memcpy (dst, src, pDirEntry->cbName + 1);
1332
1333 pSFDEntry->name.u16Size = pDirEntry->cbName + 1;
1334 pSFDEntry->name.u16Length = pDirEntry->cbName;
1335 }
1336 else
1337 {
1338 pSFDEntry->name.String.ucs2[0] = 0;
1339 pwszString = pSFDEntry->name.String.ucs2;
1340 int rc2 = RTStrToUtf16Ex(pDirEntry->szName, RTSTR_MAX, &pwszString, pDirEntry->cbName+1, NULL);
1341 AssertRC(rc2);
1342
1343 pSFDEntry->name.u16Length = RTUtf16Len (pSFDEntry->name.String.ucs2) * 2;
1344 pSFDEntry->name.u16Size = pSFDEntry->name.u16Length + 2;
1345
1346 Log(("SHFL: File name size %d\n", pSFDEntry->name.u16Size));
1347 Log(("SHFL: File name %ls\n", &pSFDEntry->name.String.ucs2));
1348
1349 // adjust cbNeeded (it was overestimated before)
1350 cbNeeded = RT_OFFSETOF (SHFLDIRINFO, name.String) + pSFDEntry->name.u16Size;
1351 }
1352
1353 pSFDEntry = (PSHFLDIRINFO)((uintptr_t)pSFDEntry + cbNeeded);
1354 *pcbBuffer += cbNeeded;
1355 cbBufferOrg-= cbNeeded;
1356
1357 *pcFiles += 1;
1358
1359 /* Free the saved last entry, that we've just returned */
1360 if (pHandle->dir.pLastValidEntry)
1361 {
1362 RTMemFree(pHandle->dir.pLastValidEntry);
1363 pHandle->dir.pLastValidEntry = NULL;
1364 }
1365
1366 if (flags & SHFL_LIST_RETURN_ONE)
1367 break; /* we're done */
1368 }
1369 Assert(rc != VINF_SUCCESS || *pcbBuffer > 0);
1370
1371end:
1372 if (pDirEntry)
1373 RTMemFree(pDirEntry);
1374
1375 return rc;
1376}
1377
1378int vbsfQueryFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer)
1379{
1380 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE);
1381 int rc = VINF_SUCCESS;
1382 RTFSOBJINFO *pObjInfo = (RTFSOBJINFO *)pBuffer;
1383
1384
1385 if (pHandle == 0 || pcbBuffer == 0 || pObjInfo == 0 || *pcbBuffer < sizeof(RTFSOBJINFO))
1386 {
1387 AssertFailed();
1388 return VERR_INVALID_PARAMETER;
1389 }
1390
1391 /* @todo other options */
1392 Assert(flags == (SHFL_INFO_GET|SHFL_INFO_FILE));
1393
1394 *pcbBuffer = 0;
1395
1396 if (pHandle->Header.u32Flags & SHFL_HF_TYPE_DIR)
1397 {
1398 rc = RTDirQueryInfo(pHandle->dir.Handle, pObjInfo, RTFSOBJATTRADD_NOTHING);
1399 }
1400 else
1401 {
1402 rc = RTFileQueryInfo(pHandle->file.Handle, pObjInfo, RTFSOBJATTRADD_NOTHING);
1403 }
1404 if (rc == VINF_SUCCESS)
1405 {
1406 *pcbBuffer = sizeof(RTFSOBJINFO);
1407 }
1408 else
1409 AssertFailed();
1410
1411 return rc;
1412}
1413
1414static int vbsfSetFileInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer)
1415{
1416 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE);
1417 int rc = VINF_SUCCESS;
1418 RTFSOBJINFO *pSFDEntry;
1419
1420 if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0 || *pcbBuffer < sizeof(RTFSOBJINFO))
1421 {
1422 AssertFailed();
1423 return VERR_INVALID_PARAMETER;
1424 }
1425
1426 *pcbBuffer = 0;
1427 pSFDEntry = (RTFSOBJINFO *)pBuffer;
1428
1429 Assert(flags == (SHFL_INFO_SET | SHFL_INFO_FILE));
1430
1431 /* Change only the time values that are not zero */
1432 if (pHandle->Header.u32Flags & SHFL_HF_TYPE_DIR)
1433 {
1434 rc = RTDirSetTimes(pHandle->dir.Handle,
1435 (RTTimeSpecGetNano(&pSFDEntry->AccessTime)) ? &pSFDEntry->AccessTime : NULL,
1436 (RTTimeSpecGetNano(&pSFDEntry->ModificationTime)) ? &pSFDEntry->ModificationTime: NULL,
1437 (RTTimeSpecGetNano(&pSFDEntry->ChangeTime)) ? &pSFDEntry->ChangeTime: NULL,
1438 (RTTimeSpecGetNano(&pSFDEntry->BirthTime)) ? &pSFDEntry->BirthTime: NULL
1439 );
1440 }
1441 else
1442 {
1443 rc = RTFileSetTimes(pHandle->file.Handle,
1444 (RTTimeSpecGetNano(&pSFDEntry->AccessTime)) ? &pSFDEntry->AccessTime : NULL,
1445 (RTTimeSpecGetNano(&pSFDEntry->ModificationTime)) ? &pSFDEntry->ModificationTime: NULL,
1446 (RTTimeSpecGetNano(&pSFDEntry->ChangeTime)) ? &pSFDEntry->ChangeTime: NULL,
1447 (RTTimeSpecGetNano(&pSFDEntry->BirthTime)) ? &pSFDEntry->BirthTime: NULL
1448 );
1449 }
1450 if (rc != VINF_SUCCESS)
1451 {
1452 Log(("RTFileSetTimes failed with %Vrc\n", rc));
1453 Log(("AccessTime %VX64\n", RTTimeSpecGetNano(&pSFDEntry->AccessTime)));
1454 Log(("ModificationTime %VX64\n", RTTimeSpecGetNano(&pSFDEntry->ModificationTime)));
1455 Log(("ChangeTime %VX64\n", RTTimeSpecGetNano(&pSFDEntry->ChangeTime)));
1456 Log(("BirthTime %VX64\n", RTTimeSpecGetNano(&pSFDEntry->AccessTime)));
1457 /* temporary hack */
1458 rc = VINF_SUCCESS;
1459 }
1460
1461 if (pHandle->Header.u32Flags & SHFL_HF_TYPE_FILE)
1462 {
1463 /* Change file attributes if necessary */
1464 if (pSFDEntry->Attr.fMode)
1465 {
1466 rc = RTFileSetMode((RTFILE)pHandle->file.Handle, pSFDEntry->Attr.fMode);
1467 if (rc != VINF_SUCCESS)
1468 {
1469 Log(("RTFileSetMode %x failed with %Vrc\n", pSFDEntry->Attr.fMode, rc));
1470 /* silent failure, because this tends to fail with e.g. windows guest & linux host */
1471 rc = VINF_SUCCESS;
1472 }
1473 }
1474 }
1475
1476 if (rc == VINF_SUCCESS)
1477 {
1478 uint32_t bufsize = sizeof(*pSFDEntry);
1479
1480 rc = vbsfQueryFileInfo(pClient, root, Handle, SHFL_INFO_GET|SHFL_INFO_FILE, &bufsize, (uint8_t *)pSFDEntry);
1481 if (rc == VINF_SUCCESS)
1482 {
1483 *pcbBuffer = sizeof(RTFSOBJINFO);
1484 }
1485 else
1486 AssertFailed();
1487 }
1488
1489 return rc;
1490}
1491
1492
1493static int vbsfSetEndOfFile(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer)
1494{
1495 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_FILE);
1496 int rc = VINF_SUCCESS;
1497 RTFSOBJINFO *pSFDEntry;
1498
1499 if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0 || *pcbBuffer < sizeof(RTFSOBJINFO))
1500 {
1501 AssertFailed();
1502 return VERR_INVALID_PARAMETER;
1503 }
1504
1505 *pcbBuffer = 0;
1506 pSFDEntry = (RTFSOBJINFO *)pBuffer;
1507
1508 if (flags & SHFL_INFO_SIZE)
1509 {
1510 rc = RTFileSetSize(pHandle->file.Handle, pSFDEntry->cbObject);
1511 if (rc != VINF_SUCCESS)
1512 AssertFailed();
1513 }
1514 else
1515 AssertFailed();
1516
1517 if (rc == VINF_SUCCESS)
1518 {
1519 RTFSOBJINFO fileinfo;
1520
1521 /* Query the new object info and return it */
1522 rc = RTFileQueryInfo(pHandle->file.Handle, &fileinfo, RTFSOBJATTRADD_NOTHING);
1523 if (rc == VINF_SUCCESS)
1524 {
1525 *pSFDEntry = fileinfo;
1526 *pcbBuffer = sizeof(RTFSOBJINFO);
1527 }
1528 else
1529 AssertFailed();
1530 }
1531
1532 return rc;
1533}
1534
1535int vbsfQueryVolumeInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer)
1536{
1537 int rc = VINF_SUCCESS;
1538 SHFLVOLINFO *pSFDEntry;
1539 char *pszFullPath = NULL;
1540 SHFLSTRING dummy;
1541
1542 if (pcbBuffer == 0 || pBuffer == 0 || *pcbBuffer < sizeof(SHFLVOLINFO))
1543 {
1544 AssertFailed();
1545 return VERR_INVALID_PARAMETER;
1546 }
1547
1548 /* @todo other options */
1549 Assert(flags == (SHFL_INFO_GET|SHFL_INFO_VOLUME));
1550
1551 *pcbBuffer = 0;
1552 pSFDEntry = (PSHFLVOLINFO)pBuffer;
1553
1554 ShflStringInitBuffer(&dummy, sizeof(dummy));
1555 rc = vbsfBuildFullPath (pClient, root, &dummy, 0, &pszFullPath, NULL);
1556
1557 if (VBOX_SUCCESS (rc))
1558 {
1559 rc = RTFsQuerySizes(pszFullPath, &pSFDEntry->ullTotalAllocationBytes, &pSFDEntry->ullAvailableAllocationBytes, &pSFDEntry->ulBytesPerAllocationUnit, &pSFDEntry->ulBytesPerSector);
1560 if (rc != VINF_SUCCESS)
1561 goto exit;
1562
1563 rc = RTFsQuerySerial(pszFullPath, &pSFDEntry->ulSerial);
1564 if (rc != VINF_SUCCESS)
1565 goto exit;
1566
1567 rc = RTFsQueryProperties(pszFullPath, &pSFDEntry->fsProperties);
1568 if (rc != VINF_SUCCESS)
1569 goto exit;
1570
1571 *pcbBuffer = sizeof(SHFLVOLINFO);
1572 }
1573 else AssertFailed();
1574
1575exit:
1576 AssertMsg(rc == VINF_SUCCESS, ("failure: rc = %Vrc\n", rc));
1577 /* free the path string */
1578 vbsfFreeFullPath(pszFullPath);
1579 return rc;
1580}
1581
1582int vbsfQueryFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer)
1583{
1584 if (pcbBuffer == 0 || pBuffer == 0)
1585 {
1586 AssertFailed();
1587 return VERR_INVALID_PARAMETER;
1588 }
1589
1590 if (flags & SHFL_INFO_FILE)
1591 return vbsfQueryFileInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer);
1592
1593 if (flags & SHFL_INFO_VOLUME)
1594 return vbsfQueryVolumeInfo(pClient, root, flags, pcbBuffer, pBuffer);
1595
1596 AssertFailed();
1597 return VERR_INVALID_PARAMETER;
1598}
1599
1600int vbsfSetFSInfo(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint32_t flags, uint32_t *pcbBuffer, uint8_t *pBuffer)
1601{
1602 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_DIR|SHFL_HF_TYPE_FILE|SHFL_HF_TYPE_VOLUME);
1603
1604 if (pHandle == 0 || pcbBuffer == 0 || pBuffer == 0)
1605 {
1606 AssertFailed();
1607 return VERR_INVALID_PARAMETER;
1608 }
1609
1610 /* is the guest allowed to write to this share? */
1611 bool fWritable;
1612 int rc = vbsfMappingsQueryWritable (pClient, root, &fWritable);
1613 if (RT_FAILURE(rc) || !fWritable)
1614 return VERR_WRITE_PROTECT;
1615
1616 if (flags & SHFL_INFO_FILE)
1617 return vbsfSetFileInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer);
1618
1619 if (flags & SHFL_INFO_SIZE)
1620 return vbsfSetEndOfFile(pClient, root, Handle, flags, pcbBuffer, pBuffer);
1621
1622// if (flags & SHFL_INFO_VOLUME)
1623// return vbsfVolumeInfo(pClient, root, Handle, flags, pcbBuffer, pBuffer);
1624 AssertFailed();
1625 return VERR_INVALID_PARAMETER;
1626}
1627
1628int vbsfLock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags)
1629{
1630 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_FILE);
1631 uint32_t fRTLock = 0;
1632 int rc;
1633
1634 Assert((flags & SHFL_LOCK_MODE_MASK) != SHFL_LOCK_CANCEL);
1635
1636 if (pHandle == 0)
1637 {
1638 AssertFailed();
1639 return VERR_INVALID_HANDLE;
1640 }
1641 if ( ((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL)
1642 || (flags & SHFL_LOCK_ENTIRE)
1643 )
1644 {
1645 AssertFailed();
1646 return VERR_INVALID_PARAMETER;
1647 }
1648
1649 /* Lock type */
1650 switch(flags & SHFL_LOCK_MODE_MASK)
1651 {
1652 case SHFL_LOCK_SHARED:
1653 fRTLock = RTFILE_LOCK_READ;
1654 break;
1655
1656 case SHFL_LOCK_EXCLUSIVE:
1657 fRTLock = RTFILE_LOCK_READ | RTFILE_LOCK_WRITE;
1658 break;
1659
1660 default:
1661 AssertFailed();
1662 return VERR_INVALID_PARAMETER;
1663 }
1664
1665 /* Lock wait type */
1666 if (flags & SHFL_LOCK_WAIT)
1667 fRTLock |= RTFILE_LOCK_WAIT;
1668 else
1669 fRTLock |= RTFILE_LOCK_IMMEDIATELY;
1670
1671#ifdef RT_OS_WINDOWS
1672 rc = RTFileLock(pHandle->file.Handle, fRTLock, offset, length);
1673 if (rc != VINF_SUCCESS)
1674 Log(("RTFileLock %RTfile %RX64 %RX64 failed with %Rrc\n", pHandle->file.Handle, offset, length, rc));
1675#else
1676 Log(("vbsfLock: Pretend success handle=%x\n", Handle));
1677 rc = VINF_SUCCESS;
1678#endif
1679 return rc;
1680}
1681
1682int vbsfUnlock(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLHANDLE Handle, uint64_t offset, uint64_t length, uint32_t flags)
1683{
1684 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(Handle, SHFL_HF_TYPE_FILE);
1685 int rc;
1686
1687 Assert((flags & SHFL_LOCK_MODE_MASK) == SHFL_LOCK_CANCEL);
1688
1689 if (pHandle == 0)
1690 {
1691 return VERR_INVALID_HANDLE;
1692 }
1693 if ( ((flags & SHFL_LOCK_MODE_MASK) != SHFL_LOCK_CANCEL)
1694 || (flags & SHFL_LOCK_ENTIRE)
1695 )
1696 {
1697 return VERR_INVALID_PARAMETER;
1698 }
1699
1700#ifdef RT_OS_WINDOWS
1701 rc = RTFileUnlock(pHandle->file.Handle, offset, length);
1702 if (rc != VINF_SUCCESS)
1703 Log(("RTFileUnlock %RTfile %RX64 %RTX64 failed with %Rrc\n", pHandle->file.Handle, offset, length, rc));
1704#else
1705 Log(("vbsfUnlock: Pretend success handle=%x\n", Handle));
1706 rc = VINF_SUCCESS;
1707#endif
1708
1709 return rc;
1710}
1711
1712
1713int vbsfRemove(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pPath, uint32_t cbPath, uint32_t flags)
1714{
1715 int rc = VINF_SUCCESS;
1716
1717 /* Validate input */
1718 if ( flags & ~(SHFL_REMOVE_FILE|SHFL_REMOVE_DIR)
1719 || cbPath == 0
1720 || pPath == 0)
1721 {
1722 AssertFailed();
1723 return VERR_INVALID_PARAMETER;
1724 }
1725
1726 /* Build a host full path for the given path
1727 * and convert ucs2 to utf8 if necessary.
1728 */
1729 char *pszFullPath = NULL;
1730
1731 rc = vbsfBuildFullPath (pClient, root, pPath, cbPath, &pszFullPath, NULL);
1732 if (VBOX_SUCCESS (rc))
1733 {
1734 /* is the guest allowed to write to this share? */
1735 bool fWritable;
1736 rc = vbsfMappingsQueryWritable (pClient, root, &fWritable);
1737 if (RT_FAILURE(rc) || !fWritable)
1738 rc = VERR_WRITE_PROTECT;
1739
1740 if (VBOX_SUCCESS (rc))
1741 {
1742 if (flags & SHFL_REMOVE_FILE)
1743 rc = RTFileDelete(pszFullPath);
1744 else
1745 rc = RTDirRemove(pszFullPath);
1746 }
1747
1748#ifndef DEBUG_dmik
1749 // VERR_ACCESS_DENIED for example?
1750 // Assert(rc == VINF_SUCCESS || rc == VERR_DIR_NOT_EMPTY);
1751#endif
1752 /* free the path string */
1753 vbsfFreeFullPath(pszFullPath);
1754 }
1755 return rc;
1756}
1757
1758
1759int vbsfRename(SHFLCLIENTDATA *pClient, SHFLROOT root, SHFLSTRING *pSrc, SHFLSTRING *pDest, uint32_t flags)
1760{
1761 int rc = VINF_SUCCESS;
1762
1763 /* Validate input */
1764 if ( flags & ~(SHFL_REMOVE_FILE|SHFL_REMOVE_DIR|SHFL_RENAME_REPLACE_IF_EXISTS)
1765 || pSrc == 0
1766 || pDest == 0)
1767 {
1768 AssertFailed();
1769 return VERR_INVALID_PARAMETER;
1770 }
1771
1772 /* Build a host full path for the given path
1773 * and convert ucs2 to utf8 if necessary.
1774 */
1775 char *pszFullPathSrc = NULL;
1776 char *pszFullPathDest = NULL;
1777
1778 rc = vbsfBuildFullPath (pClient, root, pSrc, pSrc->u16Size, &pszFullPathSrc, NULL);
1779 if (rc != VINF_SUCCESS)
1780 return rc;
1781
1782 rc = vbsfBuildFullPath (pClient, root, pDest, pDest->u16Size, &pszFullPathDest, NULL);
1783 if (VBOX_SUCCESS (rc))
1784 {
1785 Log(("Rename %s to %s\n", pszFullPathSrc, pszFullPathDest));
1786
1787 /* is the guest allowed to write to this share? */
1788 bool fWritable;
1789 rc = vbsfMappingsQueryWritable (pClient, root, &fWritable);
1790 if (RT_FAILURE(rc) || !fWritable)
1791 rc = VERR_WRITE_PROTECT;
1792
1793 if (VBOX_SUCCESS (rc))
1794 {
1795 if (flags & SHFL_RENAME_FILE)
1796 {
1797 rc = RTFileMove(pszFullPathSrc, pszFullPathDest, (flags & SHFL_RENAME_REPLACE_IF_EXISTS) ? RTFILEMOVE_FLAGS_REPLACE : 0);
1798 }
1799 else
1800 {
1801 /* NT ignores the REPLACE flag and simply return and already exists error. */
1802 rc = RTDirRename(pszFullPathSrc, pszFullPathDest, (flags & SHFL_RENAME_REPLACE_IF_EXISTS) ? RTPATHRENAME_FLAGS_REPLACE : 0);
1803 }
1804 }
1805
1806#ifndef DEBUG_dmik
1807 AssertRC(rc);
1808#endif
1809 /* free the path string */
1810 vbsfFreeFullPath(pszFullPathDest);
1811 }
1812 /* free the path string */
1813 vbsfFreeFullPath(pszFullPathSrc);
1814 return rc;
1815}
1816
1817/*
1818 * Clean up our mess by freeing all handles that are still valid.
1819 *
1820 */
1821int vbsfDisconnect(SHFLCLIENTDATA *pClient)
1822{
1823 for (int i=0;i<SHFLHANDLE_MAX;i++)
1824 {
1825 SHFLFILEHANDLE *pHandle = (SHFLFILEHANDLE *)vbsfQueryHandle(i, SHFL_HF_TYPE_MASK);
1826
1827 if (pHandle)
1828 {
1829 Log(("Open handle %08x\n", i));
1830 vbsfClose(pClient, SHFL_HANDLE_ROOT /* incorrect, but it's not important */, (SHFLHANDLE)i);
1831 }
1832 }
1833 return VINF_SUCCESS;
1834}
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