VirtualBox

source: vbox/trunk/src/VBox/ImageMounter/VBoxFUSE/VBoxFUSE.cpp@ 24728

Last change on this file since 24728 was 23072, checked in by vboxsync, 15 years ago

VBoxFUSE.cpp: Probably best if EDOOFUS is a known errno value.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.8 KB
Line 
1/* $Id: VBoxFUSE.cpp 23072 2009-09-16 15:39:01Z vboxsync $ */
2/** @file
3 * VBoxFUSE - Disk Image Flattening FUSE Program.
4 */
5
6/*
7 * Copyright (C) 2009 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#define LOG_GROUP LOG_GROUP_DEFAULT /** @todo log group */
27#include <iprt/types.h>
28
29#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX)
30# include <sys/param.h>
31# undef PVM /* Blasted old BSD mess still hanging around darwin. */
32#endif
33#define FUSE_USE_VERSION 27
34#include <fuse.h>
35#include <errno.h>
36#include <fcntl.h>
37#ifndef EDOOFUS
38# ifdef EBADMACHO
39# define EDOOFUS EBADMACHO
40# elif
41# define EDOOFUS EPROTO /* What a boring lot. */
42//# elif defined(EXYZ)
43//# define EDOOFUS EXYZ
44# else
45# error "Choose an unlikely and (if possible) fun error number for EDOOFUS."
46# endif
47#endif
48
49#include <VBox/VBoxHDD.h>
50#include <VBox/log.h>
51#include <VBox/err.h>
52#include <iprt/critsect.h>
53#include <iprt/assert.h>
54#include <iprt/asm.h>
55#include <iprt/mem.h>
56#include <iprt/string.h>
57#include <iprt/initterm.h>
58#include <iprt/stream.h>
59
60
61/*******************************************************************************
62* Structures and Typedefs *
63*******************************************************************************/
64/**
65 * Node type.
66 */
67typedef enum VBOXFUSETYPE
68{
69 VBOXFUSETYPE_INVALID = 0,
70 VBOXFUSETYPE_DIRECTORY,
71 VBOXFUSETYPE_FLAT_IMAGE,
72 VBOXFUSETYPE_CONTROL_PIPE
73} VBOXFUSETYPE;
74
75/**
76 * Stuff common to both directories and files.
77 */
78typedef struct VBOXFUSENODE
79{
80 /** The directory name. */
81 const char *pszName;
82 /** The name length. */
83 size_t cchName;
84 /** The node type. */
85 VBOXFUSETYPE enmType;
86 /** The number of references.
87 * The directory linking this node will always retain one. */
88 int32_t volatile cRefs;
89 /** Critical section serializing access to the node data. */
90 RTCRITSECT CritSect;
91 /** Pointer to the directory (parent). */
92 struct VBOXFUSEDIR *pDir;
93 /** The mode mask. */
94 RTFMODE fMode;
95 /** The User ID of the directory owner. */
96 RTUID Uid;
97 /** The Group ID of the directory. */
98 RTUID Gid;
99 /** The link count. */
100 uint32_t cLinks;
101 /** The inode number. */
102 RTINODE Ino;
103 /** The size of the primary stream. */
104 RTFOFF cbPrimary;
105} VBOXFUSENODE;
106typedef VBOXFUSENODE *PVBOXFUSENODE;
107
108/**
109 * A flat image file.
110 */
111typedef struct VBOXFUSEFLATIMAGE
112{
113 /** The standard bits. */
114 VBOXFUSENODE Node;
115 /** The virtual disk container. */
116 PVBOXHDD pDisk;
117 /** The format name. */
118 char *pszFormat;
119 /** The number of readers.
120 * Read only images will have this set to INT32_MAX/2 on creation. */
121 int32_t cReaders;
122 /** The number of writers. (Just 1 or 0 really.) */
123 int32_t cWriters;
124} VBOXFUSEFLATIMAGE;
125typedef VBOXFUSEFLATIMAGE *PVBOXFUSEFLATIMAGE;
126
127/**
128 * A control pipe (file).
129 */
130typedef struct VBOXFUSECTRLPIPE
131{
132 /** The standard bits. */
133 VBOXFUSENODE Node;
134} VBOXFUSECTRLPIPE;
135typedef VBOXFUSECTRLPIPE *PVBOXFUSECTRLPIPE;
136
137
138/**
139 * A Directory.
140 *
141 * This is just a container of files and subdirectories, nothing special.
142 */
143typedef struct VBOXFUSEDIR
144{
145 /** The standard bits. */
146 VBOXFUSENODE Node;
147 /** The number of directory entries. */
148 uint32_t cEntries;
149 /** Array of pointers to directory entries.
150 * Whether what's being pointed to is a file, directory or something else can be
151 * determined by the enmType field. */
152 PVBOXFUSENODE *paEntries;
153} VBOXFUSEDIR;
154typedef VBOXFUSEDIR *PVBOXFUSEDIR;
155
156/** The number of elements to grow VBOXFUSEDIR::paEntries by. */
157#define VBOXFUSE_DIR_GROW_BY 2 /* 32 */
158
159
160/*******************************************************************************
161* Global Variables *
162*******************************************************************************/
163/** The root of the file hierarchy. */
164static VBOXFUSEDIR *g_pTreeRoot;
165/** The next inode number. */
166static RTINODE volatile g_NextIno = 1;
167
168
169/*******************************************************************************
170* Internal Functions *
171*******************************************************************************/
172static int vboxfuseTreeLookupParent(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir);
173static int vboxfuseTreeLookupParentForInsert(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir);
174
175
176/**
177 * Node destructor.
178 *
179 * @returns true.
180 * @param pNode The node.
181 * @param fLocked Whether it's locked.
182 */
183static bool vboxfuseNodeDestroy(PVBOXFUSENODE pNode, bool fLocked)
184{
185 Assert(pNode->cRefs == 0);
186
187 /*
188 * Type specific cleanups.
189 */
190 switch (pNode->enmType)
191 {
192 case VBOXFUSETYPE_DIRECTORY:
193 {
194 PVBOXFUSEDIR pDir = (PVBOXFUSEDIR)pNode;
195 RTMemFree(pDir->paEntries);
196 pDir->paEntries = NULL;
197 pDir->cEntries = 0;
198 break;
199 }
200
201 case VBOXFUSETYPE_FLAT_IMAGE:
202 {
203 PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)pNode;
204 if (pFlatImage->pDisk)
205 {
206 int rc2 = VDClose(pFlatImage->pDisk, false /* fDelete */); AssertRC(rc2);
207 pFlatImage->pDisk = NULL;
208 }
209 RTStrFree(pFlatImage->pszFormat);
210 pFlatImage->pszFormat = NULL;
211 break;
212 }
213
214 case VBOXFUSETYPE_CONTROL_PIPE:
215 break;
216
217 default:
218 AssertMsgFailed(("%d\n", pNode->enmType));
219 break;
220 }
221
222 /*
223 * Generic cleanup.
224 */
225 pNode->enmType = VBOXFUSETYPE_INVALID;
226 pNode->pszName = NULL;
227
228 /*
229 * Unlock and destroy the lock, before we finally frees the node.
230 */
231 if (fLocked)
232 RTCritSectLeave(&pNode->CritSect);
233 RTCritSectDelete(&pNode->CritSect);
234
235 RTMemFree(pNode);
236
237 return true;
238}
239
240
241/**
242 * Locks a FUSE node.
243 *
244 * @param pNode The node.
245 */
246static void vboxfuseNodeLock(PVBOXFUSENODE pNode)
247{
248 int rc = RTCritSectEnter(&pNode->CritSect);
249 AssertRC(rc);
250}
251
252
253/**
254 * Unlocks a FUSE node.
255 *
256 * @param pNode The node.
257 */
258static void vboxfuseNodeUnlock(PVBOXFUSENODE pNode)
259{
260 int rc = RTCritSectLeave(&pNode->CritSect);
261 AssertRC(rc);
262}
263
264
265/**
266 * Retain a VBoxFUSE node.
267 *
268 * @param pNode The node.
269 */
270static void vboxfuseNodeRetain(PVBOXFUSENODE pNode)
271{
272 int32_t cNewRefs = ASMAtomicIncS32(&pNode->cRefs);
273 Assert(cNewRefs != 1);
274}
275
276
277/**
278 * Releases a VBoxFUSE node reference.
279 *
280 * @returns true if deleted, false if not.
281 * @param pNode The node.
282 */
283static bool vboxfuseNodeRelease(PVBOXFUSENODE pNode)
284{
285 if (ASMAtomicDecS32(&pNode->cRefs) == 0)
286 return vboxfuseNodeDestroy(pNode, false /* fLocked */);
287 return false;
288}
289
290
291/**
292 * Locks and retains a VBoxFUSE node.
293 *
294 * @param pNode The node.
295 */
296static void vboxfuseNodeLockAndRetain(PVBOXFUSENODE pNode)
297{
298 vboxfuseNodeLock(pNode);
299 vboxfuseNodeRetain(pNode);
300}
301
302
303/**
304 * Releases a VBoxFUSE node reference and unlocks it.
305 *
306 * @returns true if deleted, false if not.
307 * @param pNode The node.
308 */
309static bool vboxfuseNodeReleaseAndUnlock(PVBOXFUSENODE pNode)
310{
311 if (ASMAtomicDecS32(&pNode->cRefs) == 0)
312 return vboxfuseNodeDestroy(pNode, true /* fLocked */);
313 vboxfuseNodeUnlock(pNode);
314 return false;
315}
316
317
318/**
319 * Creates stat info for a locked node.
320 *
321 * @param pNode The node (locked).
322 */
323static void vboxfuseNodeFillStat(PVBOXFUSENODE pNode, struct stat *pStat)
324{
325 pStat->st_dev = 0; /* ignored */
326 pStat->st_ino = pNode->Ino; /* maybe ignored */
327 pStat->st_mode = pNode->fMode;
328 pStat->st_nlink = pNode->cLinks;
329 pStat->st_uid = pNode->Uid;
330 pStat->st_gid = pNode->Gid;
331 pStat->st_rdev = 0;
332 /** @todo file times */
333 pStat->st_atime = 0;
334// pStat->st_atimensec = 0;
335 pStat->st_mtime = 0;
336// pStat->st_mtimensec = 0;
337 pStat->st_ctime = 0;
338// pStat->st_ctimensec = 0;
339 pStat->st_size = pNode->cbPrimary;
340 pStat->st_blocks = (pNode->cbPrimary + DEV_BSIZE - 1) / DEV_BSIZE;
341 pStat->st_blksize = 0x1000; /* ignored */
342#ifndef RT_OS_LINUX
343 pStat->st_flags = 0;
344 pStat->st_gen = 0;
345#endif
346}
347
348
349/**
350 * Allocates a new node and initialize the node part of it.
351 *
352 * The returned node has one reference.
353 *
354 * @returns VBox status code.
355 *
356 * @param cbNode The size of the node.
357 * @param pszName The name of the node.
358 * @param enmType The node type.
359 * @param pDir The directory (parent).
360 * @param ppNode Where to return the pointer to the node.
361 */
362static int vboxfuseNodeAlloc(size_t cbNode, const char *pszName, VBOXFUSETYPE enmType, PVBOXFUSEDIR pDir,
363 PVBOXFUSENODE *ppNode)
364{
365 Assert(cbNode >= sizeof(VBOXFUSENODE));
366
367 /*
368 * Allocate the memory for it and init the critical section.
369 */
370 size_t cchName = strlen(pszName);
371 PVBOXFUSENODE pNode = (PVBOXFUSENODE)RTMemAlloc(cchName + 1 + RT_ALIGN_Z(cbNode, 8));
372 if (!pNode)
373 return VERR_NO_MEMORY;
374
375 int rc = RTCritSectInit(&pNode->CritSect);
376 if (RT_FAILURE(rc))
377 {
378 RTMemFree(pNode);
379 return rc;
380 }
381
382 /*
383 * Initialize the members.
384 */
385 pNode->pszName = (char *)memcpy((uint8_t *)pNode + RT_ALIGN_Z(cbNode, 8), pszName, cchName + 1);
386 pNode->cchName = cchName;
387 pNode->enmType = enmType;
388 pNode->cRefs = 1;
389 pNode->pDir = pDir;
390#if 0
391 pNode->fMode = enmType == VBOXFUSETYPE_DIRECTORY ? S_IFDIR | 0755 : S_IFREG | 0644;
392#else
393 pNode->fMode = enmType == VBOXFUSETYPE_DIRECTORY ? S_IFDIR | 0777 : S_IFREG | 0666;
394#endif
395 pNode->Uid = 0;
396 pNode->Gid = 0;
397 pNode->cLinks = 0;
398 pNode->Ino = g_NextIno++; /** @todo make this safe! */
399 pNode->cbPrimary = 0;
400
401 *ppNode = pNode;
402 return VINF_SUCCESS;
403}
404
405
406/**
407 * Inserts a node into a directory
408 *
409 * The caller has locked and referneced the directory as well as checked that
410 * the name doesn't already exist within it. On success both the reference and
411 * and link counters will be incremented.
412 *
413 * @returns VBox status code.
414 *
415 * @param pDir The parent directory. Can be NULL when creating the root
416 * directory.
417 * @param pNode The node to insert.
418 */
419static int vboxfuseDirInsertChild(PVBOXFUSEDIR pDir, PVBOXFUSENODE pNode)
420{
421 if (!pDir)
422 {
423 /*
424 * Special case: Root Directory.
425 */
426 AssertReturn(!g_pTreeRoot, VERR_ALREADY_EXISTS);
427 AssertReturn(pNode->enmType == VBOXFUSETYPE_DIRECTORY, VERR_INTERNAL_ERROR);
428 g_pTreeRoot = (PVBOXFUSEDIR)pNode;
429 }
430 else
431 {
432 /*
433 * Common case.
434 */
435 if (!(pDir->cEntries % VBOXFUSE_DIR_GROW_BY))
436 {
437 void *pvNew = RTMemRealloc(pDir->paEntries, sizeof(*pDir->paEntries) * (pDir->cEntries + VBOXFUSE_DIR_GROW_BY));
438 if (!pvNew)
439 return VERR_NO_MEMORY;
440 pDir->paEntries = (PVBOXFUSENODE *)pvNew;
441 }
442 pDir->paEntries[pDir->cEntries++] = pNode;
443 pDir->Node.cLinks++;
444 }
445
446 vboxfuseNodeRetain(pNode);
447 pNode->cLinks++;
448 return VINF_SUCCESS;
449}
450
451
452/**
453 * Create a directory node.
454 *
455 * @returns VBox status code.
456 * @param pszPath The path to the directory.
457 * @param ppDir Optional, where to return the new directory locked and
458 * referenced (making cRefs == 2).
459 */
460static int vboxfuseDirCreate(const char *pszPath, PVBOXFUSEDIR *ppDir)
461{
462 /*
463 * Figure out where the directory is going.
464 */
465 const char *pszName;
466 PVBOXFUSEDIR pParent;
467 int rc = vboxfuseTreeLookupParentForInsert(pszPath, &pszName, &pParent);
468 if (RT_FAILURE(rc))
469 return rc;
470
471 /*
472 * Allocate and initialize the new directory node.
473 */
474 PVBOXFUSEDIR pNewDir;
475 rc = vboxfuseNodeAlloc(sizeof(*pNewDir), pszName, VBOXFUSETYPE_DIRECTORY, pParent, (PVBOXFUSENODE *)&pNewDir);
476 if (RT_SUCCESS(rc))
477 {
478 pNewDir->cEntries = 0;
479 pNewDir->paEntries = NULL;
480
481 /*
482 * Insert it.
483 */
484 rc = vboxfuseDirInsertChild(pParent, &pNewDir->Node);
485 if ( RT_SUCCESS(rc)
486 && ppDir)
487 {
488 vboxfuseNodeLockAndRetain(&pNewDir->Node);
489 *ppDir = pNewDir;
490 }
491 vboxfuseNodeRelease(&pNewDir->Node);
492 }
493 if (pParent)
494 vboxfuseNodeReleaseAndUnlock(&pParent->Node);
495 return rc;
496}
497
498
499/**
500 * Creates a flattened image
501 *
502 * @returns VBox status code.
503 * @param pszPath Where to create the flattened file in the FUSE file
504 * system.
505 * @param pszImage The image to flatten.
506 * @param ppFile Where to return the pointer to the instance.
507 * Optional.
508 */
509static int vboxfuseFlatImageCreate(const char *pszPath, const char *pszImage, PVBOXFUSEFLATIMAGE *ppFile)
510{
511 /*
512 * Check that we can create this file.
513 */
514 const char *pszName;
515 PVBOXFUSEDIR pParent;
516 int rc = vboxfuseTreeLookupParentForInsert(pszPath, &pszName, &pParent);
517 if (RT_FAILURE(rc))
518 return rc;
519 if (pParent)
520 vboxfuseNodeReleaseAndUnlock(&pParent->Node);
521
522 /*
523 * Try open the image file (without holding any locks).
524 */
525 char *pszFormat;
526 rc = VDGetFormat(NULL /* pVDIIfsDisk */, pszImage, &pszFormat);
527 if (RT_FAILURE(rc))
528 {
529 LogRel(("VDGetFormat(%s,) failed, rc=%Rrc\n", pszImage, rc));
530 return rc;
531 }
532
533 PVBOXHDD pDisk = NULL;
534 rc = VDCreate(NULL /* pVDIIfsDisk */, &pDisk);
535 if (RT_SUCCESS(rc))
536 {
537 rc = VDOpen(pDisk, pszFormat, pszImage, VD_OPEN_FLAGS_READONLY, NULL /* pVDIfsImage */);
538 if (RT_FAILURE(rc))
539 {
540 LogRel(("VDCreate(,%s,%s,,,) failed, rc=%Rrc\n", pszFormat, pszImage, rc));
541 VDClose(pDisk, false /* fDeletes */);
542 }
543 }
544 else
545 Log(("VDCreate failed, rc=%Rrc\n", rc));
546 if (RT_FAILURE(rc))
547 {
548 RTStrFree(pszFormat);
549 return rc;
550 }
551
552 /*
553 * Allocate and initialize the new directory node.
554 */
555 rc = vboxfuseTreeLookupParentForInsert(pszPath, &pszName, &pParent);
556 if (RT_SUCCESS(rc))
557 {
558 PVBOXFUSEFLATIMAGE pNewFlatImage;
559 rc = vboxfuseNodeAlloc(sizeof(*pNewFlatImage), pszName, VBOXFUSETYPE_FLAT_IMAGE, pParent, (PVBOXFUSENODE *)&pNewFlatImage);
560 if (RT_SUCCESS(rc))
561 {
562 pNewFlatImage->pDisk = pDisk;
563 pNewFlatImage->pszFormat = pszFormat;
564 pNewFlatImage->cReaders = VDIsReadOnly(pNewFlatImage->pDisk) ? INT32_MAX / 2 : 0;
565 pNewFlatImage->cWriters = 0;
566 pNewFlatImage->Node.cbPrimary = VDGetSize(pNewFlatImage->pDisk, 0 /* base */);
567
568 /*
569 * Insert it.
570 */
571 rc = vboxfuseDirInsertChild(pParent, &pNewFlatImage->Node);
572 if ( RT_SUCCESS(rc)
573 && ppFile)
574 {
575 vboxfuseNodeLockAndRetain(&pNewFlatImage->Node);
576 *ppFile = pNewFlatImage;
577 }
578 vboxfuseNodeRelease(&pNewFlatImage->Node);
579 pDisk = NULL;
580 }
581 if (pParent)
582 vboxfuseNodeReleaseAndUnlock(&pParent->Node);
583 }
584 if (RT_FAILURE(rc) && pDisk != NULL)
585 VDClose(pDisk, false /* fDelete */);
586 return rc;
587}
588
589
590//static int vboxfuseTreeMkCtrlPipe(const char *pszPath, PVBOXFUSECTRLPIPE *ppPipe)
591//{
592//}
593
594
595/**
596 * Looks up a file in the tree.
597 *
598 * Upon successfull return the returned node is both referenced and locked. The
599 * call will have to release and unlock it.
600 *
601 * @returns VBox status code
602 * @param pszPath The path to the file.
603 * @param ppNode Where to return the node.
604 */
605static int vboxfuseTreeLookup(const char *pszPath, PVBOXFUSENODE *ppNode)
606{
607 /*
608 * Root first.
609 */
610 const char *psz = pszPath;
611 if (*psz != '/')
612 return VERR_FILE_NOT_FOUND;
613
614 PVBOXFUSEDIR pDir = g_pTreeRoot;
615 vboxfuseNodeLockAndRetain(&pDir->Node);
616
617 do psz++;
618 while (*psz == '/');
619 if (!*psz)
620 {
621 /* looking for the root. */
622 *ppNode = &pDir->Node;
623 return VINF_SUCCESS;
624 }
625
626 /*
627 * Take it bit by bit from here on.
628 */
629 for (;;)
630 {
631 /*
632 * Find the length of the current directory entry and check if it must be file.
633 */
634 const char * const pszName = psz;
635 psz = strchr(psz, '/');
636 if (!psz)
637 psz = strchr(pszName, '\0');
638 size_t cchName = psz - pszName;
639
640 bool fMustBeDir = *psz == '/';
641 while (*psz == '/')
642 psz++;
643
644 /*
645 * Look it up.
646 * This is safe as the directory will hold a refernece to each node
647 * so the nodes cannot possibly be destroyed while we're searching them.
648 */
649 PVBOXFUSENODE pNode = NULL;
650 uint32_t i = pDir->cEntries;
651 PVBOXFUSENODE *paEntries = pDir->paEntries;
652 while (i-- > 0)
653 {
654 PVBOXFUSENODE pCur = paEntries[i];
655 if ( pCur->cchName == cchName
656 && !memcmp(pCur->pszName, pszName, cchName))
657 {
658 pNode = pCur;
659 vboxfuseNodeLockAndRetain(pNode);
660 break;
661 }
662 }
663 vboxfuseNodeReleaseAndUnlock(&pDir->Node);
664
665 if (!pNode)
666 return *psz ? VERR_PATH_NOT_FOUND : VERR_FILE_NOT_FOUND;
667 if ( fMustBeDir
668 && pNode->enmType != VBOXFUSETYPE_DIRECTORY)
669 {
670 vboxfuseNodeReleaseAndUnlock(pNode);
671 return VERR_NOT_A_DIRECTORY;
672 }
673
674 /*
675 * Are we done?
676 */
677 if (!*psz)
678 {
679 *ppNode = pNode;
680 return VINF_SUCCESS;
681 }
682
683 /* advance */
684 pDir = (PVBOXFUSEDIR)pNode;
685 }
686}
687
688
689/**
690 * Errno convertsion wrapper around vboxfuseTreeLookup().
691 *
692 * @returns 0 on success, negated errno on failure.
693 * @param pszPath The path to the file.
694 * @param ppNode Where to return the node.
695 */
696static int vboxfuseTreeLookupErrno(const char *pszPath, PVBOXFUSENODE *ppNode)
697{
698 int rc = vboxfuseTreeLookup(pszPath, ppNode);
699 if (RT_SUCCESS(rc))
700 return 0;
701 return -RTErrConvertToErrno(rc);
702}
703
704
705/**
706 * Looks up a parent directory in the tree.
707 *
708 * Upon successfull return the returned directory is both referenced and locked.
709 * The call will have to release and unlock it.
710 *
711 * @returns VBox status code.
712 *
713 * @param pszPath The path to the file which parent we seek.
714 * @param ppszName Where to return the pointer to the child's name within
715 * pszPath.
716 * @param ppDir Where to return the parent directory.
717 */
718static int vboxfuseTreeLookupParent(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir)
719{
720 /*
721 * Root first.
722 */
723 const char *psz = pszPath;
724 if (*psz != '/')
725 return VERR_INVALID_PARAMETER;
726 do psz++;
727 while (*psz == '/');
728 if (!*psz)
729 {
730 /* looking for the root. */
731 *ppszName = psz + 1;
732 *ppDir = NULL;
733 return VINF_SUCCESS;
734 }
735
736 /*
737 * Take it bit by bit from here on.
738 */
739 PVBOXFUSEDIR pDir = g_pTreeRoot;
740 AssertReturn(pDir, VERR_WRONG_ORDER);
741 vboxfuseNodeLockAndRetain(&pDir->Node);
742 for (;;)
743 {
744 /*
745 * Find the length of the current directory entry and check if it must be file.
746 */
747 const char * const pszName = psz;
748 psz = strchr(psz, '/');
749 if (!psz)
750 {
751 /* that's all folks.*/
752 *ppszName = pszName;
753 *ppDir = pDir;
754 return VINF_SUCCESS;
755 }
756 size_t cchName = psz - pszName;
757
758 bool fMustBeDir = *psz == '/';
759 while (*psz == '/')
760 psz++;
761
762 /* Trailing slashes are not allowed (because it's simpler without them). */
763 if (!*psz)
764 return VERR_INVALID_PARAMETER;
765
766 /*
767 * Look it up.
768 * This is safe as the directory will hold a refernece to each node
769 * so the nodes cannot possibly be destroyed while we're searching them.
770 */
771 PVBOXFUSENODE pNode = NULL;
772 uint32_t i = pDir->cEntries;
773 PVBOXFUSENODE *paEntries = pDir->paEntries;
774 while (i-- > 0)
775 {
776 PVBOXFUSENODE pCur = paEntries[i];
777 if ( pCur->cchName == cchName
778 && !memcmp(pCur->pszName, pszName, cchName))
779 {
780 pNode = pCur;
781 vboxfuseNodeLockAndRetain(pNode);
782 break;
783 }
784 }
785 vboxfuseNodeReleaseAndUnlock(&pDir->Node);
786
787 if (!pNode)
788 return VERR_FILE_NOT_FOUND;
789 if ( fMustBeDir
790 && pNode->enmType != VBOXFUSETYPE_DIRECTORY)
791 {
792 vboxfuseNodeReleaseAndUnlock(pNode);
793 return VERR_PATH_NOT_FOUND;
794 }
795
796 /* advance */
797 pDir = (PVBOXFUSEDIR)pNode;
798 }
799}
800
801
802/**
803 * Looks up a parent directory in the tree and checking that the specified child
804 * doesn't already exist.
805 *
806 * Upon successfull return the returned directory is both referenced and locked.
807 * The call will have to release and unlock it.
808 *
809 * @returns VBox status code.
810 *
811 * @param pszPath The path to the file which parent we seek.
812 * @param ppszName Where to return the pointer to the child's name within
813 * pszPath.
814 * @param ppDir Where to return the parent directory.
815 */
816static int vboxfuseTreeLookupParentForInsert(const char *pszPath, const char **ppszName, PVBOXFUSEDIR *ppDir)
817{
818 /*
819 * Lookup the parent directory using vboxfuseTreeLookupParent first.
820 */
821 const char *pszName;
822 PVBOXFUSEDIR pDir;
823 int rc = vboxfuseTreeLookupParent(pszPath, &pszName, &pDir);
824 if (RT_SUCCESS(rc))
825 {
826 /*
827 * Check that it doesn't exist already
828 */
829 if (pDir)
830 {
831 size_t const cchName = strlen(pszName);
832 uint32_t i = pDir->cEntries;
833 PVBOXFUSENODE *paEntries = pDir->paEntries;
834 while (i-- > 0)
835 {
836 PVBOXFUSENODE pCur = paEntries[i];
837 if ( pCur->cchName == cchName
838 && !memcmp(pCur->pszName, pszName, cchName))
839 {
840 vboxfuseNodeReleaseAndUnlock(&pDir->Node);
841 rc = VERR_ALREADY_EXISTS;
842 break;
843 }
844 }
845 }
846 if (RT_SUCCESS(rc))
847 {
848 *ppDir = pDir;
849 *ppszName = pszName;
850 }
851 }
852 return rc;
853}
854
855
856
857
858
859/** @copydoc fuse_operations::getattr */
860static int vboxfuseOp_getattr(const char *pszPath, struct stat *pStat)
861{
862 PVBOXFUSENODE pNode;
863 int rc = vboxfuseTreeLookupErrno(pszPath, &pNode);
864 if (!rc)
865 {
866 vboxfuseNodeFillStat(pNode, pStat);
867 vboxfuseNodeReleaseAndUnlock(pNode);
868 }
869 LogFlow(("vboxfuseOp_getattr: rc=%d \"%s\"\n", rc, pszPath));
870 return rc;
871}
872
873
874/** @copydoc fuse_operations::opendir */
875static int vboxfuseOp_opendir(const char *pszPath, struct fuse_file_info *pInfo)
876{
877 PVBOXFUSENODE pNode;
878 int rc = vboxfuseTreeLookupErrno(pszPath, &pNode);
879 if (!rc)
880 {
881 /*
882 * Check that it's a directory and that the caller should see it.
883 */
884 if (pNode->enmType != VBOXFUSETYPE_DIRECTORY)
885 rc = -ENOTDIR;
886 /** @todo access checks. */
887 else
888 {
889 /** @todo update the accessed TS? */
890
891 /*
892 * Put a reference to the node in the fuse_file_info::fh member so
893 * we don't have to parse the path in readdir.
894 */
895 pInfo->fh = (uintptr_t)pNode;
896 vboxfuseNodeUnlock(pNode);
897 }
898
899 /* cleanup */
900 if (rc)
901 vboxfuseNodeReleaseAndUnlock(pNode);
902 }
903 LogFlow(("vboxfuseOp_opendir: rc=%d \"%s\"\n", rc, pszPath));
904 return rc;
905}
906
907
908/** @copydoc fuse_operations::readdir */
909static int vboxfuseOp_readdir(const char *pszPath, void *pvBuf, fuse_fill_dir_t pfnFiller,
910 off_t offDir, struct fuse_file_info *pInfo)
911{
912 PVBOXFUSEDIR pDir = (PVBOXFUSEDIR)(uintptr_t)pInfo->fh;
913 AssertPtr(pDir);
914 Assert(pDir->Node.enmType == VBOXFUSETYPE_DIRECTORY);
915 vboxfuseNodeLock(&pDir->Node);
916 LogFlow(("vboxfuseOp_readdir: offDir=%llx \"%s\"\n", (uint64_t)offDir, pszPath));
917
918#define VBOXFUSE_FAKE_DIRENT_SIZE 512
919
920 /*
921 * First the mandatory dot and dot-dot entries.
922 */
923 struct stat st;
924 int rc = 0;
925 if (!offDir)
926 {
927 offDir += VBOXFUSE_FAKE_DIRENT_SIZE;
928 vboxfuseNodeFillStat(&pDir->Node, &st);
929 rc = pfnFiller(pvBuf, ".", &st, offDir);
930 }
931 if ( offDir == VBOXFUSE_FAKE_DIRENT_SIZE
932 && !rc)
933 {
934 offDir += VBOXFUSE_FAKE_DIRENT_SIZE;
935 rc = pfnFiller(pvBuf, "..", NULL, offDir);
936 }
937
938 /*
939 * Convert the offset to a directory index and start/continue filling the buffer.
940 * The entries only needs locking since the directory already has a reference
941 * to each of them.
942 */
943 Assert(offDir >= VBOXFUSE_FAKE_DIRENT_SIZE * 2 || rc);
944 uint32_t i = offDir / VBOXFUSE_FAKE_DIRENT_SIZE - 2;
945 while ( !rc
946 && i < pDir->cEntries)
947 {
948 PVBOXFUSENODE pNode = pDir->paEntries[i];
949 vboxfuseNodeLock(pNode);
950
951 vboxfuseNodeFillStat(pNode, &st);
952 offDir = (i + 3) * VBOXFUSE_FAKE_DIRENT_SIZE;
953 rc = pfnFiller(pvBuf, pNode->pszName, &st, offDir);
954
955 vboxfuseNodeUnlock(pNode);
956
957 /* next */
958 i++;
959 }
960
961 vboxfuseNodeUnlock(&pDir->Node);
962 LogFlow(("vboxfuseOp_readdir: returns offDir=%llx\n", (uint64_t)offDir));
963 return 0;
964}
965
966
967/** @copydoc fuse_operations::releasedir */
968static int vboxfuseOp_releasedir(const char *pszPath, struct fuse_file_info *pInfo)
969{
970 PVBOXFUSEDIR pDir = (PVBOXFUSEDIR)(uintptr_t)pInfo->fh;
971 AssertPtr(pDir);
972 Assert(pDir->Node.enmType == VBOXFUSETYPE_DIRECTORY);
973 pInfo->fh = 0;
974 vboxfuseNodeRelease(&pDir->Node);
975 LogFlow(("vboxfuseOp_releasedir: \"%s\"\n", pszPath));
976 return 0;
977}
978
979
980/** @copydoc fuse_operations::symlink */
981static int vboxfuseOp_symlink(const char *pszDst, const char *pszPath)
982{
983 /*
984 * "Interface" for mounting a image.
985 */
986 int rc = vboxfuseFlatImageCreate(pszPath, pszDst, NULL);
987 if (RT_SUCCESS(rc))
988 {
989 Log(("vboxfuseOp_symlink: \"%s\" => \"%s\" SUCCESS!\n", pszPath, pszDst));
990 return 0;
991 }
992
993 LogFlow(("vboxfuseOp_symlink: \"%s\" => \"%s\" rc=%Rrc\n", pszPath, pszDst, rc));
994 return -RTErrConvertToErrno(rc);
995}
996
997
998/** @copydoc fuse_operations::open */
999static int vboxfuseOp_open(const char *pszPath, struct fuse_file_info *pInfo)
1000{
1001 LogFlow(("vboxfuseOp_open(\"%s\", .flags=%#x)\n", pszPath, pInfo->flags));
1002
1003 /*
1004 * Validate incoming flags.
1005 */
1006#ifdef RT_OS_DARWIN
1007 if (pInfo->flags & (O_APPEND | O_NONBLOCK | O_SYMLINK | O_NOCTTY | O_SHLOCK | O_EXLOCK | O_ASYNC
1008 | O_CREAT | O_TRUNC | O_EXCL | O_EVTONLY))
1009 return -EINVAL;
1010 if ((pInfo->flags & O_ACCMODE) == O_ACCMODE)
1011 return -EINVAL;
1012#elif defined(RT_OS_LINUX)
1013 if (pInfo->flags & ( O_APPEND | O_ASYNC | O_DIRECT /* | O_LARGEFILE ? */
1014 | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK
1015 /* | O_SYNC ? */))
1016 return -EINVAL;
1017 if ((pInfo->flags & O_ACCMODE) == O_ACCMODE)
1018 return -EINVAL;
1019#else
1020# error "Port me"
1021#endif
1022
1023 PVBOXFUSENODE pNode;
1024 int rc = vboxfuseTreeLookupErrno(pszPath, &pNode);
1025 if (!rc)
1026 {
1027 /*
1028 * Check flags and stuff.
1029 */
1030 switch (pNode->enmType)
1031 {
1032 /* not expected here? */
1033 case VBOXFUSETYPE_DIRECTORY:
1034 AssertFailed();
1035 rc = -EISDIR;
1036 break;
1037
1038 case VBOXFUSETYPE_FLAT_IMAGE:
1039 {
1040 PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)pNode;
1041#ifdef O_DIRECTORY
1042 if (pInfo->flags & O_DIRECTORY)
1043 rc = -ENOTDIR;
1044#endif
1045 if ( (pInfo->flags & O_ACCMODE) == O_WRONLY
1046 || (pInfo->flags & O_ACCMODE) == O_RDWR)
1047 {
1048 if ( pFlatImage->cWriters == 0
1049 && pFlatImage->cReaders == 0)
1050 pFlatImage->cWriters++;
1051 else
1052 rc = -ETXTBSY;
1053 }
1054 else if ((pInfo->flags & O_ACCMODE) == O_RDONLY)
1055 {
1056 if (pFlatImage->cWriters == 0)
1057 {
1058 if (pFlatImage->cReaders + 1 < ( pFlatImage->cReaders < INT32_MAX / 2
1059 ? INT32_MAX / 4
1060 : INT32_MAX / 2 + INT32_MAX / 4) )
1061 pFlatImage->cReaders++;
1062 else
1063 rc = -EMLINK;
1064 }
1065 else
1066 rc = -ETXTBSY;
1067 }
1068 break;
1069 }
1070
1071 case VBOXFUSETYPE_CONTROL_PIPE:
1072 rc = -ENOTSUP;
1073 break;
1074
1075 default:
1076 rc = -EDOOFUS;
1077 break;
1078 }
1079 if (!rc)
1080 {
1081 /*
1082 * Put a reference to the node in the fuse_file_info::fh member so
1083 * we don't have to parse the path in the other file methods.
1084 */
1085 pInfo->fh = (uintptr_t)pNode;
1086 vboxfuseNodeUnlock(pNode);
1087 }
1088 else
1089 {
1090 /* cleanup */
1091 vboxfuseNodeReleaseAndUnlock(pNode);
1092 }
1093 }
1094 LogFlow(("vboxfuseOp_opendir: rc=%d \"%s\"\n", rc, pszPath));
1095 return rc;
1096}
1097
1098
1099/** @copydoc fuse_operations::release */
1100static int vboxfuseOp_release(const char *pszPath, struct fuse_file_info *pInfo)
1101{
1102 PVBOXFUSENODE pNode = (PVBOXFUSENODE)(uintptr_t)pInfo->fh;
1103 AssertPtr(pNode);
1104 pInfo->fh = 0;
1105
1106 switch (pNode->enmType)
1107 {
1108 case VBOXFUSETYPE_DIRECTORY:
1109 /* nothing to do */
1110 vboxfuseNodeRelease(pNode);
1111 break;
1112
1113 case VBOXFUSETYPE_FLAT_IMAGE:
1114 {
1115 PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)pNode;
1116 vboxfuseNodeLock(&pFlatImage->Node);
1117
1118 if ( (pInfo->flags & O_ACCMODE) == O_WRONLY
1119 || (pInfo->flags & O_ACCMODE) == O_RDWR)
1120 {
1121 pFlatImage->cWriters--;
1122 Assert(pFlatImage->cWriters >= 0);
1123 }
1124 else if ((pInfo->flags & O_ACCMODE) == O_RDONLY)
1125 {
1126 pFlatImage->cReaders--;
1127 Assert(pFlatImage->cReaders >= 0);
1128 }
1129 else
1130 AssertFailed();
1131
1132 vboxfuseNodeReleaseAndUnlock(&pFlatImage->Node);
1133 break;
1134 }
1135
1136 case VBOXFUSETYPE_CONTROL_PIPE:
1137 /* nothing to do yet */
1138 vboxfuseNodeRelease(pNode);
1139 break;
1140
1141 default:
1142 AssertMsgFailed(("%s\n", pszPath));
1143 return -EDOOFUS;
1144 }
1145
1146 LogFlow(("vboxfuseOp_release: \"%s\"\n", pszPath));
1147 return 0;
1148}
1149
1150/** The VDRead/VDWrite block granularity. */
1151#define VBOXFUSE_MIN_SIZE 512
1152/** Offset mask corresponding to VBOXFUSE_MIN_SIZE. */
1153#define VBOXFUSE_MIN_SIZE_MASK_OFF (0x1ff)
1154/** Block mask corresponding to VBOXFUSE_MIN_SIZE. */
1155#define VBOXFUSE_MIN_SIZE_MASK_BLK (~UINT64_C(0x1ff))
1156
1157/** @copydoc fuse_operations::read */
1158static int vboxfuseOp_read(const char *pszPath, char *pbBuf, size_t cbBuf,
1159 off_t offFile, struct fuse_file_info *pInfo)
1160{
1161 /* paranoia */
1162 AssertReturn((int)cbBuf >= 0, -EINVAL);
1163 AssertReturn((unsigned)cbBuf == cbBuf, -EINVAL);
1164 AssertReturn(offFile >= 0, -EINVAL);
1165 AssertReturn((off_t)(offFile + cbBuf) >= offFile, -EINVAL);
1166
1167 PVBOXFUSENODE pNode = (PVBOXFUSENODE)(uintptr_t)pInfo->fh;
1168 AssertPtr(pNode);
1169 switch (pNode->enmType)
1170 {
1171 case VBOXFUSETYPE_DIRECTORY:
1172 return -ENOTSUP;
1173
1174 case VBOXFUSETYPE_FLAT_IMAGE:
1175 {
1176 PVBOXFUSEFLATIMAGE pFlatImage = (PVBOXFUSEFLATIMAGE)(uintptr_t)pInfo->fh;
1177 LogFlow(("vboxfuseOp_read: offFile=%#llx cbBuf=%#zx pszPath=\"%s\"\n", (uint64_t)offFile, cbBuf, pszPath));
1178 vboxfuseNodeLock(&pFlatImage->Node);
1179
1180 int rc;
1181 if ((off_t)(offFile + cbBuf) < offFile)
1182 rc = -EINVAL;
1183 else if (offFile >= pFlatImage->Node.cbPrimary)
1184 rc = 0;
1185 else if (!cbBuf)
1186 rc = 0;
1187 else
1188 {
1189 /* Adjust for EOF. */
1190 if ((off_t)(offFile + cbBuf) >= pFlatImage->Node.cbPrimary)
1191 cbBuf = pFlatImage->Node.cbPrimary - offFile;
1192
1193 /*
1194 * Aligned read?
1195 */
1196 int rc2;
1197 if ( !(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)
1198 && !(cbBuf & VBOXFUSE_MIN_SIZE_MASK_OFF))
1199 rc2 = VDRead(pFlatImage->pDisk, offFile, pbBuf, cbBuf);
1200 else
1201 {
1202 /*
1203 * Unaligned read - lots of extra work.
1204 */
1205 uint8_t abBlock[VBOXFUSE_MIN_SIZE];
1206 if (((offFile + cbBuf) & VBOXFUSE_MIN_SIZE_MASK_BLK) == (offFile & VBOXFUSE_MIN_SIZE_MASK_BLK))
1207 {
1208 /* a single partial block. */
1209 rc2 = VDRead(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE);
1210 if (RT_SUCCESS(rc2))
1211 memcpy(pbBuf, &abBlock[offFile & VBOXFUSE_MIN_SIZE_MASK_OFF], cbBuf);
1212 }
1213 else
1214 {
1215 /* read unaligned head. */
1216 rc2 = VINF_SUCCESS;
1217 if (offFile & VBOXFUSE_MIN_SIZE_MASK_OFF)
1218 {
1219 rc2 = VDRead(pFlatImage->pDisk, offFile & VBOXFUSE_MIN_SIZE_MASK_BLK, abBlock, VBOXFUSE_MIN_SIZE);
1220 if (RT_SUCCESS(rc2))
1221 {
1222 size_t cbCopy = VBOXFUSE_MIN_SIZE - (offFile & VBOXFUSE_MIN_SIZE_MASK_OFF);
1223 memcpy(pbBuf, &abBlock[offFile & VBOXFUSE_MIN_SIZE_MASK_OFF], cbCopy);
1224 pbBuf += cbCopy;
1225 offFile += cbCopy;
1226 cbBuf -= cbCopy;
1227 }
1228 }
1229
1230 /* read the middle. */
1231 Assert(!(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF));
1232 if (cbBuf >= VBOXFUSE_MIN_SIZE && RT_SUCCESS(rc2))
1233 {
1234 size_t cbRead = cbBuf & VBOXFUSE_MIN_SIZE_MASK_BLK;
1235 rc2 = VDRead(pFlatImage->pDisk, offFile, pbBuf, cbRead);
1236 if (RT_SUCCESS(rc2))
1237 {
1238 pbBuf += cbRead;
1239 offFile += cbRead;
1240 cbBuf -= cbRead;
1241 }
1242 }
1243
1244 /* unaligned tail read. */
1245 Assert(cbBuf < VBOXFUSE_MIN_SIZE);
1246 Assert(!(offFile & VBOXFUSE_MIN_SIZE_MASK_OFF));
1247 if (cbBuf && RT_SUCCESS(rc2))
1248 {
1249 rc2 = VDRead(pFlatImage->pDisk, offFile, abBlock, VBOXFUSE_MIN_SIZE);
1250 if (RT_SUCCESS(rc2))
1251 memcpy(pbBuf, &abBlock[0], cbBuf);
1252 }
1253 }
1254 }
1255
1256 /* convert the return code */
1257 if (RT_SUCCESS(rc2))
1258 rc = cbBuf;
1259 else
1260 rc = -RTErrConvertToErrno(rc2);
1261 }
1262
1263 vboxfuseNodeUnlock(&pFlatImage->Node);
1264 return rc;
1265 }
1266
1267 case VBOXFUSETYPE_CONTROL_PIPE:
1268 return -ENOTSUP;
1269
1270 default:
1271 AssertMsgFailed(("%s\n", pszPath));
1272 return -EDOOFUS;
1273 }
1274}
1275
1276
1277/**
1278 * The FUSE operations.
1279 *
1280 * @remarks We'll initialize this manually since we cannot use C99 style
1281 * initialzer designations in C++ (yet).
1282 */
1283static struct fuse_operations g_vboxfuseOps;
1284
1285
1286
1287int main(int argc, char **argv)
1288{
1289 /*
1290 * Initialize the runtime and VD.
1291 */
1292 int rc = RTR3Init();
1293 if (RT_FAILURE(rc))
1294 {
1295 RTStrmPrintf(g_pStdErr, "VBoxFUSE: RTR3Init failed, rc=%Rrc\n", rc);
1296 return 1;
1297 }
1298 RTPrintf("VBoxFUSE: Hello...\n");
1299 rc = VDInit();
1300 if (RT_FAILURE(rc))
1301 {
1302 RTStrmPrintf(g_pStdErr, "VBoxFUSE: VDInit failed, rc=%Rrc\n", rc);
1303 return 1;
1304 }
1305
1306 /*
1307 * Initializes the globals and populate the file hierarchy.
1308 */
1309 rc = vboxfuseDirCreate("/", NULL);
1310 if (RT_SUCCESS(rc))
1311 rc = vboxfuseDirCreate("/FlattenedImages", NULL);
1312 if (RT_FAILURE(rc))
1313 {
1314 RTStrmPrintf(g_pStdErr, "VBoxFUSE: vboxfuseDirCreate failed, rc=%Rrc\n", rc);
1315 return 1;
1316 }
1317
1318 /*
1319 * Initialize the g_vboxfuseOps. (C++ sucks!)
1320 */
1321 memset(&g_vboxfuseOps, 0, sizeof(g_vboxfuseOps));
1322 g_vboxfuseOps.getattr = vboxfuseOp_getattr;
1323 g_vboxfuseOps.opendir = vboxfuseOp_opendir;
1324 g_vboxfuseOps.readdir = vboxfuseOp_readdir;
1325 g_vboxfuseOps.releasedir = vboxfuseOp_releasedir;
1326 g_vboxfuseOps.symlink = vboxfuseOp_symlink;
1327 g_vboxfuseOps.open = vboxfuseOp_open;
1328 g_vboxfuseOps.read = vboxfuseOp_read;
1329 g_vboxfuseOps.release = vboxfuseOp_release;
1330
1331 /*
1332 * Hand control over to libfuse.
1333 */
1334
1335#if 0
1336 /** @todo multithreaded fun. */
1337#else
1338 rc = fuse_main(argc, argv, &g_vboxfuseOps, NULL);
1339#endif
1340 RTPrintf("VBoxFUSE: fuse_main -> %d\n", rc);
1341 return rc;
1342}
1343
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