/* tools/editdisklbl/editdisklbl.c
 *
 * Copyright 2008, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define __USE_LARGEFILE64
#define __USE_FILE_OFFSET64
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "diskconfig.h"

/* give us some room */
#define EXTRA_LBAS      100

static struct pf_map {
    struct part_info *pinfo;
    const char *filename;
} part_file_map[MAX_NUM_PARTS] = { {0, 0} };

static int
usage(void)
{
    fprintf(stderr,
            "\nusage: editdisklbl <options> part1=file1 [part2=file2,...]\n"
            "Where options can be one of:\n"
            "\t\t-l <layout conf>  -- The image layout config file.\n"
            "\t\t-i <image file>   -- The image file to edit.\n"
            "\t\t-t                -- Test mode (optional)\n"
            "\t\t-v                -- Be verbose\n"
            "\t\t-h                -- This message (optional)\n"
            );
    return 1;
}

static int
parse_args(int argc, char *argv[], struct disk_info **dinfo, int *test,
           int *verbose)
{
    char *layout_conf = NULL;
    char *img_file = NULL;
    struct stat filestat;
    int x;
    int update_lba = 0;

    while ((x = getopt (argc, argv, "thl:i:")) != EOF) {
        switch (x) {
            case 'h':
                return usage();
            case 'l':
                layout_conf = optarg;
                break;
            case 't':
                *test = 1;
                break;
            case 'i':
                img_file = optarg;
                break;
            case 'v':
                *verbose = 1;
                break;
            default:
                fprintf(stderr, "Unknown argument: %c\n", (char)optopt);
                return usage();
        }
    }

    if (!img_file || !layout_conf) {
        fprintf(stderr, "Image filename and configuration file are required\n");
        return usage();
    }

    /* we'll need to parse the command line later for partition-file
     * mappings, so make sure there's at least something there */
    if (optind >= argc) {
        fprintf(stderr, "Must provide partition -> file mappings\n");
        return usage();
    }

    if (stat(img_file, &filestat)) {
        perror("Cannot stat image file");
        return 1;
    }

    /* make sure we don't screw up and write to a block device on the host
     * and wedge things. I just don't trust myself. */
    if (!S_ISREG(filestat.st_mode)) {
        fprintf(stderr, "This program should only be used on regular files.");
        return 1;
    }

    /* load the disk layout file */
    if (!(*dinfo = load_diskconfig(layout_conf, img_file))) {
        fprintf(stderr, "Errors encountered while loading disk conf file %s",
                layout_conf);
        return 1;
    }

    if ((*dinfo)->num_lba == 0) {
        (*dinfo)->num_lba = (*dinfo)->skip_lba + EXTRA_LBAS;
        update_lba = 1;
    }

    /* parse the filename->partition mappings from the command line and patch
     * up a loaded config file's partition table entries to have
     * length == filesize */
    x = 0;
    while (optind < argc) {
        char *pair = argv[optind++];
        char *part_name;
        struct part_info *pinfo;
        struct stat tmp_stat;

        if (x >= MAX_NUM_PARTS) {
            fprintf(stderr, "Error: Too many partitions specified (%d)!\n", x);
            return 1;
        }

        if (!(part_name = strsep(&pair, "=")) || !pair || !(*pair)) {
            fprintf(stderr, "Error parsing partition mappings\n");
            return usage();
        }

        if (!(pinfo = find_part(*dinfo, part_name))) {
            fprintf(stderr, "Partition '%s' not found.\n", part_name);
            return 1;
        }

        /* here pair points to the filename (after the '=') */
        part_file_map[x].pinfo = pinfo;
        part_file_map[x++].filename = pair;

        if (stat(pair, &tmp_stat) < 0) {
            fprintf(stderr, "Could not stat file: %s\n", pair);
            return 1;
        }

        pinfo->len_kb = (uint32_t) ((tmp_stat.st_size + 1023) >> 10);
        if (update_lba)
            (*dinfo)->num_lba +=
                    ((uint64_t)pinfo->len_kb * 1024) / (*dinfo)->sect_size;
        printf("Updated %s length to be %uKB\n", pinfo->name, pinfo->len_kb);
    }

    return 0;
}

int
main(int argc, char *argv[])
{
    struct disk_info *dinfo = NULL;
    int test = 0;
    int verbose = 0;
    int cnt;

    if (parse_args(argc, argv, &dinfo, &test, &verbose))
        return 1;

    if (verbose)
        dump_disk_config(dinfo);

    if (test)
        printf("Test mode enabled. Actions will not be committed to disk!\n");

    if (apply_disk_config(dinfo, test)) {
        fprintf(stderr, "Could not apply disk configuration!\n");
        return 1;
    }

    printf("Copying images to specified partition offsets\n");
    /* now copy the images to their appropriate locations on disk */
    for (cnt = 0; cnt < MAX_NUM_PARTS && part_file_map[cnt].pinfo; ++cnt) {
        loff_t offs = part_file_map[cnt].pinfo->start_lba * dinfo->sect_size;
        const char *dest_fn = dinfo->device;
        if (write_raw_image(dest_fn, part_file_map[cnt].filename, offs, test)) {
            fprintf(stderr, "Could not write images after editing label.\n");
            return 1;
        }
    }
    printf("File edit complete. Wrote %d images.\n", cnt);

    return 0;
}
