2N/A/* file.c - file I/O functions */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2002,2006,2007,2009 Free Software Foundation, Inc.
2N/A * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/misc.h>
2N/A#include <grub/err.h>
2N/A#include <grub/file.h>
2N/A#include <grub/net.h>
2N/A#include <grub/mm.h>
2N/A#include <grub/fs.h>
2N/A#include <grub/time.h>
2N/A#include <grub/env.h>
2N/A#include <grub/device.h>
2N/A
2N/A#define SUPPORT_PROGRESS
2N/A
2N/A#ifdef SUPPORT_PROGRESS
2N/A#include <grub/term.h>
2N/A#endif
2N/A
2N/Avoid (*EXPORT_VAR (grub_grubnet_fini)) (void);
2N/A
2N/Aint grub_file_force_not_easily_seekable;
2N/Aint grub_print_progress;
2N/A
2N/Agrub_file_filter_t grub_file_filters_all[GRUB_FILE_FILTER_MAX];
2N/Agrub_file_filter_t grub_file_filters_enabled[GRUB_FILE_FILTER_MAX];
2N/A
2N/A/* Get the device part of the filename NAME. It is enclosed by parentheses. */
2N/Achar *
2N/Agrub_file_get_device_name (const char *name)
2N/A{
2N/A if (name[0] == '(')
2N/A {
2N/A char *p = grub_strchr (name, ')');
2N/A char *ret;
2N/A
2N/A if (! p)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FILENAME, "missing `)'");
2N/A return 0;
2N/A }
2N/A
2N/A ret = (char *) grub_malloc (p - name);
2N/A if (! ret)
2N/A return 0;
2N/A
2N/A grub_memcpy (ret, name + 1, p - name - 1);
2N/A ret[p - name - 1] = '\0';
2N/A return ret;
2N/A }
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Agrub_file_t
2N/Agrub_file_open (const char *name)
2N/A{
2N/A grub_device_t device = 0;
2N/A grub_file_t file = 0, last_file = 0;
2N/A char *device_name;
2N/A char *file_name;
2N/A grub_file_filter_id_t filter;
2N/A
2N/A device_name = grub_file_get_device_name (name);
2N/A if (grub_errno)
2N/A goto fail;
2N/A
2N/A /* Get the file part of NAME. */
2N/A file_name = (name[0] == '(') ? grub_strchr (name, ')') : NULL;
2N/A if (file_name)
2N/A file_name++;
2N/A else
2N/A file_name = (char *) name;
2N/A
2N/A device = grub_device_open (device_name);
2N/A grub_free (device_name);
2N/A if (! device)
2N/A goto fail;
2N/A
2N/A file = (grub_file_t) grub_zalloc (sizeof (*file));
2N/A if (! file)
2N/A goto fail;
2N/A
2N/A file->device = device;
2N/A
2N/A if (device->disk && file_name[0] != '/')
2N/A /* This is a block list. */
2N/A file->fs = &grub_fs_blocklist;
2N/A else
2N/A {
2N/A file->fs = grub_fs_probe (device);
2N/A if (! file->fs)
2N/A goto fail;
2N/A }
2N/A
2N/A {
2N/A const char *noprogress = grub_env_get("grub_file_noprogress");
2N/A file->enable_progress = noprogress ? 0 : grub_print_progress;
2N/A }
2N/A grub_print_progress = 0;
2N/A
2N/A if ((file->fs->open) (file, file_name) != GRUB_ERR_NONE)
2N/A goto fail;
2N/A
2N/A for (filter = 0; file && filter < ARRAY_SIZE (grub_file_filters_enabled);
2N/A filter++)
2N/A if (grub_file_filters_enabled[filter])
2N/A {
2N/A last_file = file;
2N/A file = grub_file_filters_enabled[filter] (file);
2N/A }
2N/A if (!file)
2N/A grub_file_close (last_file);
2N/A
2N/A grub_memcpy (grub_file_filters_enabled, grub_file_filters_all,
2N/A sizeof (grub_file_filters_enabled));
2N/A grub_file_force_not_easily_seekable = 0;
2N/A
2N/A return file;
2N/A
2N/A fail:
2N/A if (device)
2N/A grub_device_close (device);
2N/A
2N/A /* if (net) grub_net_close (net); */
2N/A
2N/A grub_free (file);
2N/A
2N/A grub_memcpy (grub_file_filters_enabled, grub_file_filters_all,
2N/A sizeof (grub_file_filters_enabled));
2N/A
2N/A return 0;
2N/A}
2N/A
2N/A#define PROGRESS_THRESHOLD (2000) /* 2s */
2N/A#define PROGRESS_BLOCKSIZE (32*1024)
2N/Avoid
2N/Agrub_file_progress_hook(grub_file_t file, grub_size_t amt_read, int done)
2N/A{
2N/A grub_uint64_t curtime;
2N/A grub_off_t filesz;
2N/A
2N/A if (file == NULL || !file->enable_progress)
2N/A return;
2N/A
2N/A if (done)
2N/A {
2N/A file->enable_progress = 0;
2N/A grub_printf("done.\n");
2N/A grub_refresh();
2N/A return;
2N/A }
2N/A
2N/A curtime = grub_get_time_ms();
2N/A file->progress_counter += amt_read;
2N/A filesz = grub_file_size(file);
2N/A
2N/A if (file->progress_last == 0 ||
2N/A (curtime - file->progress_last) >= PROGRESS_THRESHOLD ||
2N/A file->progress_counter == filesz)
2N/A {
2N/A grub_dprintf("progress", "counter = 0x%llx | last = 0x%llx\n",
2N/A (unsigned long long) file->progress_counter,
2N/A (unsigned long long) file->progress_last);
2N/A
2N/A file->progress_last = curtime;
2N/A if (filesz == 0 || file->progress_counter > filesz)
2N/A grub_printf("...");
2N/A else
2N/A {
2N/A unsigned pct = grub_divmod64(file->progress_counter * 100,
2N/A filesz, 0);
2N/A grub_printf("%u%%...", pct);
2N/A grub_dprintf("progress", "pct completed is %u\n", pct);
2N/A }
2N/A grub_refresh();
2N/A }
2N/A}
2N/A
2N/A
2N/Agrub_ssize_t
2N/Agrub_file_read (grub_file_t file, void *buf, grub_size_t len)
2N/A{
2N/A grub_ssize_t res;
2N/A#ifdef SUPPORT_PROGRESS
2N/A grub_ssize_t amt_read;
2N/A grub_size_t readsz;
2N/A#endif
2N/A
2N/A if (!file->is_size_approximate)
2N/A {
2N/A if (file->offset > file->size)
2N/A {
2N/A grub_error (GRUB_ERR_OUT_OF_RANGE,
2N/A "attempt to read past the end of file");
2N/A return -1;
2N/A }
2N/A
2N/A if (len == 0 || len > file->size - file->offset)
2N/A len = file->size - file->offset;
2N/A
2N/A /* Prevent an overflow. */
2N/A if ((grub_ssize_t) len < 0)
2N/A len >>= 1;
2N/A
2N/A if (len == 0)
2N/A return 0;
2N/A }
2N/A#ifdef SUPPORT_PROGRESS
2N/A if (!file->enable_progress || grub_strcmp(file->fs->name, "netfs") == 0)
2N/A {
2N/A#endif
2N/A res = (file->fs->read) (file, buf, len);
2N/A if (res > 0)
2N/A file->offset += res;
2N/A#ifdef SUPPORT_PROGRESS
2N/A }
2N/A else
2N/A {
2N/A char *buffer = buf;
2N/A grub_size_t remaining = len;
2N/A res = 0;
2N/A do
2N/A {
2N/A readsz = remaining;
2N/A if (readsz > PROGRESS_BLOCKSIZE)
2N/A readsz = PROGRESS_BLOCKSIZE;
2N/A amt_read = (file->fs->read) (file, buffer, readsz);
2N/A
2N/A if (amt_read < 0)
2N/A {
2N/A res = amt_read;
2N/A break;
2N/A }
2N/A
2N/A remaining -= amt_read;
2N/A buffer += amt_read;
2N/A res += amt_read;
2N/A
2N/A file->offset += amt_read;
2N/A
2N/A grub_file_progress_hook(file, amt_read, 0);
2N/A
2N/A } while (remaining > 0 && (grub_size_t)amt_read == readsz);
2N/A }
2N/A#endif
2N/A
2N/A return res;
2N/A}
2N/A
2N/Agrub_err_t
2N/Agrub_file_close (grub_file_t file)
2N/A{
2N/A if (file->fs->close)
2N/A (file->fs->close) (file);
2N/A
2N/A if (file->device)
2N/A grub_device_close (file->device);
2N/A
2N/A#ifdef SUPPORT_PROGRESS
2N/A grub_file_progress_hook(file, 0, 1);
2N/A#endif
2N/A
2N/A grub_free (file);
2N/A return grub_errno;
2N/A}
2N/A
2N/Agrub_off_t
2N/Agrub_file_seek (grub_file_t file, grub_off_t offset)
2N/A{
2N/A grub_off_t old;
2N/A
2N/A if (offset > file->size)
2N/A {
2N/A grub_error (GRUB_ERR_OUT_OF_RANGE,
2N/A "attempt to seek outside of the file");
2N/A return -1;
2N/A }
2N/A
2N/A old = file->offset;
2N/A file->offset = offset;
2N/A
2N/A return old;
2N/A}