PaulJuliusMartinez / jless

jless is a command-line JSON viewer designed for reading, exploring, and searching through JSON data.
https://jless.io
MIT License
4.61k stars 87 forks source link

not sure: `jless` not working in process substitution through named pipe from Python program #162

Closed hacker-DOM closed 1 month ago

hacker-DOM commented 1 month ago

I'm not the best with these things so don't have much more to contribute, but logging it here fwiw.

I think the following command (zsh)

❯ python -c "import sys; print('{\"a\": 5}',file=sys.stderr)" 2> >(jless)                               
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 5, kind: Uncategorized, message: "Input/output error" }', src/main.rs:68:45
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

does the following:

  1. runs jless in a subshell
  2. creates a named pipe referenced by a file descriptor that points to the subshell's standard input
  3. allows you to use the file descriptor to send data to the named pipe (to the subshell)
  4. in the command above, we are sending standard error, so i would expect it to Just Work
Instead we get (RUST_BACKTRACE=full) ```zsh ❯ export RUST_BACKTRACE=full; python -c "import sys; print('{\"a\": 5}',file=sys.stderr)" 2> >(jless) thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 5, kind: Uncategorized, message: "Input/output error" }', src/main.rs:68:45 stack backtrace: 0: 0x102e10f10 - ::fmt::hf56058dac04a8100 1: 0x102e40a0c - core::fmt::write::h887ee594c50d2f6b 2: 0x102e1e6c0 - std::io::Write::write_fmt::h8e068fbe7ba944b7 3: 0x102e10d20 - std::sys_common::backtrace::print::hefee1a8be582057a 4: 0x102e1bc1c - std::panicking::default_hook::{{closure}}::h7ba479390a999ae5 5: 0x102e1b8e4 - std::panicking::default_hook::hc4ff20421fd3aa8b 6: 0x102e1c22c - std::panicking::rust_panic_with_hook::h97e5266e8ce2f24f 7: 0x102e111fc - std::panicking::begin_panic_handler::{{closure}}::h5c38d4c71a65b53e 8: 0x102e11008 - std::sys_common::backtrace::__rust_end_short_backtrace::h9c79ccffe3575672 9: 0x102e1be48 - _rust_begin_unwind 10: 0x102e4fb94 - core::panicking::panic_fmt::h8d86c61b68da2636 11: 0x102e4fb1c - core::result::unwrap_failed::h2eeab09a9c5ad8bf 12: 0x102d1a3e0 - jless::main::h34bd4558100ce200 13: 0x102d16f64 - std::sys_common::backtrace::__rust_begin_short_backtrace::h25d0e48c36500bc3 14: 0x102d16f7c - std::rt::lang_start::{{closure}}::h7f54ba6ccd5e4470 15: 0x102e1bd6c - std::panicking::try::hd27950ade386a4c1 16: 0x102e25944 - std::rt::lang_start_internal::h4c832f79c4cb8516 17: 0x102d1a874 - _main ```

Fun fact: this does work:

❯ (echo hi; echo '{"a": 5}' >&2) 2> >(jless)                                                         
hi

(it prints hi to console and opens jless with the json).

hacker-DOM commented 1 month ago

Solution (I do not know why it works):

  1. Create a subshell (still doesn't work ❌)
❯ (python -c "import sys; print('{\"a\": 5}',file=sys.stderr)") 2> >(jless)
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 5, kind: Uncategorized, message: "Input/output error" }', src/main.rs:68:45
  1. Add an echo (works 🎉 (I can't print it here but it correctly opens jless))
❯ (python -c "import sys; print('{\"a\": 5}',file=sys.stderr)"; echo) 2> >(jless)
hacker-DOM commented 1 month ago

Closing this in case it's not relevant, but I think the expected behavior is still that it would work even without the echo. One can test with a script

filedescriptor.py

import sys
import logging

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')

def log_first_last_stdin():
    first_line = None
    last_line = None

    # Read stdin line by line
    for line in sys.stdin:
        if first_line is None:
            first_line = line.strip()
            logging.info(f"First stdin line: {first_line}")
        last_line = line.strip()

    if last_line is not None:
        logging.info(f"Last stdin line: {last_line}")

if __name__ == "__main__":
    log_first_last_stdin()

that both versions yield the same stdin:

❯ (echo hi; echo '{"a": 5}' >&2) 2> >(python filedescriptors.py)
hi
2024-06-08 10:15:48,954 - First stdin line: {"a": 5}
2024-06-08 10:15:48,954 - Last stdin line: {"a": 5}
❯ python -c "import sys; print('{"a": 5}',file=sys.stderr)" 2> >(python filedescriptors.py)
2024-06-08 10:16:32,563 - First stdin line: {a: 5}                                                                           
2024-06-08 10:16:32,563 - Last stdin line: {a: 5}

so it does indeed seem to be a problem in jless.

It seems to occur in these lines:

https://github.com/PaulJuliusMartinez/jless/blob/e6cdef719c7319020391d6bbf838ab272ce44cf0/src/main.rs#L68

and then either:

https://github.com/redox-os/termion/blob/42491e843cfeb809b39b19f977fbb8b4cc383852/src/raw.rs#L103, or https://github.com/redox-os/termion/blob/42491e843cfeb809b39b19f977fbb8b4cc383852/src/raw.rs#L108