VirtualBox

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

Last change on this file since 6840 was 6838, checked in by vboxsync, 17 years ago

More bugfixes for shared folders.

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

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