storage_backend_fs.c 40.9 KB
Newer Older
1 2 3
/*
 * storage_backend_fs.c: storage backend for FS and directory handling
 *
4
 * Copyright (C) 2007-2012 Red Hat, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17
 * Copyright (C) 2007-2008 Daniel P. Berrange
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library.  If not, see
O
Osier Yang 已提交
19
 * <http://www.gnu.org/licenses/>.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 *
 * Author: Daniel P. Berrange <berrange@redhat.com>
 */

#include <config.h>

#include <sys/statvfs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

36 37 38 39
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>

O
Osier Yang 已提交
40 41 42 43
#if HAVE_LIBBLKID
# include <blkid/blkid.h>
#endif

44
#include "virterror_internal.h"
45 46
#include "storage_backend_fs.h"
#include "storage_conf.h"
47
#include "storage_file.h"
48
#include "command.h"
49
#include "memory.h"
50
#include "xml.h"
E
Eric Blake 已提交
51
#include "virfile.h"
O
Osier Yang 已提交
52
#include "logging.h"
53

54
#define VIR_FROM_THIS VIR_FROM_STORAGE
55

56 57 58 59 60
#define VIR_STORAGE_VOL_FS_OPEN_FLAGS       (VIR_STORAGE_VOL_OPEN_DEFAULT   |\
                                             VIR_STORAGE_VOL_OPEN_DIR)
#define VIR_STORAGE_VOL_FS_REFRESH_FLAGS    (VIR_STORAGE_VOL_FS_OPEN_FLAGS  &\
                                             ~VIR_STORAGE_VOL_OPEN_ERROR)

E
Eric Blake 已提交
61
static int ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
62
virStorageBackendProbeTarget(virStorageVolTargetPtr target,
63
                             char **backingStore,
64
                             int *backingStoreFormat,
65 66 67 68
                             unsigned long long *allocation,
                             unsigned long long *capacity,
                             virStorageEncryptionPtr *encryption)
{
69 70
    int fd = -1;
    int ret = -1;
71
    virStorageFileMetadata *meta = NULL;
72

E
Eric Blake 已提交
73 74
    *backingStore = NULL;
    *backingStoreFormat = VIR_STORAGE_FILE_AUTO;
75 76 77
    if (encryption)
        *encryption = NULL;

78
    if ((ret = virStorageBackendVolOpenCheckMode(target->path,
79
                                        VIR_STORAGE_VOL_FS_REFRESH_FLAGS)) < 0)
80
        goto error; /* Take care to propagate ret, it is not always -1 */
81
    fd = ret;
82

83
    if ((ret = virStorageBackendUpdateVolTargetInfoFD(target, fd,
84 85
                                                      allocation,
                                                      capacity)) < 0) {
86
        goto error;
87 88
    }

89
    if ((target->format = virStorageFileProbeFormatFromFD(target->path, fd)) < 0) {
90 91
        ret = -1;
        goto error;
92 93
    }

94 95
    if (!(meta = virStorageFileGetMetadataFromFD(target->path, fd,
                                                 target->format))) {
96 97
        ret = -1;
        goto error;
98
    }
99

100
    VIR_FORCE_CLOSE(fd);
101

102 103 104
    if (meta->backingStore) {
        *backingStore = meta->backingStore;
        meta->backingStore = NULL;
E
Eric Blake 已提交
105 106
        if (meta->backingStoreFormat == VIR_STORAGE_FILE_AUTO &&
            meta->backingStoreIsFile) {
107
            if ((ret = virStorageFileProbeFormat(*backingStore, -1, -1)) < 0) {
108 109 110
                /* If the backing file is currently unavailable, only log an error,
                 * but continue. Returning -1 here would disable the whole storage
                 * pool, making it unavailable for even maintenance. */
111 112 113
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("cannot probe backing volume format: %s"),
                               *backingStore);
114 115 116 117
                ret = -3;
            } else {
                *backingStoreFormat = ret;
                ret = 0;
118 119
            }
        } else {
120
            *backingStoreFormat = meta->backingStoreFormat;
121
            ret = 0;
122
        }
E
Eric Blake 已提交
123
    } else {
124
        ret = 0;
125 126
    }

127 128
    if (capacity && meta->capacity)
        *capacity = meta->capacity;
129

130
    if (encryption != NULL && meta->encrypted) {
131
        if (VIR_ALLOC(*encryption) < 0) {
132
            virReportOOMError();
133
            goto cleanup;
134 135 136 137 138 139 140 141 142 143 144 145
        }

        switch (target->format) {
        case VIR_STORAGE_FILE_QCOW:
        case VIR_STORAGE_FILE_QCOW2:
            (*encryption)->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW;
            break;
        default:
            break;
        }

        /* XXX ideally we'd fill in secret UUID here
E
Eric Blake 已提交
146
         * but we cannot guarantee 'conn' is non-NULL
147 148 149 150 151
         * at this point in time :-(  So we only fill
         * in secrets when someone first queries a vol
         */
    }

152 153
    virStorageFileFreeMetadata(meta);

154
    return ret;
155

156 157 158
error:
    VIR_FORCE_CLOSE(fd);

159
cleanup:
160 161 162
    virStorageFileFreeMetadata(meta);
    return ret;

163 164 165
}

#if WITH_STORAGE_FS
166

167
# include <mntent.h>
168

169 170
struct _virNetfsDiscoverState {
    const char *host;
171
    virStoragePoolSourceList list;
172 173 174 175 176
};

typedef struct _virNetfsDiscoverState virNetfsDiscoverState;

static int
177
virStorageBackendFileSystemNetFindPoolSourcesFunc(virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
178 179 180 181 182
                                                  char **const groups,
                                                  void *data)
{
    virNetfsDiscoverState *state = data;
    const char *name, *path;
183 184
    virStoragePoolSource *src = NULL;
    int ret = -1;
185 186 187

    path = groups[0];

188
    if (!(name = strrchr(path, '/'))) {
189 190
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("invalid netfs path (no /): %s"), path);
191
        goto cleanup;
192 193 194
    }
    name += 1;
    if (*name == '\0') {
195 196
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("invalid netfs path (ends in /): %s"), path);
197
        goto cleanup;
198 199
    }

200
    if (!(src = virStoragePoolSourceListNewSource(&state->list)))
201
        goto cleanup;
202

203 204
    if (VIR_ALLOC_N(src->hosts, 1) < 0) {
        virReportOOMError();
205 206
        goto cleanup;
    }
207
    src->nhost = 1;
208 209

    if (!(src->hosts[0].name = strdup(state->host)) ||
210
        !(src->dir = strdup(path))) {
211
        virReportOOMError();
212 213
        goto cleanup;
    }
214
    src->format = VIR_STORAGE_POOL_NETFS_NFS;
215

216 217 218
    ret = 0;
cleanup:
    return ret;
219 220
}

221

222
static char *
223
virStorageBackendFileSystemNetFindPoolSources(virConnectPtr conn ATTRIBUTE_UNUSED,
224
                                              const char *srcSpec,
E
Eric Blake 已提交
225
                                              unsigned int flags)
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
{
    /*
     *  # showmount --no-headers -e HOSTNAME
     *  /tmp   *
     *  /A dir demo1.foo.bar,demo2.foo.bar
     *
     * Extract directory name (including possible interior spaces ...).
     */

    const char *regexes[] = {
        "^(/.*\\S) +\\S+$"
    };
    int vars[] = {
        1
    };
241 242 243 244 245 246 247 248
    virNetfsDiscoverState state = {
        .host = NULL,
        .list = {
            .type = VIR_STORAGE_POOL_NETFS,
            .nsources = 0,
            .sources = NULL
        }
    };
249
    virStoragePoolSourcePtr source = NULL;
250
    char *retval = NULL;
251
    unsigned int i;
252
    virCommandPtr cmd = NULL;
253

E
Eric Blake 已提交
254 255
    virCheckFlags(0, NULL);

256 257 258 259 260 261 262 263 264
    if (!srcSpec) {
        virReportError(VIR_ERR_INVALID_ARG,
                       "%s", _("hostname must be specified for netfs sources"));
        return NULL;
    }

    if (!(source = virStoragePoolDefParseSourceString(srcSpec,
                                                      VIR_STORAGE_POOL_NETFS)))
        return NULL;
265

266
    if (source->nhost != 1) {
267 268
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Expected exactly 1 host for the storage pool"));
269 270 271 272
        goto cleanup;
    }

    state.host = source->hosts[0].name;
273

274 275 276 277 278 279 280
    cmd = virCommandNewArgList(SHOWMOUNT,
                               "--no-headers",
                               "--exports",
                               source->hosts[0].name,
                               NULL);

    if (virStorageBackendRunProgRegex(NULL, cmd, 1, regexes, vars,
281
                            virStorageBackendFileSystemNetFindPoolSourcesFunc,
282
                            &state, NULL) < 0)
283 284
        goto cleanup;

285
    retval = virStoragePoolSourceListFormat(&state.list);
286
    if (retval == NULL) {
287
        virReportOOMError();
288 289 290 291
        goto cleanup;
    }

 cleanup:
292
    for (i = 0; i < state.list.nsources; i++)
293 294
        virStoragePoolSourceClear(&state.list.sources[i]);
    VIR_FREE(state.list.sources);
295

296
    virStoragePoolSourceFree(source);
297
    virCommandFree(cmd);
298 299 300 301
    return retval;
}


302 303 304 305 306 307 308 309 310
/**
 * @conn connection to report errors against
 * @pool storage pool to check for status
 *
 * Determine if a storage pool is already mounted
 *
 * Return 0 if not mounted, 1 if mounted, -1 on error
 */
static int
311
virStorageBackendFileSystemIsMounted(virStoragePoolObjPtr pool) {
312
    FILE *mtab;
313 314
    struct mntent ent;
    char buf[1024];
315 316

    if ((mtab = fopen(_PATH_MOUNTED, "r")) == NULL) {
317
        virReportSystemError(errno,
318 319
                             _("cannot read mount list '%s'"),
                             _PATH_MOUNTED);
320 321 322
        return -1;
    }

323 324
    while ((getmntent_r(mtab, &ent, buf, sizeof(buf))) != NULL) {
        if (STREQ(ent.mnt_dir, pool->def->target.path)) {
325
            VIR_FORCE_FCLOSE(mtab);
326 327 328 329
            return 1;
        }
    }

330
    VIR_FORCE_FCLOSE(mtab);
331 332 333 334 335 336 337 338 339 340 341 342 343
    return 0;
}

/**
 * @conn connection to report errors against
 * @pool storage pool to mount
 *
 * Ensure that a FS storage pool is mounted on its target location.
 * If already mounted, this is a no-op
 *
 * Returns 0 if successfully mounted, -1 on error
 */
static int
344
virStorageBackendFileSystemMount(virStoragePoolObjPtr pool) {
345
    char *src = NULL;
346 347 348
    /* 'mount -t auto' doesn't seem to auto determine nfs (or cifs),
     *  while plain 'mount' does. We have to craft separate argvs to
     *  accommodate this */
349 350 351 352 353 354
    bool netauto = (pool->def->type == VIR_STORAGE_POOL_NETFS &&
                    pool->def->source.format == VIR_STORAGE_POOL_NETFS_AUTO);
    bool glusterfs = (pool->def->type == VIR_STORAGE_POOL_NETFS &&
                      pool->def->source.format == VIR_STORAGE_POOL_NETFS_GLUSTERFS);
    virCommandPtr cmd = NULL;
    int ret = -1;
355 356

    if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
357
        if (pool->def->source.nhost != 1) {
358 359
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("Expected exactly 1 host for the storage pool"));
360 361 362
            return -1;
        }
        if (pool->def->source.hosts[0].name == NULL) {
363 364
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("missing source host"));
365 366 367
            return -1;
        }
        if (pool->def->source.dir == NULL) {
368 369
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("missing source path"));
370 371 372 373
            return -1;
        }
    } else {
        if (pool->def->source.ndevice != 1) {
374 375
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("missing source device"));
376 377 378 379
            return -1;
        }
    }

J
Jim Meyering 已提交
380
    /* Short-circuit if already mounted */
381
    if ((ret = virStorageBackendFileSystemIsMounted(pool)) != 0) {
382 383 384
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("Target '%s' is already mounted"),
                       pool->def->target.path);
385
        return -1;
386 387 388
    }

    if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
389
        if (virAsprintf(&src, "%s:%s",
390
                        pool->def->source.hosts[0].name,
391
                        pool->def->source.dir) == -1) {
392
            virReportOOMError();
393 394
            return -1;
        }
395

396
    } else {
397
        if ((src = strdup(pool->def->source.devices[0].path)) == NULL) {
398
            virReportOOMError();
399 400
            return -1;
        }
401 402
    }

403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
    if (netauto)
        cmd = virCommandNewArgList(MOUNT,
                                   src,
                                   pool->def->target.path,
                                   NULL);
    else if (glusterfs)
        cmd = virCommandNewArgList( MOUNT,
                                    "-t",
                                    (pool->def->type == VIR_STORAGE_POOL_FS ?
                                     virStoragePoolFormatFileSystemTypeToString(pool->def->source.format) :
                                     virStoragePoolFormatFileSystemNetTypeToString(pool->def->source.format)),
                                    src,
                                    "-o",
                                    "direct-io-mode=1",
                                    pool->def->target.path,
                                    NULL);
    else
        cmd = virCommandNewArgList(MOUNT,
                                   "-t",
                                   (pool->def->type == VIR_STORAGE_POOL_FS ?
                                    virStoragePoolFormatFileSystemTypeToString(pool->def->source.format) :
                                    virStoragePoolFormatFileSystemNetTypeToString(pool->def->source.format)),
                                   src,
                                   pool->def->target.path,
                                   NULL);

    if (virCommandRun(cmd, NULL) < 0)
        goto cleanup;

    ret = 0;
cleanup:
    virCommandFree(cmd);
435
    VIR_FREE(src);
436
    return ret;
437 438 439 440 441 442 443 444 445 446 447 448
}

/**
 * @conn connection to report errors against
 * @pool storage pool to unmount
 *
 * Ensure that a FS storage pool is not mounted on its target location.
 * If already unmounted, this is a no-op
 *
 * Returns 0 if successfully unmounted, -1 on error
 */
static int
449
virStorageBackendFileSystemUnmount(virStoragePoolObjPtr pool) {
450 451
    virCommandPtr cmd = NULL;
    int ret = -1;
452 453

    if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
454
        if (pool->def->source.nhost != 1) {
455 456
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("Expected exactly 1 host for the storage pool"));
457 458 459
            return -1;
        }
        if (pool->def->source.hosts[0].name == NULL) {
460 461
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("missing source host"));
462 463 464
            return -1;
        }
        if (pool->def->source.dir == NULL) {
465 466
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("missing source dir"));
467 468 469 470
            return -1;
        }
    } else {
        if (pool->def->source.ndevice != 1) {
471 472
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           "%s", _("missing source device"));
473 474 475 476 477
            return -1;
        }
    }

    /* Short-circuit if already unmounted */
478
    if ((ret = virStorageBackendFileSystemIsMounted(pool)) != 1) {
479 480 481 482 483 484
        if (ret < 0)
            return -1;
        else
            return 0;
    }

485 486 487
    cmd = virCommandNewArgList(UMOUNT,
                               pool->def->target.path,
                               NULL);
488

489 490 491 492 493 494 495
    if (virCommandRun(cmd, NULL) < 0)
        goto cleanup;

    ret = 0;
cleanup:
    virCommandFree(cmd);
    return ret;
496 497 498 499
}
#endif /* WITH_STORAGE_FS */


500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
static int
virStorageBackendFileSystemCheck(virConnectPtr conn ATTRIBUTE_UNUSED,
                                 virStoragePoolObjPtr pool,
                                 bool *isActive)
{
    *isActive = false;
    if (pool->def->type == VIR_STORAGE_POOL_DIR) {
        if (access(pool->def->target.path, F_OK) == 0)
            *isActive = true;
#if WITH_STORAGE_FS
    } else {
        int ret;
        if ((ret = virStorageBackendFileSystemIsMounted(pool)) != 0) {
            if (ret < 0)
                return -1;
            *isActive = true;
        }
#endif /* WITH_STORAGE_FS */
    }

    return 0;
}

#if WITH_STORAGE_FS
524 525 526 527 528 529 530 531 532 533 534
/**
 * @conn connection to report errors against
 * @pool storage pool to start
 *
 * Starts a directory or FS based storage pool.
 *
 *  - If it is a FS based pool, mounts the unlying source device on the pool
 *
 * Returns 0 on success, -1 on error
 */
static int
535
virStorageBackendFileSystemStart(virConnectPtr conn ATTRIBUTE_UNUSED,
536 537 538
                                 virStoragePoolObjPtr pool)
{
    if (pool->def->type != VIR_STORAGE_POOL_DIR &&
539
        virStorageBackendFileSystemMount(pool) < 0)
540 541 542 543 544 545
        return -1;

    return 0;
}
#endif /* WITH_STORAGE_FS */

O
Osier Yang 已提交
546 547 548 549 550 551 552 553 554 555 556 557 558 559
#if HAVE_LIBBLKID
static virStoragePoolProbeResult
virStorageBackendFileSystemProbe(const char *device,
                                 const char *format) {

    virStoragePoolProbeResult ret = FILESYSTEM_PROBE_ERROR;
    blkid_probe probe = NULL;
    const char *fstype = NULL;
    char *names[2], *libblkid_format = NULL;

    VIR_DEBUG("Probing for existing filesystem of type %s on device %s",
              format, device);

    if (blkid_known_fstype(format) == 0) {
560 561 562 563
        virReportError(VIR_ERR_STORAGE_PROBE_FAILED,
                       _("Not capable of probing for "
                         "filesystem of type %s"),
                       format);
O
Osier Yang 已提交
564 565 566 567 568
        goto error;
    }

    probe = blkid_new_probe_from_filename(device);
    if (probe == NULL) {
569 570 571 572
        virReportError(VIR_ERR_STORAGE_PROBE_FAILED,
                       _("Failed to create filesystem probe "
                         "for device %s"),
                       device);
O
Osier Yang 已提交
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
        goto error;
    }

    if ((libblkid_format = strdup(format)) == NULL) {
        virReportOOMError();
        goto error;
    }

    names[0] = libblkid_format;
    names[1] = NULL;

    blkid_probe_filter_superblocks_type(probe,
                                        BLKID_FLTR_ONLYIN,
                                        names);

    if (blkid_do_probe(probe) != 0) {
        VIR_INFO("No filesystem of type '%s' found on device '%s'",
                 format, device);
        ret = FILESYSTEM_PROBE_NOT_FOUND;
    } else if (blkid_probe_lookup_value(probe, "TYPE", &fstype, NULL) == 0) {
593 594 595 596
        virReportError(VIR_ERR_STORAGE_POOL_BUILT,
                       _("Existing filesystem of type '%s' found on "
                         "device '%s'"),
                       fstype, device);
O
Osier Yang 已提交
597 598 599 600
        ret = FILESYSTEM_PROBE_FOUND;
    }

    if (blkid_do_probe(probe) != 1) {
601
        virReportError(VIR_ERR_STORAGE_PROBE_FAILED, "%s",
602 603
                       _("Found additional probes to run, "
                         "filesystem probing may be incorrect"));
O
Osier Yang 已提交
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
        ret = FILESYSTEM_PROBE_ERROR;
    }

error:
    VIR_FREE(libblkid_format);

    if (probe != NULL) {
        blkid_free_probe(probe);
    }

    return ret;
}

#else /* #if HAVE_LIBBLKID */

static virStoragePoolProbeResult
virStorageBackendFileSystemProbe(const char *device ATTRIBUTE_UNUSED,
                                 const char *format ATTRIBUTE_UNUSED)
{
623
    virReportError(VIR_ERR_OPERATION_INVALID, "%s",
624 625
                   _("probing for filesystems is unsupported "
                     "by this build"));
O
Osier Yang 已提交
626 627 628 629 630 631

    return FILESYSTEM_PROBE_ERROR;
}

#endif /* #if HAVE_LIBBLKID */

632 633
/* some platforms don't support mkfs */
#ifdef MKFS
O
Osier Yang 已提交
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
static int
virStorageBackendExecuteMKFS(const char *device,
                             const char *format)
{
    int ret = 0;
    virCommandPtr cmd = NULL;

    cmd = virCommandNewArgList(MKFS,
                               "-t",
                               format,
                               device,
                               NULL);

    if (virCommandRun(cmd, NULL) < 0) {
        virReportSystemError(errno,
                             _("Failed to make filesystem of "
                               "type '%s' on device '%s'"),
                             format, device);
        ret = -1;
    }
    return ret;
}
656 657 658 659 660
#else /* #ifdef MKFS */
static int
virStorageBackendExecuteMKFS(const char *device ATTRIBUTE_UNUSED,
                             const char *format ATTRIBUTE_UNUSED)
{
661 662 663 664 665
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   _("mkfs is not supported on this platform: "
                     "Failed to make filesystem of "
                     "type '%s' on device '%s'"),
                   format, device);
666 667 668
    return -1;
}
#endif /* #ifdef MKFS */
O
Osier Yang 已提交
669 670 671 672 673 674 675 676 677 678

static int
virStorageBackendMakeFileSystem(virStoragePoolObjPtr pool,
                                unsigned int flags)
{
    const char *device = NULL, *format = NULL;
    bool ok_to_mkfs = false;
    int ret = -1;

    if (pool->def->source.devices == NULL) {
679 680 681
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("No source device specified when formatting pool '%s'"),
                       pool->def->name);
O
Osier Yang 已提交
682 683 684 685 686 687 688 689
        goto error;
    }

    device = pool->def->source.devices[0].path;
    format = virStoragePoolFormatFileSystemTypeToString(pool->def->source.format);
    VIR_DEBUG("source device: '%s' format: '%s'", device, format);

    if (!virFileExists(device)) {
690 691 692
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("Source device does not exist when formatting pool '%s'"),
                       pool->def->name);
O
Osier Yang 已提交
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
        goto error;
    }

    if (flags & VIR_STORAGE_POOL_BUILD_OVERWRITE) {
        ok_to_mkfs = true;
    } else if (flags & VIR_STORAGE_POOL_BUILD_NO_OVERWRITE &&
               virStorageBackendFileSystemProbe(device, format) ==
               FILESYSTEM_PROBE_NOT_FOUND) {
        ok_to_mkfs = true;
    }

    if (ok_to_mkfs) {
        ret = virStorageBackendExecuteMKFS(device, format);
    }

error:
    return ret;
}

712 713 714 715

/**
 * @conn connection to report errors against
 * @pool storage pool to build
E
Eric Blake 已提交
716
 * @flags controls the pool formatting behaviour
717 718 719
 *
 * Build a directory or FS based storage pool.
 *
O
Osier Yang 已提交
720 721 722 723 724 725 726
 * If no flag is set, it only makes the directory; If
 * VIR_STORAGE_POOL_BUILD_NO_OVERWRITE set, it probes to determine if
 * filesystem already exists on the target device, renurning an error
 * if exists, or using mkfs to format the target device if not; If
 * VIR_STORAGE_POOL_BUILD_OVERWRITE is set, mkfs is always executed,
 * any existed data on the target device is overwritten unconditionally.
 *
727 728 729 730 731
 *  - If it is a FS based pool, mounts the unlying source device on the pool
 *
 * Returns 0 on success, -1 on error
 */
static int
732
virStorageBackendFileSystemBuild(virConnectPtr conn ATTRIBUTE_UNUSED,
733
                                 virStoragePoolObjPtr pool,
E
Eric Blake 已提交
734
                                 unsigned int flags)
735
{
736
    int err, ret = -1;
O
Osier Yang 已提交
737 738
    char *parent = NULL;
    char *p = NULL;
739

O
Osier Yang 已提交
740 741 742 743 744 745
    virCheckFlags(VIR_STORAGE_POOL_BUILD_OVERWRITE |
                  VIR_STORAGE_POOL_BUILD_NO_OVERWRITE, ret);

    if (flags == (VIR_STORAGE_POOL_BUILD_OVERWRITE |
                  VIR_STORAGE_POOL_BUILD_NO_OVERWRITE)) {

746
        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
747 748
                       _("Overwrite and no overwrite flags"
                         " are mutually exclusive"));
O
Osier Yang 已提交
749 750
        goto error;
    }
E
Eric Blake 已提交
751

752
    if ((parent = strdup(pool->def->target.path)) == NULL) {
753
        virReportOOMError();
754 755 756
        goto error;
    }
    if (!(p = strrchr(parent, '/'))) {
757 758 759
        virReportError(VIR_ERR_INVALID_ARG,
                       _("path '%s' is not absolute"),
                       pool->def->target.path);
760
        goto error;
761 762
    }

763 764 765 766
    if (p != parent) {
        /* assure all directories in the path prior to the final dir
         * exist, with default uid/gid/mode. */
        *p = '\0';
767 768
        if (virFileMakePath(parent) < 0) {
            virReportSystemError(errno, _("cannot create path '%s'"),
769 770 771 772 773 774 775 776
                                 parent);
            goto error;
        }
    }

    /* Now create the final dir in the path with the uid/gid/mode
     * requested in the config. If the dir already exists, just set
     * the perms. */
777 778
    if ((err = virDirCreate(pool->def->target.path,
                            pool->def->target.perms.mode,
779 780
                            pool->def->target.perms.uid,
                            pool->def->target.perms.gid,
781 782 783 784 785 786 787
                            VIR_DIR_CREATE_FORCE_PERMS |
                            VIR_DIR_CREATE_ALLOW_EXIST |
                            (pool->def->type == VIR_STORAGE_POOL_NETFS
                            ? VIR_DIR_CREATE_AS_UID : 0)) < 0)) {
        virReportSystemError(-err, _("cannot create path '%s'"),
                             pool->def->target.path);
        goto error;
788
    }
O
Osier Yang 已提交
789

790 791
    /* Reflect the actual uid and gid to the config. */
    if (pool->def->target.perms.uid == (uid_t) -1)
792
        pool->def->target.perms.uid = getuid();
793
    if (pool->def->target.perms.gid == (gid_t) -1)
794
        pool->def->target.perms.gid = getgid();
795

O
Osier Yang 已提交
796 797 798 799 800 801
    if (flags != 0) {
        ret = virStorageBackendMakeFileSystem(pool, flags);
    } else {
        ret = 0;
    }

802 803 804
error:
    VIR_FREE(parent);
    return ret;
805 806 807 808 809 810 811 812
}


/**
 * Iterate over the pool's directory and enumerate all disk images
 * within it. This is non-recursive.
 */
static int
813
virStorageBackendFileSystemRefresh(virConnectPtr conn ATTRIBUTE_UNUSED,
814 815 816 817 818
                                   virStoragePoolObjPtr pool)
{
    DIR *dir;
    struct dirent *ent;
    struct statvfs sb;
819
    virStorageVolDefPtr vol = NULL;
820 821

    if (!(dir = opendir(pool->def->target.path))) {
822
        virReportSystemError(errno,
823 824
                             _("cannot open path '%s'"),
                             pool->def->target.path);
825 826 827 828 829
        goto cleanup;
    }

    while ((ent = readdir(dir)) != NULL) {
        int ret;
830
        char *backingStore;
831
        int backingStoreFormat;
832

833 834
        if (VIR_ALLOC(vol) < 0)
            goto no_memory;
835

836 837
        if ((vol->name = strdup(ent->d_name)) == NULL)
            goto no_memory;
838

839
        vol->type = VIR_STORAGE_VOL_FILE;
840
        vol->target.format = VIR_STORAGE_FILE_RAW; /* Real value is filled in during probe */
841 842 843
        if (virAsprintf(&vol->target.path, "%s/%s",
                        pool->def->target.path,
                        vol->name) == -1)
844 845 846 847
            goto no_memory;

        if ((vol->key = strdup(vol->target.path)) == NULL)
            goto no_memory;
848

849
        if ((ret = virStorageBackendProbeTarget(&vol->target,
850
                                                &backingStore,
851
                                                &backingStoreFormat,
852
                                                &vol->allocation,
853
                                                &vol->capacity,
854
                                                &vol->target.encryption)) < 0) {
855
            if (ret == -2) {
856
                /* Silently ignore non-regular files,
857
                 * eg '.' '..', 'lost+found', dangling symbolic link */
858 859
                virStorageVolDefFree(vol);
                vol = NULL;
860
                continue;
861 862 863 864 865 866 867 868 869
            } else if (ret == -3) {
                /* The backing file is currently unavailable, its format is not
                 * explicitly specified, the probe to auto detect the format
                 * failed: continue with faked RAW format, since AUTO will
                 * break virStorageVolTargetDefFormat() generating the line
                 * <format type='...'/>. */
                backingStoreFormat = VIR_STORAGE_FILE_RAW;
            } else
                goto cleanup;
870 871
        }

872 873 874 875
        /* directory based volume */
        if (vol->target.format == VIR_STORAGE_FILE_DIR)
            vol->type = VIR_STORAGE_VOL_DIR;

876
        if (backingStore != NULL) {
877 878 879 880
            vol->backingStore.path = backingStore;
            vol->backingStore.format = backingStoreFormat;

            if (virStorageBackendUpdateVolTargetInfo(&vol->backingStore,
881 882
                                        NULL, NULL,
                                        VIR_STORAGE_VOL_OPEN_DEFAULT) < 0) {
883 884
                /* The backing file is currently unavailable, the capacity,
                 * allocation, owner, group and mode are unknown. Just log the
E
Eric Blake 已提交
885
                 * error and continue.
886 887 888
                 * Unfortunately virStorageBackendProbeTarget() might already
                 * have logged a similar message for the same problem, but only
                 * if AUTO format detection was used. */
889 890 891
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("cannot probe backing volume info: %s"),
                               vol->backingStore.path);
892 893 894 895
            }
        }


896 897 898 899 900
        if (VIR_REALLOC_N(pool->volumes.objs,
                          pool->volumes.count+1) < 0)
            goto no_memory;
        pool->volumes.objs[pool->volumes.count++] = vol;
        vol = NULL;
901 902 903 904 905
    }
    closedir(dir);


    if (statvfs(pool->def->target.path, &sb) < 0) {
906
        virReportSystemError(errno,
907 908
                             _("cannot statvfs path '%s'"),
                             pool->def->target.path);
909 910 911 912 913 914 915 916 917 918
        return -1;
    }
    pool->def->capacity = ((unsigned long long)sb.f_frsize *
                           (unsigned long long)sb.f_blocks);
    pool->def->available = ((unsigned long long)sb.f_bfree *
                            (unsigned long long)sb.f_bsize);
    pool->def->allocation = pool->def->capacity - pool->def->available;

    return 0;

919
no_memory:
920
    virReportOOMError();
921 922
    /* fallthrough */

923
 cleanup:
924 925
    if (dir)
        closedir(dir);
926
    virStorageVolDefFree(vol);
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942
    virStoragePoolObjClearVols(pool);
    return -1;
}


/**
 * @conn connection to report errors against
 * @pool storage pool to start
 *
 * Stops a directory or FS based storage pool.
 *
 *  - If it is a FS based pool, unmounts the unlying source device on the pool
 *  - Releases all cached data about volumes
 */
#if WITH_STORAGE_FS
static int
943
virStorageBackendFileSystemStop(virConnectPtr conn ATTRIBUTE_UNUSED,
944 945 946
                                virStoragePoolObjPtr pool)
{
    if (pool->def->type != VIR_STORAGE_POOL_DIR &&
947
        virStorageBackendFileSystemUnmount(pool) < 0)
948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965
        return -1;

    return 0;
}
#endif /* WITH_STORAGE_FS */


/**
 * @conn connection to report errors against
 * @pool storage pool to build
 *
 * Build a directory or FS based storage pool.
 *
 *  - If it is a FS based pool, mounts the unlying source device on the pool
 *
 * Returns 0 on success, -1 on error
 */
static int
966
virStorageBackendFileSystemDelete(virConnectPtr conn ATTRIBUTE_UNUSED,
967
                                  virStoragePoolObjPtr pool,
E
Eric Blake 已提交
968
                                  unsigned int flags)
969
{
E
Eric Blake 已提交
970 971
    virCheckFlags(0, -1);

972 973
    /* XXX delete all vols first ? */

974
    if (rmdir(pool->def->target.path) < 0) {
975
        virReportSystemError(errno,
976
                             _("failed to remove pool '%s'"),
977
                             pool->def->target.path);
978 979 980 981 982 983 984 985
        return -1;
    }

    return 0;
}


/**
986 987 988 989
 * Set up a volume definition to be added to a pool's volume list, but
 * don't do any file creation or allocation. By separating the two processes,
 * we allow allocation progress reporting (by polling the volume's 'info'
 * function), and can drop the parent pool lock during the (slow) allocation.
990 991
 */
static int
992
virStorageBackendFileSystemVolCreate(virConnectPtr conn ATTRIBUTE_UNUSED,
993 994 995 996
                                     virStoragePoolObjPtr pool,
                                     virStorageVolDefPtr vol)
{

997 998
    vol->type = VIR_STORAGE_VOL_FILE;

R
Ryota Ozaki 已提交
999
    VIR_FREE(vol->target.path);
1000 1001 1002
    if (virAsprintf(&vol->target.path, "%s/%s",
                    pool->def->target.path,
                    vol->name) == -1) {
1003
        virReportOOMError();
1004 1005
        return -1;
    }
1006

R
Ryota Ozaki 已提交
1007
    VIR_FREE(vol->key);
1008 1009
    vol->key = strdup(vol->target.path);
    if (vol->key == NULL) {
1010
        virReportOOMError();
1011 1012 1013
        return -1;
    }

1014 1015 1016
    return 0;
}

1017
static int createFileDir(virConnectPtr conn ATTRIBUTE_UNUSED,
1018
                         virStoragePoolObjPtr pool,
1019
                         virStorageVolDefPtr vol,
1020
                         virStorageVolDefPtr inputvol,
E
Eric Blake 已提交
1021 1022
                         unsigned int flags)
{
1023 1024
    int err;

E
Eric Blake 已提交
1025 1026
    virCheckFlags(0, -1);

1027
    if (inputvol) {
1028 1029 1030
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s",
                       _("cannot copy from volume to a directory volume"));
1031 1032 1033
        return -1;
    }

1034
    if ((err = virDirCreate(vol->target.path, vol->target.perms.mode,
1035 1036
                            vol->target.perms.uid,
                            vol->target.perms.gid,
1037
                            VIR_DIR_CREATE_FORCE_PERMS |
1038
                            (pool->def->type == VIR_STORAGE_POOL_NETFS
1039 1040
                             ? VIR_DIR_CREATE_AS_UID : 0))) < 0) {
        virReportSystemError(-err, _("cannot create path '%s'"),
1041 1042 1043
                             vol->target.path);
        return -1;
    }
1044

1045 1046
    return 0;
}
1047

1048
static int
1049
_virStorageBackendFileSystemVolBuild(virConnectPtr conn,
1050
                                     virStoragePoolObjPtr pool,
1051 1052
                                     virStorageVolDefPtr vol,
                                     virStorageVolDefPtr inputvol)
1053
{
1054
    virStorageBackendBuildVolFrom create_func;
1055
    int tool_type;
1056

1057
    if (inputvol) {
1058
        if (vol->target.encryption != NULL) {
1059 1060 1061 1062
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           "%s", _("storage pool does not support "
                                   "building encrypted volumes from "
                                   "other volumes"));
1063 1064
            return -1;
        }
1065
        create_func = virStorageBackendGetBuildVolFromFunction(vol,
1066
                                                               inputvol);
1067 1068
        if (!create_func)
            return -1;
1069
    } else if (vol->target.format == VIR_STORAGE_FILE_RAW) {
1070
        create_func = virStorageBackendCreateRaw;
1071
    } else if (vol->target.format == VIR_STORAGE_FILE_DIR) {
1072
        create_func = createFileDir;
1073
    } else if ((tool_type = virStorageBackendFindFSImageTool(NULL)) != -1) {
1074
        create_func = virStorageBackendFSImageToolTypeToFunc(tool_type);
1075 1076

        if (!create_func)
1077
            return -1;
1078
    } else {
1079 1080 1081
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("creation of non-raw images "
                               "is not supported without qemu-img"));
1082 1083 1084
        return -1;
    }

1085
    if (create_func(conn, pool, vol, inputvol, 0) < 0)
1086
        return -1;
1087 1088 1089
    return 0;
}

1090 1091 1092 1093 1094 1095 1096
/**
 * Allocate a new file as a volume. This is either done directly
 * for raw/sparse files, or by calling qemu-img/qcow-create for
 * special kinds of files
 */
static int
virStorageBackendFileSystemVolBuild(virConnectPtr conn,
1097
                                    virStoragePoolObjPtr pool,
1098
                                    virStorageVolDefPtr vol) {
1099
    return _virStorageBackendFileSystemVolBuild(conn, pool, vol, NULL);
1100 1101 1102 1103 1104 1105 1106
}

/*
 * Create a storage vol using 'inputvol' as input
 */
static int
virStorageBackendFileSystemVolBuildFrom(virConnectPtr conn,
1107
                                        virStoragePoolObjPtr pool,
1108 1109
                                        virStorageVolDefPtr vol,
                                        virStorageVolDefPtr inputvol,
E
Eric Blake 已提交
1110 1111 1112 1113
                                        unsigned int flags)
{
    virCheckFlags(0, -1);

1114
    return _virStorageBackendFileSystemVolBuild(conn, pool, vol, inputvol);
1115
}
1116 1117

/**
1118
 * Remove a volume - no support for BLOCK and NETWORK yet
1119 1120
 */
static int
1121
virStorageBackendFileSystemVolDelete(virConnectPtr conn ATTRIBUTE_UNUSED,
1122 1123
                                     virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
                                     virStorageVolDefPtr vol,
E
Eric Blake 已提交
1124
                                     unsigned int flags)
1125
{
E
Eric Blake 已提交
1126 1127
    virCheckFlags(0, -1);

1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
    switch (vol->type) {
    case VIR_STORAGE_VOL_FILE:
        if (unlink(vol->target.path) < 0) {
            /* Silently ignore failures where the vol has already gone away */
            if (errno != ENOENT) {
                virReportSystemError(errno,
                                     _("cannot unlink file '%s'"),
                                     vol->target.path);
                return -1;
            }
        }
        break;
    case VIR_STORAGE_VOL_DIR:
        if (rmdir(vol->target.path) < 0) {
1142
            virReportSystemError(errno,
1143
                                 _("cannot remove directory '%s'"),
1144
                                 vol->target.path);
1145 1146
            return -1;
        }
1147 1148 1149 1150
        break;
    case VIR_STORAGE_VOL_BLOCK:
    case VIR_STORAGE_VOL_NETWORK:
    default:
1151 1152 1153
        virReportError(VIR_ERR_NO_SUPPORT,
                       _("removing block or network volumes is not supported: %s"),
                       vol->target.path);
1154
        return -1;
1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167
    }
    return 0;
}


/**
 * Update info about a volume's capacity/allocation
 */
static int
virStorageBackendFileSystemVolRefresh(virConnectPtr conn,
                                      virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
                                      virStorageVolDefPtr vol)
{
1168 1169
    int ret;

1170
    /* Refresh allocation / permissions info in case its changed */
1171 1172
    ret = virStorageBackendUpdateVolInfoFlags(vol, 0,
                                              VIR_STORAGE_VOL_FS_OPEN_FLAGS);
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
    if (ret < 0)
        return ret;

    /* Load any secrets if posible */
    if (vol->target.encryption &&
        vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_QCOW &&
        vol->target.encryption->nsecrets == 0) {
        virSecretPtr sec;
        virStorageEncryptionSecretPtr encsec = NULL;

        sec = virSecretLookupByUsage(conn,
                                     VIR_SECRET_USAGE_TYPE_VOLUME,
                                     vol->target.path);
        if (sec) {
            if (VIR_ALLOC_N(vol->target.encryption->secrets, 1) < 0 ||
                VIR_ALLOC(encsec) < 0) {
                VIR_FREE(vol->target.encryption->secrets);
1190
                virReportOOMError();
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204
                virSecretFree(sec);
                return -1;
            }

            vol->target.encryption->nsecrets = 1;
            vol->target.encryption->secrets[0] = encsec;

            encsec->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE;
            virSecretGetUUID(sec, encsec->uuid);
            virSecretFree(sec);
        }
    }

    return 0;
1205 1206
}

1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
static int
virStorageBackendFilesystemResizeQemuImg(const char *path,
                                         unsigned long long capacity)
{
    int ret = -1;
    char *img_tool;
    virCommandPtr cmd = NULL;

    /* KVM is usually ahead of qemu on features, so try that first */
    img_tool = virFindFileInPath("kvm-img");
    if (!img_tool)
        img_tool = virFindFileInPath("qemu-img");

    if (!img_tool) {
1221 1222
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("unable to find kvm-img or qemu-img"));
1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
        return -1;
    }

    cmd = virCommandNew(img_tool);
    virCommandAddArgList(cmd, "resize", path, NULL);
    virCommandAddArgFormat(cmd, "%llu", capacity);

    ret = virCommandRun(cmd, NULL);

    VIR_FREE(img_tool);
    virCommandFree(cmd);

    return ret;
}

/**
 * Resize a volume
 */
static int
virStorageBackendFileSystemVolResize(virConnectPtr conn ATTRIBUTE_UNUSED,
                                     virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
                                     virStorageVolDefPtr vol,
                                     unsigned long long capacity,
                                     unsigned int flags)
{
    virCheckFlags(0, -1);

    if (vol->target.format == VIR_STORAGE_FILE_RAW)
        return virStorageFileResize(vol->target.path, capacity);
    else
        return virStorageBackendFilesystemResizeQemuImg(vol->target.path,
                                                        capacity);
}

1257 1258 1259 1260
virStorageBackend virStorageBackendDirectory = {
    .type = VIR_STORAGE_POOL_DIR,

    .buildPool = virStorageBackendFileSystemBuild,
1261
    .checkPool = virStorageBackendFileSystemCheck,
1262 1263
    .refreshPool = virStorageBackendFileSystemRefresh,
    .deletePool = virStorageBackendFileSystemDelete,
1264
    .buildVol = virStorageBackendFileSystemVolBuild,
1265
    .buildVolFrom = virStorageBackendFileSystemVolBuildFrom,
1266 1267 1268
    .createVol = virStorageBackendFileSystemVolCreate,
    .refreshVol = virStorageBackendFileSystemVolRefresh,
    .deleteVol = virStorageBackendFileSystemVolDelete,
1269
    .resizeVol = virStorageBackendFileSystemVolResize,
1270 1271 1272 1273 1274 1275 1276
};

#if WITH_STORAGE_FS
virStorageBackend virStorageBackendFileSystem = {
    .type = VIR_STORAGE_POOL_FS,

    .buildPool = virStorageBackendFileSystemBuild,
1277
    .checkPool = virStorageBackendFileSystemCheck,
1278 1279 1280 1281
    .startPool = virStorageBackendFileSystemStart,
    .refreshPool = virStorageBackendFileSystemRefresh,
    .stopPool = virStorageBackendFileSystemStop,
    .deletePool = virStorageBackendFileSystemDelete,
1282
    .buildVol = virStorageBackendFileSystemVolBuild,
1283
    .buildVolFrom = virStorageBackendFileSystemVolBuildFrom,
1284 1285 1286
    .createVol = virStorageBackendFileSystemVolCreate,
    .refreshVol = virStorageBackendFileSystemVolRefresh,
    .deleteVol = virStorageBackendFileSystemVolDelete,
1287
    .resizeVol = virStorageBackendFileSystemVolResize,
1288 1289 1290 1291 1292
};
virStorageBackend virStorageBackendNetFileSystem = {
    .type = VIR_STORAGE_POOL_NETFS,

    .buildPool = virStorageBackendFileSystemBuild,
1293
    .checkPool = virStorageBackendFileSystemCheck,
1294
    .startPool = virStorageBackendFileSystemStart,
1295
    .findPoolSources = virStorageBackendFileSystemNetFindPoolSources,
1296 1297 1298
    .refreshPool = virStorageBackendFileSystemRefresh,
    .stopPool = virStorageBackendFileSystemStop,
    .deletePool = virStorageBackendFileSystemDelete,
1299
    .buildVol = virStorageBackendFileSystemVolBuild,
1300
    .buildVolFrom = virStorageBackendFileSystemVolBuildFrom,
1301 1302 1303
    .createVol = virStorageBackendFileSystemVolCreate,
    .refreshVol = virStorageBackendFileSystemVolRefresh,
    .deleteVol = virStorageBackendFileSystemVolDelete,
1304
    .resizeVol = virStorageBackendFileSystemVolResize,
1305 1306
};
#endif /* WITH_STORAGE_FS */