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 71 72 73 74 75 76
    int fd = -1;
    int ret = -1;
    virStorageFileMetadata *meta;

    if (VIR_ALLOC(meta) < 0) {
        virReportOOMError();
        return ret;
    }
77

E
Eric Blake 已提交
78 79
    *backingStore = NULL;
    *backingStoreFormat = VIR_STORAGE_FILE_AUTO;
80 81 82
    if (encryption)
        *encryption = NULL;

83
    if ((ret = virStorageBackendVolOpenCheckMode(target->path,
84
                                        VIR_STORAGE_VOL_FS_REFRESH_FLAGS)) < 0)
85
        goto error; /* Take care to propagate ret, it is not always -1 */
86
    fd = ret;
87

88
    if ((ret = virStorageBackendUpdateVolTargetInfoFD(target, fd,
89 90
                                                      allocation,
                                                      capacity)) < 0) {
91
        goto error;
92 93
    }

94
    if ((target->format = virStorageFileProbeFormatFromFD(target->path, fd)) < 0) {
95 96
        ret = -1;
        goto error;
97 98
    }

99 100
    if (virStorageFileGetMetadataFromFD(target->path, fd,
                                        target->format,
101 102 103
                                        meta) < 0) {
        ret = -1;
        goto error;
104
    }
105

106
    VIR_FORCE_CLOSE(fd);
107

108 109 110 111
    if (meta->backingStore) {
        *backingStore = meta->backingStore;
        meta->backingStore = NULL;
        if (meta->backingStoreFormat == VIR_STORAGE_FILE_AUTO) {
112 113 114 115
            if ((ret = virStorageFileProbeFormat(*backingStore)) < 0) {
                /* 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. */
116 117 118
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("cannot probe backing volume format: %s"),
                               *backingStore);
119 120 121 122
                ret = -3;
            } else {
                *backingStoreFormat = ret;
                ret = 0;
123 124
            }
        } else {
125
            *backingStoreFormat = meta->backingStoreFormat;
126
            ret = 0;
127
        }
E
Eric Blake 已提交
128
    } else {
129
        ret = 0;
130 131
    }

132 133
    if (capacity && meta->capacity)
        *capacity = meta->capacity;
134

135
    if (encryption != NULL && meta->encrypted) {
136
        if (VIR_ALLOC(*encryption) < 0) {
137
            virReportOOMError();
138
            goto cleanup;
139 140 141 142 143 144 145 146 147 148 149 150
        }

        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 已提交
151
         * but we cannot guarantee 'conn' is non-NULL
152 153 154 155 156
         * at this point in time :-(  So we only fill
         * in secrets when someone first queries a vol
         */
    }

157 158
    virStorageFileFreeMetadata(meta);

159
    return ret;
160

161 162 163
error:
    VIR_FORCE_CLOSE(fd);

164
cleanup:
165 166 167
    virStorageFileFreeMetadata(meta);
    return ret;

168 169 170
}

#if WITH_STORAGE_FS
171

172
# include <mntent.h>
173

174 175
struct _virNetfsDiscoverState {
    const char *host;
176
    virStoragePoolSourceList list;
177 178 179 180 181
};

typedef struct _virNetfsDiscoverState virNetfsDiscoverState;

static int
182
virStorageBackendFileSystemNetFindPoolSourcesFunc(virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
183 184 185 186 187
                                                  char **const groups,
                                                  void *data)
{
    virNetfsDiscoverState *state = data;
    const char *name, *path;
188 189
    virStoragePoolSource *src = NULL;
    int ret = -1;
190 191 192

    path = groups[0];

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

205
    if (!(src = virStoragePoolSourceListNewSource(&state->list)))
206
        goto cleanup;
207

208 209
    if (VIR_ALLOC_N(src->hosts, 1) < 0) {
        virReportOOMError();
210 211
        goto cleanup;
    }
212
    src->nhost = 1;
213 214

    if (!(src->hosts[0].name = strdup(state->host)) ||
215
        !(src->dir = strdup(path))) {
216
        virReportOOMError();
217 218
        goto cleanup;
    }
219
    src->format = VIR_STORAGE_POOL_NETFS_NFS;
220

221 222 223
    ret = 0;
cleanup:
    return ret;
224 225
}

226

227
static char *
228
virStorageBackendFileSystemNetFindPoolSources(virConnectPtr conn ATTRIBUTE_UNUSED,
229
                                              const char *srcSpec,
E
Eric Blake 已提交
230
                                              unsigned int flags)
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
{
    /*
     *  # 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
    };
246 247 248 249 250 251 252 253
    virNetfsDiscoverState state = {
        .host = NULL,
        .list = {
            .type = VIR_STORAGE_POOL_NETFS,
            .nsources = 0,
            .sources = NULL
        }
    };
254
    virStoragePoolSourcePtr source = NULL;
255
    char *retval = NULL;
256
    unsigned int i;
257
    virCommandPtr cmd = NULL;
258

E
Eric Blake 已提交
259 260
    virCheckFlags(0, NULL);

261 262 263 264 265 266 267 268 269
    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;
270

271
    if (source->nhost != 1) {
272 273
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Expected exactly 1 host for the storage pool"));
274 275 276 277
        goto cleanup;
    }

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

279 280 281 282 283 284 285
    cmd = virCommandNewArgList(SHOWMOUNT,
                               "--no-headers",
                               "--exports",
                               source->hosts[0].name,
                               NULL);

    if (virStorageBackendRunProgRegex(NULL, cmd, 1, regexes, vars,
286
                            virStorageBackendFileSystemNetFindPoolSourcesFunc,
287
                            &state, NULL) < 0)
288 289
        goto cleanup;

290
    retval = virStoragePoolSourceListFormat(&state.list);
291
    if (retval == NULL) {
292
        virReportOOMError();
293 294 295 296
        goto cleanup;
    }

 cleanup:
297
    for (i = 0; i < state.list.nsources; i++)
298 299
        virStoragePoolSourceClear(&state.list.sources[i]);
    VIR_FREE(state.list.sources);
300

301
    virStoragePoolSourceFree(source);
302
    virCommandFree(cmd);
303 304 305 306
    return retval;
}


307 308 309 310 311 312 313 314 315
/**
 * @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
316
virStorageBackendFileSystemIsMounted(virStoragePoolObjPtr pool) {
317
    FILE *mtab;
318 319
    struct mntent ent;
    char buf[1024];
320 321

    if ((mtab = fopen(_PATH_MOUNTED, "r")) == NULL) {
322
        virReportSystemError(errno,
323 324
                             _("cannot read mount list '%s'"),
                             _PATH_MOUNTED);
325 326 327
        return -1;
    }

328 329
    while ((getmntent_r(mtab, &ent, buf, sizeof(buf))) != NULL) {
        if (STREQ(ent.mnt_dir, pool->def->target.path)) {
330
            VIR_FORCE_FCLOSE(mtab);
331 332 333 334
            return 1;
        }
    }

335
    VIR_FORCE_FCLOSE(mtab);
336 337 338 339 340 341 342 343 344 345 346 347 348
    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
349
virStorageBackendFileSystemMount(virStoragePoolObjPtr pool) {
350
    char *src = NULL;
351 352 353
    /* '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 */
354 355 356 357 358 359
    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;
360 361

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

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

    if (pool->def->type == VIR_STORAGE_POOL_NETFS) {
394
        if (virAsprintf(&src, "%s:%s",
395
                        pool->def->source.hosts[0].name,
396
                        pool->def->source.dir) == -1) {
397
            virReportOOMError();
398 399
            return -1;
        }
400

401
    } else {
402
        if ((src = strdup(pool->def->source.devices[0].path)) == NULL) {
403
            virReportOOMError();
404 405
            return -1;
        }
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 435 436 437 438 439
    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);
440
    VIR_FREE(src);
441
    return ret;
442 443 444 445 446 447 448 449 450 451 452 453
}

/**
 * @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
454
virStorageBackendFileSystemUnmount(virStoragePoolObjPtr pool) {
455 456
    virCommandPtr cmd = NULL;
    int ret = -1;
457 458

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

    /* Short-circuit if already unmounted */
483
    if ((ret = virStorageBackendFileSystemIsMounted(pool)) != 1) {
484 485 486 487 488 489
        if (ret < 0)
            return -1;
        else
            return 0;
    }

490 491 492
    cmd = virCommandNewArgList(UMOUNT,
                               pool->def->target.path,
                               NULL);
493

494 495 496 497 498 499 500
    if (virCommandRun(cmd, NULL) < 0)
        goto cleanup;

    ret = 0;
cleanup:
    virCommandFree(cmd);
    return ret;
501 502 503 504
}
#endif /* WITH_STORAGE_FS */


505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
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
529 530 531 532 533 534 535 536 537 538 539
/**
 * @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
540
virStorageBackendFileSystemStart(virConnectPtr conn ATTRIBUTE_UNUSED,
541 542 543
                                 virStoragePoolObjPtr pool)
{
    if (pool->def->type != VIR_STORAGE_POOL_DIR &&
544
        virStorageBackendFileSystemMount(pool) < 0)
545 546 547 548 549 550
        return -1;

    return 0;
}
#endif /* WITH_STORAGE_FS */

O
Osier Yang 已提交
551 552 553 554 555 556 557 558 559 560 561 562 563 564
#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) {
565 566 567 568
        virReportError(VIR_ERR_STORAGE_PROBE_FAILED,
                       _("Not capable of probing for "
                         "filesystem of type %s"),
                       format);
O
Osier Yang 已提交
569 570 571 572 573
        goto error;
    }

    probe = blkid_new_probe_from_filename(device);
    if (probe == NULL) {
574 575 576 577
        virReportError(VIR_ERR_STORAGE_PROBE_FAILED,
                       _("Failed to create filesystem probe "
                         "for device %s"),
                       device);
O
Osier Yang 已提交
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
        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) {
598 599 600 601
        virReportError(VIR_ERR_STORAGE_POOL_BUILT,
                       _("Existing filesystem of type '%s' found on "
                         "device '%s'"),
                       fstype, device);
O
Osier Yang 已提交
602 603 604 605
        ret = FILESYSTEM_PROBE_FOUND;
    }

    if (blkid_do_probe(probe) != 1) {
606
        virReportError(VIR_ERR_STORAGE_PROBE_FAILED, "%s",
607 608
                       _("Found additional probes to run, "
                         "filesystem probing may be incorrect"));
O
Osier Yang 已提交
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
        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)
{
628
    virReportError(VIR_ERR_OPERATION_INVALID, "%s",
629 630
                   _("probing for filesystems is unsupported "
                     "by this build"));
O
Osier Yang 已提交
631 632 633 634 635 636

    return FILESYSTEM_PROBE_ERROR;
}

#endif /* #if HAVE_LIBBLKID */

637 638
/* some platforms don't support mkfs */
#ifdef MKFS
O
Osier Yang 已提交
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
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;
}
661 662 663 664 665
#else /* #ifdef MKFS */
static int
virStorageBackendExecuteMKFS(const char *device ATTRIBUTE_UNUSED,
                             const char *format ATTRIBUTE_UNUSED)
{
666 667 668 669 670
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   _("mkfs is not supported on this platform: "
                     "Failed to make filesystem of "
                     "type '%s' on device '%s'"),
                   format, device);
671 672 673
    return -1;
}
#endif /* #ifdef MKFS */
O
Osier Yang 已提交
674 675 676 677 678 679 680 681 682 683

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) {
684 685 686
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("No source device specified when formatting pool '%s'"),
                       pool->def->name);
O
Osier Yang 已提交
687 688 689 690 691 692 693 694
        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)) {
695 696 697
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("Source device does not exist when formatting pool '%s'"),
                       pool->def->name);
O
Osier Yang 已提交
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
        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;
}

717 718 719 720

/**
 * @conn connection to report errors against
 * @pool storage pool to build
E
Eric Blake 已提交
721
 * @flags controls the pool formatting behaviour
722 723 724
 *
 * Build a directory or FS based storage pool.
 *
O
Osier Yang 已提交
725 726 727 728 729 730 731
 * 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.
 *
732 733 734 735 736
 *  - If it is a FS based pool, mounts the unlying source device on the pool
 *
 * Returns 0 on success, -1 on error
 */
static int
737
virStorageBackendFileSystemBuild(virConnectPtr conn ATTRIBUTE_UNUSED,
738
                                 virStoragePoolObjPtr pool,
E
Eric Blake 已提交
739
                                 unsigned int flags)
740
{
741
    int err, ret = -1;
O
Osier Yang 已提交
742 743
    char *parent = NULL;
    char *p = NULL;
744

O
Osier Yang 已提交
745 746 747 748 749 750
    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)) {

751
        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
752 753
                       _("Overwrite and no overwrite flags"
                         " are mutually exclusive"));
O
Osier Yang 已提交
754 755
        goto error;
    }
E
Eric Blake 已提交
756

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

768 769 770 771
    if (p != parent) {
        /* assure all directories in the path prior to the final dir
         * exist, with default uid/gid/mode. */
        *p = '\0';
772 773
        if (virFileMakePath(parent) < 0) {
            virReportSystemError(errno, _("cannot create path '%s'"),
774 775 776 777 778 779 780 781
                                 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. */
782 783
    if ((err = virDirCreate(pool->def->target.path,
                            pool->def->target.perms.mode,
784 785
                            pool->def->target.perms.uid,
                            pool->def->target.perms.gid,
786 787 788 789 790 791 792
                            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;
793
    }
O
Osier Yang 已提交
794

795 796
    /* Reflect the actual uid and gid to the config. */
    if (pool->def->target.perms.uid == (uid_t) -1)
797
        pool->def->target.perms.uid = getuid();
798
    if (pool->def->target.perms.gid == (gid_t) -1)
799
        pool->def->target.perms.gid = getgid();
800

O
Osier Yang 已提交
801 802 803 804 805 806
    if (flags != 0) {
        ret = virStorageBackendMakeFileSystem(pool, flags);
    } else {
        ret = 0;
    }

807 808 809
error:
    VIR_FREE(parent);
    return ret;
810 811 812 813 814 815 816 817
}


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

    if (!(dir = opendir(pool->def->target.path))) {
827
        virReportSystemError(errno,
828 829
                             _("cannot open path '%s'"),
                             pool->def->target.path);
830 831 832 833 834
        goto cleanup;
    }

    while ((ent = readdir(dir)) != NULL) {
        int ret;
835
        char *backingStore;
836
        int backingStoreFormat;
837

838 839
        if (VIR_ALLOC(vol) < 0)
            goto no_memory;
840

841 842
        if ((vol->name = strdup(ent->d_name)) == NULL)
            goto no_memory;
843

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

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

854
        if ((ret = virStorageBackendProbeTarget(&vol->target,
855
                                                &backingStore,
856
                                                &backingStoreFormat,
857
                                                &vol->allocation,
858
                                                &vol->capacity,
859
                                                &vol->target.encryption)) < 0) {
860
            if (ret == -2) {
861
                /* Silently ignore non-regular files,
862
                 * eg '.' '..', 'lost+found', dangling symbolic link */
863 864
                virStorageVolDefFree(vol);
                vol = NULL;
865
                continue;
866 867 868 869 870 871 872 873 874
            } 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;
875 876
        }

877 878 879 880
        /* directory based volume */
        if (vol->target.format == VIR_STORAGE_FILE_DIR)
            vol->type = VIR_STORAGE_VOL_DIR;

881
        if (backingStore != NULL) {
882 883 884 885
            vol->backingStore.path = backingStore;
            vol->backingStore.format = backingStoreFormat;

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


901 902 903 904 905
        if (VIR_REALLOC_N(pool->volumes.objs,
                          pool->volumes.count+1) < 0)
            goto no_memory;
        pool->volumes.objs[pool->volumes.count++] = vol;
        vol = NULL;
906 907 908 909 910
    }
    closedir(dir);


    if (statvfs(pool->def->target.path, &sb) < 0) {
911
        virReportSystemError(errno,
912 913
                             _("cannot statvfs path '%s'"),
                             pool->def->target.path);
914 915 916 917 918 919 920 921 922 923
        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;

924
no_memory:
925
    virReportOOMError();
926 927
    /* fallthrough */

928
 cleanup:
929 930
    if (dir)
        closedir(dir);
931
    virStorageVolDefFree(vol);
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
    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
948
virStorageBackendFileSystemStop(virConnectPtr conn ATTRIBUTE_UNUSED,
949 950 951
                                virStoragePoolObjPtr pool)
{
    if (pool->def->type != VIR_STORAGE_POOL_DIR &&
952
        virStorageBackendFileSystemUnmount(pool) < 0)
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
        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
971
virStorageBackendFileSystemDelete(virConnectPtr conn ATTRIBUTE_UNUSED,
972
                                  virStoragePoolObjPtr pool,
E
Eric Blake 已提交
973
                                  unsigned int flags)
974
{
E
Eric Blake 已提交
975 976
    virCheckFlags(0, -1);

977 978
    /* XXX delete all vols first ? */

979
    if (rmdir(pool->def->target.path) < 0) {
980
        virReportSystemError(errno,
981
                             _("failed to remove pool '%s'"),
982
                             pool->def->target.path);
983 984 985 986 987 988 989 990
        return -1;
    }

    return 0;
}


/**
991 992 993 994
 * 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.
995 996
 */
static int
997
virStorageBackendFileSystemVolCreate(virConnectPtr conn ATTRIBUTE_UNUSED,
998 999 1000 1001
                                     virStoragePoolObjPtr pool,
                                     virStorageVolDefPtr vol)
{

1002 1003
    vol->type = VIR_STORAGE_VOL_FILE;

R
Ryota Ozaki 已提交
1004
    VIR_FREE(vol->target.path);
1005 1006 1007
    if (virAsprintf(&vol->target.path, "%s/%s",
                    pool->def->target.path,
                    vol->name) == -1) {
1008
        virReportOOMError();
1009 1010
        return -1;
    }
1011

R
Ryota Ozaki 已提交
1012
    VIR_FREE(vol->key);
1013 1014
    vol->key = strdup(vol->target.path);
    if (vol->key == NULL) {
1015
        virReportOOMError();
1016 1017 1018
        return -1;
    }

1019 1020 1021
    return 0;
}

1022
static int createFileDir(virConnectPtr conn ATTRIBUTE_UNUSED,
1023
                         virStoragePoolObjPtr pool,
1024
                         virStorageVolDefPtr vol,
1025
                         virStorageVolDefPtr inputvol,
E
Eric Blake 已提交
1026 1027
                         unsigned int flags)
{
1028 1029
    int err;

E
Eric Blake 已提交
1030 1031
    virCheckFlags(0, -1);

1032
    if (inputvol) {
1033 1034 1035
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s",
                       _("cannot copy from volume to a directory volume"));
1036 1037 1038
        return -1;
    }

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

1050 1051
    return 0;
}
1052

1053
static int
1054
_virStorageBackendFileSystemVolBuild(virConnectPtr conn,
1055
                                     virStoragePoolObjPtr pool,
1056 1057
                                     virStorageVolDefPtr vol,
                                     virStorageVolDefPtr inputvol)
1058
{
1059
    virStorageBackendBuildVolFrom create_func;
1060
    int tool_type;
1061

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

        if (!create_func)
1082
            return -1;
1083
    } else {
1084 1085 1086
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("creation of non-raw images "
                               "is not supported without qemu-img"));
1087 1088 1089
        return -1;
    }

1090
    if (create_func(conn, pool, vol, inputvol, 0) < 0)
1091
        return -1;
1092 1093 1094
    return 0;
}

1095 1096 1097 1098 1099 1100 1101
/**
 * 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,
1102
                                    virStoragePoolObjPtr pool,
1103
                                    virStorageVolDefPtr vol) {
1104
    return _virStorageBackendFileSystemVolBuild(conn, pool, vol, NULL);
1105 1106 1107 1108 1109 1110 1111
}

/*
 * Create a storage vol using 'inputvol' as input
 */
static int
virStorageBackendFileSystemVolBuildFrom(virConnectPtr conn,
1112
                                        virStoragePoolObjPtr pool,
1113 1114
                                        virStorageVolDefPtr vol,
                                        virStorageVolDefPtr inputvol,
E
Eric Blake 已提交
1115 1116 1117 1118
                                        unsigned int flags)
{
    virCheckFlags(0, -1);

1119
    return _virStorageBackendFileSystemVolBuild(conn, pool, vol, inputvol);
1120
}
1121 1122

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

1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
    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) {
1147
            virReportSystemError(errno,
1148
                                 _("cannot remove directory '%s'"),
1149
                                 vol->target.path);
1150 1151
            return -1;
        }
1152 1153 1154 1155
        break;
    case VIR_STORAGE_VOL_BLOCK:
    case VIR_STORAGE_VOL_NETWORK:
    default:
1156 1157 1158
        virReportError(VIR_ERR_NO_SUPPORT,
                       _("removing block or network volumes is not supported: %s"),
                       vol->target.path);
1159
        return -1;
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
    }
    return 0;
}


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

1175
    /* Refresh allocation / permissions info in case its changed */
1176 1177
    ret = virStorageBackendUpdateVolInfoFlags(vol, 0,
                                              VIR_STORAGE_VOL_FS_OPEN_FLAGS);
1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
    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);
1195
                virReportOOMError();
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
                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;
1210 1211
}

1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
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) {
1226 1227
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("unable to find kvm-img or qemu-img"));
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 1257 1258 1259 1260 1261
        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);
}

1262 1263 1264 1265
virStorageBackend virStorageBackendDirectory = {
    .type = VIR_STORAGE_POOL_DIR,

    .buildPool = virStorageBackendFileSystemBuild,
1266
    .checkPool = virStorageBackendFileSystemCheck,
1267 1268
    .refreshPool = virStorageBackendFileSystemRefresh,
    .deletePool = virStorageBackendFileSystemDelete,
1269
    .buildVol = virStorageBackendFileSystemVolBuild,
1270
    .buildVolFrom = virStorageBackendFileSystemVolBuildFrom,
1271 1272 1273
    .createVol = virStorageBackendFileSystemVolCreate,
    .refreshVol = virStorageBackendFileSystemVolRefresh,
    .deleteVol = virStorageBackendFileSystemVolDelete,
1274
    .resizeVol = virStorageBackendFileSystemVolResize,
1275 1276 1277 1278 1279 1280 1281
};

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

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

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