iohelper.c 6.8 KB
Newer Older
1 2 3
/*
 * iohelper.c: Helper program to perform I/O operations on files
 *
4
 * Copyright (C) 2011-2012 Red Hat, Inc.
5 6 7 8 9 10 11 12 13 14 15 16
 *
 * 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
17
 * License along with this library.  If not, see
O
Osier Yang 已提交
18
 * <http://www.gnu.org/licenses/>.
19 20 21 22 23 24 25 26 27 28 29 30
 *
 * Current support
 *   - Read existing file
 *   - Write existing file
 *   - Create & write new file
 */

#include <config.h>

#include <unistd.h>
#include <fcntl.h>

31
#include "virutil.h"
32
#include "virthread.h"
E
Eric Blake 已提交
33
#include "virfile.h"
34
#include "viralloc.h"
35
#include "virerror.h"
36
#include "virrandom.h"
37
#include "virstring.h"
38
#include "virgettext.h"
39 40 41

#define VIR_FROM_THIS VIR_FROM_STORAGE

42
static int
43
runIO(const char *path, int fd, int oflags)
44
{
45
    VIR_AUTOFREE(void *) base = NULL; /* Location to be freed */
46
    char *buf = NULL; /* Aligned location within base */
47
    size_t buflen = 1024*1024;
48
    intptr_t alignMask = 64*1024 - 1;
49 50 51 52
    int ret = -1;
    int fdin, fdout;
    const char *fdinname, *fdoutname;
    unsigned long long total = 0;
53 54
    bool direct = O_DIRECT && ((oflags & O_DIRECT) != 0);
    off_t end = 0;
55

56 57
#if HAVE_POSIX_MEMALIGN
    if (posix_memalign(&base, alignMask + 1, buflen)) {
58 59 60
        virReportOOMError();
        goto cleanup;
    }
61 62
    buf = base;
#else
63
    if (VIR_ALLOC_N(buf, buflen + alignMask) < 0)
64 65
        goto cleanup;
    base = buf;
P
Paolo Bonzini 已提交
66
    buf = (char *) (((intptr_t) base + alignMask) & ~alignMask);
67
#endif
68

69
    switch (oflags & O_ACCMODE) {
70 71 72 73 74
    case O_RDONLY:
        fdin = fd;
        fdinname = path;
        fdout = STDOUT_FILENO;
        fdoutname = "stdout";
75 76
        /* To make the implementation simpler, we give up on any
         * attempt to use O_DIRECT in a non-trivial manner.  */
77
        if (direct && ((end = lseek(fd, 0, SEEK_CUR)) != 0)) {
78 79 80 81
            virReportSystemError(end < 0 ? errno : EINVAL, "%s",
                                 _("O_DIRECT read needs entire seekable file"));
            goto cleanup;
        }
82 83 84 85 86 87
        break;
    case O_WRONLY:
        fdin = STDIN_FILENO;
        fdinname = "stdin";
        fdout = fd;
        fdoutname = path;
88 89 90 91 92 93 94
        /* To make the implementation simpler, we give up on any
         * attempt to use O_DIRECT in a non-trivial manner.  */
        if (direct && (end = lseek(fd, 0, SEEK_END)) != 0) {
            virReportSystemError(end < 0 ? errno : EINVAL, "%s",
                                 _("O_DIRECT write needs empty seekable file"));
            goto cleanup;
        }
95 96 97 98 99 100
        break;

    case O_RDWR:
    default:
        virReportSystemError(EINVAL,
                             _("Unable to process file with flags %d"),
101
                             (oflags & O_ACCMODE));
102 103 104 105 106 107
        goto cleanup;
    }

    while (1) {
        ssize_t got;

108 109 110 111 112 113 114 115 116
        /* If we read with O_DIRECT from file we can't use saferead as
         * it can lead to unaligned read after reading last bytes.
         * If we write with O_DIRECT use should use saferead so that
         * writes will be aligned.
         * In other cases using saferead reduces number of syscalls.
         */
        if (fdin == fd && direct) {
            if ((got = read(fdin, buf, buflen)) < 0 &&
                errno == EINTR)
117
                continue;
118 119 120 121 122
        } else {
            got = saferead(fdin, buf, buflen);
        }

        if (got < 0) {
123 124 125 126
            virReportSystemError(errno, _("Unable to read %s"), fdinname);
            goto cleanup;
        }
        if (got == 0)
127
            break;
128 129

        total += got;
130 131 132

        /* handle last write size align in direct case */
        if (got < buflen && direct && fdout == fd) {
133
            ssize_t aligned_got = (got + alignMask) & ~alignMask;
134

135 136 137
            memset(buf + got, 0, aligned_got - got);

            if (safewrite(fdout, buf, aligned_got) < 0) {
138 139 140 141 142 143 144 145 146 147
                virReportSystemError(errno, _("Unable to write %s"), fdoutname);
                goto cleanup;
            }

            if (ftruncate(fd, total) < 0) {
                virReportSystemError(errno, _("Unable to truncate %s"), fdoutname);
                goto cleanup;
            }

            break;
148
        }
149

150 151 152 153 154 155
        if (safewrite(fdout, buf, got) < 0) {
            virReportSystemError(errno, _("Unable to write %s"), fdoutname);
            goto cleanup;
        }
    }

156 157
    /* Ensure all data is written */
    if (fdatasync(fdout) < 0) {
158 159 160 161 162
        if (errno != EINVAL && errno != EROFS) {
            /* fdatasync() may fail on some special FDs, e.g. pipes */
            virReportSystemError(errno, _("unable to fsync %s"), fdoutname);
            goto cleanup;
        }
163 164
    }

165 166
    ret = 0;

167
 cleanup:
168 169 170 171 172 173 174 175
    if (VIR_CLOSE(fd) < 0 &&
        ret == 0) {
        virReportSystemError(errno, _("Unable to close %s"), path);
        ret = -1;
    }
    return ret;
}

176 177 178 179 180 181 182 183
static const char *program_name;

ATTRIBUTE_NORETURN static void
usage(int status)
{
    if (status) {
        fprintf(stderr, _("%s: try --help for more details"), program_name);
    } else {
184
        printf(_("Usage: %s FILENAME FD\n"), program_name);
185 186 187 188 189 190
    }
    exit(status);
}

int
main(int argc, char **argv)
191 192
{
    const char *path;
193 194 195 196
    int oflags = -1;
    int fd = -1;

    program_name = argv[0];
197

198 199
    if (virGettextInitialize() < 0 ||
        virThreadInitialize() < 0 ||
200
        virErrorInitialize() < 0) {
201
        fprintf(stderr, _("%s: initialization failed\n"), program_name);
202 203 204 205 206
        exit(EXIT_FAILURE);
    }

    path = argv[1];

207 208
    if (argc > 1 && STREQ(argv[1], "--help"))
        usage(EXIT_SUCCESS);
209 210
    if (argc == 3) { /* FILENAME FD */
        if (virStrToLong_i(argv[2], NULL, 10, &fd) < 0) {
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
            fprintf(stderr, _("%s: malformed fd %s"),
                    program_name, argv[3]);
            exit(EXIT_FAILURE);
        }
#ifdef F_GETFL
        oflags = fcntl(fd, F_GETFL);
#else
        /* Stupid mingw.  */
        if (fd == STDIN_FILENO)
            oflags = O_RDONLY;
        else if (fd == STDOUT_FILENO)
            oflags = O_WRONLY;
#endif
        if (oflags < 0) {
            fprintf(stderr, _("%s: unable to determine access mode of fd %d"),
                    program_name, fd);
            exit(EXIT_FAILURE);
        }
    } else { /* unknown argc pattern */
        usage(EXIT_FAILURE);
231 232
    }

233
    if (fd < 0 || runIO(path, fd, oflags) < 0)
234 235 236 237
        goto error;

    return 0;

238
 error:
239
    fprintf(stderr, _("%s: failure with %s: %s"),
240
            program_name, path, virGetLastErrorMessage());
241 242
    exit(EXIT_FAILURE);
}