#!/usr/bin/env gjs
//
// Copyright (C) 2014 Colin Walters <walters@verbum.org>
//
// SPDX-License-Identifier: LGPL-2.0+
//
// 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 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
// License along with this library. If not, see <https://www.gnu.org/licenses/>.

const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const OSTree = imports.gi.OSTree;

let repoPathArg = ARGV[0];
let refToCorrupt = ARGV[1];

let repo = OSTree.Repo.new(Gio.File.new_for_path(repoPathArg));

repo.open(null);

function listObjectChecksumsRecurse(dir, allObjects) {
    dir.ensure_resolved();
    allObjects[dir.tree_get_contents_checksum() + '.dirtree'] = true;
    allObjects[dir.get_checksum() + '.dirmeta'] = true;
    let e = dir.enumerate_children('standard::name,standard::type', 0, null);
    let info;
    while ((info = e.next_file(null)) != null) {
	let child = e.get_child(info);
	child.ensure_resolved();
	print(info.get_name() + " is " + info.get_file_type());
	if (info.get_file_type() == Gio.FileType.DIRECTORY) {
	    listObjectChecksumsRecurse(child, allObjects);
	} else {
	    allObjects[child.get_checksum() + '.filez'] = true;
	}
    } 
    e.close(null);
} 

let [,root,commit] = repo.read_commit(refToCorrupt, null);
let allObjects = {};
allObjects[commit + '.commit'] = true;
listObjectChecksumsRecurse(root, allObjects);
let i = 0;
for (let v in allObjects) 
    i++;
print("commit " + commit + " refers to " + i + " objects");
let offset = GLib.random_int_range(0, i);
let objectToCorrupt = null;
for (let v in allObjects) {
    if (offset <= 0) {
	objectToCorrupt = v;
	break;
    }
    offset--;
}
print("Choosing " + objectToCorrupt + " to corrupt");

let loosePath = repo.get_path().resolve_relative_path('objects/' + objectToCorrupt.substring(0, 2) + "/" + objectToCorrupt.substring(2));

let iostream = loosePath.open_readwrite(null);
let info = iostream.query_info('standard::size', null);
let size = info.get_size();
let datain = Gio.DataInputStream.new(iostream.get_input_stream());
let dataout = Gio.DataOutputStream.new(iostream.get_output_stream());
let bytesToChange = 10;
let status = "";
var bytesChanged = {}
for (i = 0; i < bytesToChange; i++) {
    let byteOffsetToCorrupt;
    do {
        byteOffsetToCorrupt = GLib.random_int_range(0, size);
    } while (byteOffsetToCorrupt in bytesChanged);
    iostream.seek(byteOffsetToCorrupt, GLib.SeekType.SET, null);
    let inbyte = datain.read_byte(null);
    let outbyte = (inbyte + 1) % 255;
    dataout.put_byte(outbyte, null);
    bytesChanged[byteOffsetToCorrupt] = byteOffsetToCorrupt;
    status += "Changed byte offset " + byteOffsetToCorrupt + " from " + inbyte + " to " + outbyte + "\n";
}
dataout.flush(null);
iostream.close(null);

print(status);
let successFile = Gio.File.new_for_path('corrupted-status.txt');
successFile.replace_contents(status, null, false, 0, null);
