<commit_before>/*
 * Record Linux devices and their ancestors from sysfs/udev.
 * All attributes and properties are included, non-ASCII ones get printed in hex.
 * The record is written to the standard output.
 *
 * Copyright (C) 2013 Canonical Ltd.
 * Author: <NAME> <<EMAIL>>
 *
 * umockdev 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.
 *
 * umockdev 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 program; If not, see <http://www.gnu.org/licenses/>.
 */

using UMockdevUtils;

static void
exit_error(string message, ...)
{
    stderr.vprintf(message, va_list());
    stderr.puts("\n");
    Process.exit(1);
}

static void
devices_from_dir (string dir, ref GenericArray<string> devs)
{
    Dir d;
    try {
        d = Dir.open(dir);
    } catch (FileError e) {
        return;
    }

    bool has_uevent = false;
    bool has_subsystem = false;
    string entry;
    while ((entry = d.read_name())!= null) {
        if (entry == "uevent")
            has_uevent = true;
        else if (entry == "subsystem")
            has_subsystem = true;
        else {
            var p = Path.build_filename(dir, entry);
            Posix.Stat st;
            if (Posix.lstat(p, out st) == 0 && Posix.S_ISDIR(st.st_mode) &&
                !Posix.S_ISLNK(st.st_mode))
                devices_from_dir(p, ref devs);
        }
    }
    if (has_uevent && has_subsystem)
        devs.add(dir);
}

[CCode (array_length=false, array_null_terminated=true)]
static string[]
all_devices ()
{
    var devs = new GenericArray<string>();
    devices_from_dir("/sys/devices", ref devs);
    return devs.data;
}

// If dev is a block or character device, convert it to a sysfs path.
static string
resolve (string dev)
{
    Posix.Stat st;
    if (Posix.stat(dev, out st) != 0)
        exit_error("Cannot access device %s: %s", dev, strerror(errno));

    uint maj = Posix.major(st.st_rdev);
    uint min = Posix.minor(st.st_rdev);

    string link;
    // character device?
    if (Posix.S_ISCHR(st.st_mode))
        link = "/sys/dev/char/%u:%u".printf(maj, min);
    else if (Posix.S_ISBLK(st.st_mode))
        link = "/sys/dev/block/%u:%u".printf(maj, min);
    else
        link = dev;

    string real = Posix.realpath(link);
    // FIXME: does not work under testbed for test suite
    //assert(real != null);
    if (real == null)
        real = link;

    if (!FileUtils.test(Path.build_filename(real, "uevent"), FileTest.EXISTS))
        exit_error("Invalid device %s, has no uevent attribute", real);

    return real;
}

static string?
parent(string dev)
{
    string p = Path.get_dirname(dev);
    if (!p.has_prefix("/sys"))
        return null;
    if (FileUtils.test(Path.build_filename(p, "uevent"), FileTest.EXISTS) &&
        FileUtils.test(Path.build_filename(p, "subsystem"), FileTest.EXISTS))
        return p;
    // we might have intermediate directories without uevent, so try the next
    // higher one
    return parent(p);
}

static string
format_hex(uint8[] bytes, int len=-1)
{
    if (len < 0)
        len = bytes.length;
    var result = new StringBuilder();
    for (int i = 0; i < len; ++i)
        result.append_printf("%02X", bytes[i]);
    return result.str;
}

static void
write_attr(string name, uint8[] val)
{
    // check if it's text or binary
    string strval = (string) val;
    if (val.length == strval.length && strval.validate())
        stdout.printf("A: %s=%s", name, strval.escape(""));
    else
        stdout.printf("H: %s=%s", name, format_hex(val));
    stdout.putc('\n');
}

// Return contents of device node, if applicable.
static string
dev_contents(string dev)
{
    Posix.Stat st;

    if (Posix.lstat(dev, out st) != 0)
        return "";

    // only attempt this for safe devices; USB for now
    if (!Posix.S_ISCHR(st.st_mode) || Posix.major(st.st_rdev) != 189)
        return "";

    // read the first KiB
    int fd = Posix.open(dev, Posix.O_RDONLY|Posix.O_NONBLOCK);
    if (fd < 0)
        return "";

    // read the first KiB; ignore if it's bigger
    uint8[] buffer = new uint8[1025];
    ssize_t len = Posix.read(fd, buffer, 1025);
    string result = "";
    if (len > 0 && len <= 1024)
        result = "=" + format_hex(buffer, (int) len);
    Posix.close(fd);
    return result;
}

static void
print_device_attributes(string devpath, string subdir)
{
    Dir d;
    var attr_dir = Path.build_filename(devpath, subdir);
    try {
        d = Dir.open(attr_dir);
    } catch (Error e) {
        if (subdir == "") {
            exit_error("Cannot open directory %s: %s", attr_dir, e.message);
        } else {
            // we ignore this on subdirs, some might be transient or
            // inaccessible
            debug("Cannot open directory %s: %s", attr_dir, e.message);
        }
        return;
    }

    var attributes = new List<string>();
    string entry;
    // filter out the uninteresting attributes, sort the others
    while ((entry = d.read_name()) != null)
        if (entry != "subsystem" && entry != "uevent")
            attributes.append(entry);
        else {
            // don't look into subdirs which are devices by themselves
            if (subdir != "")
                return;
        }
    attributes.sort(strcmp);

    foreach (var attr in attributes) {
        string attr_path = Path.build_filename(attr_dir, attr);
        string attr_name = Path.build_filename(subdir, attr);
        if (FileUtils.test(attr_path, FileTest.IS_SYMLINK)) {
            try {
                stdout.printf("L: %s=%s\n", attr_name, FileUtils.read_link(attr_path));
            } catch (Error e) {
                exit_error("Cannot read link %s: %s", attr_path, e.message);
            }
        } else if (FileUtils.test(attr_path, FileTest.IS_REGULAR)) {
            uint8[] contents;
            try {
                FileUtils.get_data(attr_path, out contents);
                write_attr(attr_name, contents);
            } catch (FileError e) {} // some attributes are EACCES, or "no such device", etc.
        } else if (FileUtils.test(attr_path, FileTest.IS_DIR)) {
            print_device_attributes(devpath, attr);
        }
    }
}

static void
record_device(string dev)
{
    debug("recording device %s", dev);

    // we start with udevadm dump of this device, which will include all udev properties
    string u_out, u_err;
    int exitcode;
    try {
        Process.spawn_sync(null,
                           {"udevadm", "info", "--query=all", "--path", dev},
                           null,
                           SpawnFlags.SEARCH_PATH,
                           null,
                           out u_out,
                           out u_err,
                           out exitcode);
        if (exitcode != 0)
            throw new SpawnError.FAILED("udevadm exited with code %i\n%s".printf(exitcode, u_err));
    } catch (Error e) {
        exit_error("Cannot call udevadm: %s", e.message);
    }

    foreach (string line in u_out.split("\n")) {
        // filter out redundant/uninteresting properties and link priority
        if (line.length == 0 || line.has_prefix("E: DEVPATH=") ||
            line.has_prefix("E: UDEV_LOG=") || line.has_prefix("E: USEC_INITIALIZED=") ||
            line.has_prefix("L: "))
            continue;

        if (line.has_prefix("N: ")) {
            line = line + dev_contents("/dev/" + line.substring(3).chomp());
        }
        stdout.puts(line);
        stdout.putc('\n');
    }

    // work around kernel crash, skip reading attributes for Tegra stuff (LP #1190225)
    if (dev.contains("tegra")) {
        stdout.putc('\n');
        return;
    }

    // now append all attributes
    print_device_attributes(dev, "");
    stdout.putc('\n');
}

static void
dump_devices(string[] devices)
{
    // process arguments parentwards first
    var seen = new GenericSet<string>(str_hash, str_equal);
    foreach (string device in devices) {
        while (device != null) {
            if (!seen.contains(device)) {
                seen.add(device.dup());
                record_device(device);
            }
            device = parent(device);
        }
    }
}

// split a devname=filename argument into a device number and a file name
static void
split_devfile_arg(string arg, out string dev, out string devnum, out string fname)
{
    string[] parts = arg.split ("=", 2); // devname, ioctlfilename
    if (parts.length != 2)
        exit_error("--ioctl argument must be devname=filename");
    dev = parts[0];
    fname = parts[1];

    // build device major/minor
    Posix.Stat st;
    if (Posix.stat(dev, out st) != 0)
        exit_error("Cannot access device %s: %s", dev, strerror(errno));

    if (Posix.S_ISCHR(st.st_mode) || Posix.S_ISBLK(st.st_mode)) {
        // if we have a device node, get devnum from stat
        devnum = Posix.major(st.st_rdev).to_string() + ":" + Posix.minor(st.st_rdev).to_string();
    } else if (Posix.S_ISSOCK(st.st_mode)) {
        // Unix sockets are passed by name
        devnum = dev;
    } else {
        // otherwise we assume that we have a sysfs device, resolve via dev attribute
        try {
            FileUtils.get_contents(Path.build_filename(dev, "dev"), out devnum);
        } catch (Error e) {
            exit_error("Cannot open %s/dev: %s", dev, e.message);
        }
    }
}

// Record ioctls for given device into outfile
static UMockdev.IoctlBase
record_ioctl(string root_dir, string arg)
{
    UMockdev.IoctlBase handler;
    string dev, devnum, outfile;
    split_devfile_arg(arg, out dev, out devnum, out outfile);

    handler = new UMockdev.IoctlTreeRecorder(null, dev, outfile);

    string sockpath = Path.build_filename(root_dir, "ioctl", dev);
    handler.register_path(dev, sockpath);

    return handler;
}

// Record reads/writes for given device into outfile
static uint record_script_counter = 0;
static void
record_script(string arg, string format)
{
    string dev, devnum, outfile;
    split_devfile_arg(arg, out dev, out devnum, out outfile);
    string c = record_script_counter.to_string();

    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_FILE_" + c, outfile, true);
    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_DEV_" + c, devnum, true);
    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_DEVICE_PATH_" + c, dev, true);
    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_FORMAT_" + c, format, true);

    record_script_counter++;
}

[CCode (array_length=false, array_null_terminated=true)]
static string[] opt_devices;
static bool opt_all = false;
static string? opt_ioctl = null;
[CCode (array_length=false, array_null_terminated=true)]
static string[] opt_script;
[CCode (array_length=false, array_null_terminated=true)]
static string[] opt_evemu_events;<commit_msg>record: Record SPI devices using the SPI recorder<commit_after>/*
 * Record Linux devices and their ancestors from sysfs/udev.
 * All attributes and properties are included, non-ASCII ones get printed in hex.
 * The record is written to the standard output.
 *
 * Copyright (C) 2013 Canonical Ltd.
 * Author: <NAME> <<EMAIL>>
 *
 * umockdev 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.
 *
 * umockdev 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 program; If not, see <http://www.gnu.org/licenses/>.
 */

using UMockdevUtils;

static void
exit_error(string message, ...)
{
    stderr.vprintf(message, va_list());
    stderr.puts("\n");
    Process.exit(1);
}

static void
devices_from_dir (string dir, ref GenericArray<string> devs)
{
    Dir d;
    try {
        d = Dir.open(dir);
    } catch (FileError e) {
        return;
    }

    bool has_uevent = false;
    bool has_subsystem = false;
    string entry;
    while ((entry = d.read_name())!= null) {
        if (entry == "uevent")
            has_uevent = true;
        else if (entry == "subsystem")
            has_subsystem = true;
        else {
            var p = Path.build_filename(dir, entry);
            Posix.Stat st;
            if (Posix.lstat(p, out st) == 0 && Posix.S_ISDIR(st.st_mode) &&
                !Posix.S_ISLNK(st.st_mode))
                devices_from_dir(p, ref devs);
        }
    }
    if (has_uevent && has_subsystem)
        devs.add(dir);
}

[CCode (array_length=false, array_null_terminated=true)]
static string[]
all_devices ()
{
    var devs = new GenericArray<string>();
    devices_from_dir("/sys/devices", ref devs);
    return devs.data;
}

// If dev is a block or character device, convert it to a sysfs path.
static string
resolve (string dev)
{
    Posix.Stat st;
    if (Posix.stat(dev, out st) != 0)
        exit_error("Cannot access device %s: %s", dev, strerror(errno));

    uint maj = Posix.major(st.st_rdev);
    uint min = Posix.minor(st.st_rdev);

    string link;
    // character device?
    if (Posix.S_ISCHR(st.st_mode))
        link = "/sys/dev/char/%u:%u".printf(maj, min);
    else if (Posix.S_ISBLK(st.st_mode))
        link = "/sys/dev/block/%u:%u".printf(maj, min);
    else
        link = dev;

    string real = Posix.realpath(link);
    // FIXME: does not work under testbed for test suite
    //assert(real != null);
    if (real == null)
        real = link;

    if (!FileUtils.test(Path.build_filename(real, "uevent"), FileTest.EXISTS))
        exit_error("Invalid device %s, has no uevent attribute", real);

    return real;
}

static string?
parent(string dev)
{
    string p = Path.get_dirname(dev);
    if (!p.has_prefix("/sys"))
        return null;
    if (FileUtils.test(Path.build_filename(p, "uevent"), FileTest.EXISTS) &&
        FileUtils.test(Path.build_filename(p, "subsystem"), FileTest.EXISTS))
        return p;
    // we might have intermediate directories without uevent, so try the next
    // higher one
    return parent(p);
}

static string
format_hex(uint8[] bytes, int len=-1)
{
    if (len < 0)
        len = bytes.length;
    var result = new StringBuilder();
    for (int i = 0; i < len; ++i)
        result.append_printf("%02X", bytes[i]);
    return result.str;
}

static void
write_attr(string name, uint8[] val)
{
    // check if it's text or binary
    string strval = (string) val;
    if (val.length == strval.length && strval.validate())
        stdout.printf("A: %s=%s", name, strval.escape(""));
    else
        stdout.printf("H: %s=%s", name, format_hex(val));
    stdout.putc('\n');
}

// Return contents of device node, if applicable.
static string
dev_contents(string dev)
{
    Posix.Stat st;

    if (Posix.lstat(dev, out st) != 0)
        return "";

    // only attempt this for safe devices; USB for now
    if (!Posix.S_ISCHR(st.st_mode) || Posix.major(st.st_rdev) != 189)
        return "";

    // read the first KiB
    int fd = Posix.open(dev, Posix.O_RDONLY|Posix.O_NONBLOCK);
    if (fd < 0)
        return "";

    // read the first KiB; ignore if it's bigger
    uint8[] buffer = new uint8[1025];
    ssize_t len = Posix.read(fd, buffer, 1025);
    string result = "";
    if (len > 0 && len <= 1024)
        result = "=" + format_hex(buffer, (int) len);
    Posix.close(fd);
    return result;
}

static void
print_device_attributes(string devpath, string subdir)
{
    Dir d;
    var attr_dir = Path.build_filename(devpath, subdir);
    try {
        d = Dir.open(attr_dir);
    } catch (Error e) {
        if (subdir == "") {
            exit_error("Cannot open directory %s: %s", attr_dir, e.message);
        } else {
            // we ignore this on subdirs, some might be transient or
            // inaccessible
            debug("Cannot open directory %s: %s", attr_dir, e.message);
        }
        return;
    }

    var attributes = new List<string>();
    string entry;
    // filter out the uninteresting attributes, sort the others
    while ((entry = d.read_name()) != null)
        if (entry != "subsystem" && entry != "uevent")
            attributes.append(entry);
        else {
            // don't look into subdirs which are devices by themselves
            if (subdir != "")
                return;
        }
    attributes.sort(strcmp);

    foreach (var attr in attributes) {
        string attr_path = Path.build_filename(attr_dir, attr);
        string attr_name = Path.build_filename(subdir, attr);
        if (FileUtils.test(attr_path, FileTest.IS_SYMLINK)) {
            try {
                stdout.printf("L: %s=%s\n", attr_name, FileUtils.read_link(attr_path));
            } catch (Error e) {
                exit_error("Cannot read link %s: %s", attr_path, e.message);
            }
        } else if (FileUtils.test(attr_path, FileTest.IS_REGULAR)) {
            uint8[] contents;
            try {
                FileUtils.get_data(attr_path, out contents);
                write_attr(attr_name, contents);
            } catch (FileError e) {} // some attributes are EACCES, or "no such device", etc.
        } else if (FileUtils.test(attr_path, FileTest.IS_DIR)) {
            print_device_attributes(devpath, attr);
        }
    }
}

static void
record_device(string dev)
{
    debug("recording device %s", dev);

    // we start with udevadm dump of this device, which will include all udev properties
    string u_out, u_err;
    int exitcode;
    try {
        Process.spawn_sync(null,
                           {"udevadm", "info", "--query=all", "--path", dev},
                           null,
                           SpawnFlags.SEARCH_PATH,
                           null,
                           out u_out,
                           out u_err,
                           out exitcode);
        if (exitcode != 0)
            throw new SpawnError.FAILED("udevadm exited with code %i\n%s".printf(exitcode, u_err));
    } catch (Error e) {
        exit_error("Cannot call udevadm: %s", e.message);
    }

    foreach (string line in u_out.split("\n")) {
        // filter out redundant/uninteresting properties and link priority
        if (line.length == 0 || line.has_prefix("E: DEVPATH=") ||
            line.has_prefix("E: UDEV_LOG=") || line.has_prefix("E: USEC_INITIALIZED=") ||
            line.has_prefix("L: "))
            continue;

        if (line.has_prefix("N: ")) {
            line = line + dev_contents("/dev/" + line.substring(3).chomp());
        }
        stdout.puts(line);
        stdout.putc('\n');
    }

    // work around kernel crash, skip reading attributes for Tegra stuff (LP #1190225)
    if (dev.contains("tegra")) {
        stdout.putc('\n');
        return;
    }

    // now append all attributes
    print_device_attributes(dev, "");
    stdout.putc('\n');
}

static void
dump_devices(string[] devices)
{
    // process arguments parentwards first
    var seen = new GenericSet<string>(str_hash, str_equal);
    foreach (string device in devices) {
        while (device != null) {
            if (!seen.contains(device)) {
                seen.add(device.dup());
                record_device(device);
            }
            device = parent(device);
        }
    }
}

// split a devname=filename argument into a device number and a file name
static void
split_devfile_arg(string arg, out string dev, out string devnum, out bool is_block, out string fname)
{
    string[] parts = arg.split ("=", 2); // devname, ioctlfilename
    if (parts.length != 2)
        exit_error("--ioctl argument must be devname=filename");
    dev = parts[0];
    fname = parts[1];

    // build device major/minor
    Posix.Stat st;
    if (Posix.stat(dev, out st) != 0)
        exit_error("Cannot access device %s: %s", dev, strerror(errno));

    is_block = Posix.S_ISBLK(st.st_mode);
    if (Posix.S_ISCHR(st.st_mode) || Posix.S_ISBLK(st.st_mode)) {
        // if we have a device node, get devnum from stat
        devnum = Posix.major(st.st_rdev).to_string() + ":" + Posix.minor(st.st_rdev).to_string();
    } else if (Posix.S_ISSOCK(st.st_mode)) {
        // Unix sockets are passed by name
        devnum = dev;
    } else {
        // otherwise we assume that we have a sysfs device, resolve via dev attribute
        try {
            FileUtils.get_contents(Path.build_filename(dev, "dev"), out devnum);
        } catch (Error e) {
            exit_error("Cannot open %s/dev: %s", dev, e.message);
        }
    }
}

// Record ioctls for given device into outfile
static UMockdev.IoctlBase
record_ioctl(string root_dir, string arg)
{
    UMockdev.IoctlBase handler;
    string dev, devnum, outfile;
    bool is_block;
    split_devfile_arg(arg, out dev, out devnum, out is_block, out outfile);

    /* SPI: major 153, character device */
    if (!is_block && devnum.has_prefix("153:"))
        handler = new UMockdev.IoctlSpiRecorder(null, dev, outfile);
    else
        handler = new UMockdev.IoctlTreeRecorder(null, dev, outfile);

    string sockpath = Path.build_filename(root_dir, "ioctl", dev);
    handler.register_path(dev, sockpath);

    return handler;
}

// Record reads/writes for given device into outfile
static uint record_script_counter = 0;
static void
record_script(string arg, string format)
{
    string dev, devnum, outfile;
    bool is_block;
    split_devfile_arg(arg, out dev, out devnum, out is_block, out outfile);
    string c = record_script_counter.to_string();

    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_FILE_" + c, outfile, true);
    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_DEV_" + c, devnum, true);
    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_DEVICE_PATH_" + c, dev, true);
    Environment.set_variable("UMOCKDEV_SCRIPT_RECORD_FORMAT_" + c, format, true);

    record_script_counter++;
}

[CCode (array_length=false, array_null_terminated=true)]
static string[] opt_devices;
static bool opt_all = false;
static string? opt_ioctl = null;
[CCode (array_length=false, array_null_terminated=true)]
static string[] opt_script;
[CCode (array_length=false, array_null_terminated=true)]
static string[] opt_evemu_events;
{'fraction_non_alphanumeric': 0.12289962171197326, 'fraction_numerical': 0.004926541743643881, 'mean_word_length': 2.5313762037899967, 'pattern_counts': {'":': 2, '<': 27, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1970525', 'n_tokens_mistral': 7323, 'n_tokens_neox': 7117, 'n_words': 2027}