"use strict";
/*
Copyright 2016 Balena.io

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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.findPartition = exports.LabelNotFound = exports.getFsLabel = void 0;
exports.interact = interact;
exports.explorePartition = explorePartition;
/**
 * @module imagefs
 */
const ext2fs = require("ext2fs");
const fatfs = require("fatfs");
const Fs = require("fs");
const util_1 = require("util");
const file_disk_1 = require("file-disk");
const partitioninfo = require("partitioninfo");
const typed_error_1 = require("typed-error");
const utils_1 = require("./utils");
var fsLabel_1 = require("./fsLabel");
Object.defineProperty(exports, "getFsLabel", { enumerable: true, get: function () { return fsLabel_1.getFsLabel; } });
Object.defineProperty(exports, "LabelNotFound", { enumerable: true, get: function () { return fsLabel_1.LabelNotFound; } });
var utils_2 = require("./utils");
Object.defineProperty(exports, "findPartition", { enumerable: true, get: function () { return utils_2.findPartition; } });
class MountError extends typed_error_1.TypedError {
}
const SECTOR_SIZE = 512;
async function runInFat(disk, offset, size, fn) {
    function sectorPosition(sector) {
        return offset + sector * SECTOR_SIZE;
    }
    const fat = fatfs.createFileSystem({
        sectorSize: SECTOR_SIZE,
        numSectors: size / SECTOR_SIZE,
        readSectors: async (sector, dest, callback) => {
            try {
                const { bytesRead, buffer } = await disk.read(dest, 0, dest.length, sectorPosition(sector));
                callback(null, bytesRead, buffer);
            }
            catch (e) {
                callback(e);
            }
        },
        writeSectors: async (sector, data, callback) => {
            try {
                const { bytesWritten, buffer } = await disk.write(data, 0, data.length, sectorPosition(sector));
                callback(null, bytesWritten, buffer);
            }
            catch (e) {
                callback(e);
            }
        },
    });
    await new Promise(function (resolve, reject) {
        fat.on('error', (e) => {
            reject(new MountError(e));
        });
        fat.on('ready', resolve);
    });
    // Check whether fatfs added the promises namespace on their side
    if (fat.promises == null) {
        // Lazily populate the promise based variants
        const originalFatKeys = Object.keys(fat);
        Object.defineProperty(fat, 'promises', {
            enumerable: true,
            configurable: true,
            get() {
                const promises = {};
                for (const key of originalFatKeys) {
                    const value = fat[key];
                    if (typeof value === 'function' && (key in Fs.promises)) {
                        promises[key] = (0, util_1.promisify)(value);
                    }
                }
                originalFatKeys.length = 0;
                // We need the delete first as the current property is read-only
                // and the delete removes that restriction
                delete this.promises;
                return (this.promises = promises);
            },
        });
    }
    return await fn(fat);
}
async function runInExt(disk, offset, fn) {
    let fs;
    try {
        fs = await ext2fs.mount(disk, offset);
    }
    catch (e) {
        throw new MountError(e);
    }
    try {
        return await fn(fs);
    }
    finally {
        await ext2fs.umount(fs);
    }
}
async function tryInteract(disk, offset, size, fn) {
    try {
        return await runInFat(disk, offset, size, fn);
    }
    catch (e) {
        if (!(e instanceof MountError)) {
            throw e;
        }
        try {
            return await runInExt(disk, offset, fn);
        }
        catch (e2) {
            if (!(e2 instanceof MountError)) {
                throw e2;
            }
            throw new Error('Unsupported filesystem.');
        }
    }
}
async function getPartitionOffset(disk, partition) {
    if (partition === undefined) {
        const size = await disk.getCapacity();
        return { offset: 0, size };
    }
    else {
        return await partitioninfo.get(disk, partition);
    }
}
async function diskInteract(disk, partition, fn) {
    const { offset, size } = await getPartitionOffset(disk, partition);
    return await tryInteract(disk, offset, size, fn);
}
/**
 * @summary Run a function with a node fs like interface for a partition
 *
 * @example
 *
 * const contents = await interact('/foo/bar.img', 5, async (fs) => {
 * 	return await promisify(fs.readFile)('/bar/qux');
 * });
 * console.log(contents);
 *
 */
async function interact(disk, partition, fn) {
    if (typeof disk === 'string') {
        return await (0, file_disk_1.withOpenFile)(disk, 'r+', async (handle) => {
            disk = new file_disk_1.FileDisk(handle);
            return await diskInteract(disk, partition, fn);
        });
    }
    else if (disk instanceof file_disk_1.Disk) {
        return await diskInteract(disk, partition, fn);
    }
    else {
        throw new Error('image must be a String (file path) or a Disk instance');
    }
}
/**
 * @summary Allow a provided function to explore the contents of the
 * first found partition of an image
 *
 * @param {string} imagePath - pathname of image for search
 * @param {string[]} partitionNames - partitions to find
 * @param {function(fs): Promise<T>} - function for exploration
 * @returns {T}
 */
async function explorePartition(imagePath, partitionNames, exploreFn) {
    return await (0, file_disk_1.withOpenFile)(imagePath, 'r', async (handle) => {
        const disk = new file_disk_1.FileDisk(handle, true, false, false);
        const partitionInfo = await partitioninfo.getPartitions(disk, {
            includeExtended: false,
            getLogical: true,
        });
        const findResult = await (0, utils_1.findPartition)(disk, partitionInfo, partitionNames);
        if (findResult == null) {
            throw new Error(`Can't find partition for ${partitionNames}`);
        }
        return await interact(disk, findResult.index, exploreFn);
    });
}
//# sourceMappingURL=index.js.map