Skip to content

Conversation

@dvorka
Copy link
Owner

@dvorka dvorka commented May 18, 2025

This PR adds ability of HSTR to read input from pipe - just for inspiration:

git log --pretty=format:%s | hstr
find | hstr
ps aux | hstr
cat /var/log/nginx/access.log | hstr
grep phrase . | hstr
cat data.csv | hstr
netstat -tulpn | hstr
kubectl logs mypod | hstr

Tasks:

@dvorka dvorka requested a review from Copilot May 18, 2025 19:14
@dvorka dvorka self-assigned this May 18, 2025
@dvorka dvorka linked an issue May 18, 2025 that may be closed by this pull request
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces stdin support for HSTR, allowing the utility to read input from a pipe and enable searching of piped data. Key changes include updating function signatures (e.g., fill_terminal_input and prioritized_history_create) to accept additional parameters such as file descriptors and history state, and modifying curses initialization to handle tty reading when input is piped.

Reviewed Changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/include/hstr_utils.h Updated fill_terminal_input signature to include file descriptor parameter.
src/include/hstr_history.h Added HISTORY_STATE parameter to prioritized_history_create.
src/include/hstr_curses.h Modified hstr_curses_start to accept a tty initialization flag and return a FILE*.
src/hstr_utils.c Adjusted fill_terminal_input implementation to use the new file descriptor parameter.
src/hstr_history.c Updated prioritized_history_create to support an external history state.
src/hstr_curses.c Updated curses initialization logic to conditionally use /dev/tty when reading from pipe.
src/hstr.c Updated main loop and interactive functions to support pipe input and pass the correct fd.
pad.xml, hstr.pro, build scripts Bumped version numbers and updated build scripts accordingly.
README.md Added package information.
Comments suppressed due to low confidence (1)

src/hstr.c:1728

  • The code closes stdin after processing piped history input, which may be problematic if further standard input operations are expected; double-check that this behavior is intended.
fclose(fp);

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

src/hstr_history.c:188

  • rawHistory is allocated with malloc and not zero-initialized. In the population loop, entries can be skipped (e.g., empty lines) without writing a value into rawHistory[rawOffset], leaving garbage pointers; later code assumes rawItems[i] is non-NULL (second pass in hstr_make_selection calls strstr(source[i], ...) without a NULL check), which can crash. Initialize rawHistory to NULL (e.g., calloc), and ensure skipped/ignored items are removed from the raw list and rawCount reflects only valid strings.
        HIST_ENTRY **historyList=historyState ? historyState->entries : history_list();
        char **rawHistory=malloc(sizeof(char*) * historyState->length);
        int rawOffset=historyState->length-1, rawTimestamps=0;
        char *line;
        int i;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (2)

.github/workflows/build_ubuntu.yml:14

  • The workflow runs on ubuntu-latest, but the install step is explicitly tailored to Ubuntu 24.04 packages. Since ubuntu-latest can move to a newer release and break package availability, consider pinning runs-on: ubuntu-24.04 (or adjusting the step to detect the distro version dynamically).
    src/hstr_history.c:192
  • rawHistory is allocated with malloc() and entries are only written for non-empty lines and timestamps. If historyList[i]->line is empty (which is plausible with piped stdin input), the loop continues without setting rawHistory[rawOffset], leaving an uninitialized pointer that later gets exposed via prioritizedHistory->rawItems/rawCount and can crash when accessed. Consider initializing rawHistory to NULL (e.g., calloc), setting rawHistory[rawOffset]=NULL before continue, and/or tracking rawCount based on actually populated entries (similar to the timestamp compaction).
        HIST_ENTRY **historyList=historyState ? historyState->entries : history_list();
        char **rawHistory=malloc(sizeof(char*) * historyState->length);
        int rawOffset=historyState->length-1, rawTimestamps=0;
        char *line;
        int i;
        for(i=0; i<historyState->length; i++, rawOffset--) {
            if(!historyList[i]->line || !strlen(historyList[i]->line)) {
                continue;
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1865 to +1868
if(hstr->readingFromPipe) {
free_history_state(historyState);
}

Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When reading from stdin (pipe mode), historyState is freed immediately after prioritized_history_create(), but prioritized_history_create() stores rawItems as pointers into historyState->entries[i]->line (no strdup). This leaves hstr->history->rawItems pointing to freed memory and can crash when rendering/selecting raw view. Keep historyState alive until after prioritized_history_destroy(), or make prioritized_history_create() deep-copy raw lines when an external historyState is provided and free them in prioritized_history_destroy().

Suggested change
if(hstr->readingFromPipe) {
free_history_state(historyState);
}

Copilot uses AI. Check for mistakes.
Comment on lines +1820 to +1861
FILE *fp = stdin;
if(fp == NULL) {
fprintf(stderr, "Error opening stdin\n");
free_history_state(historyState);
hstr_exit(EXIT_FAILURE);
}

char *line = NULL;
size_t len = 0;
ssize_t read;
historyState->length=0;
while ((read = getline(&line, &len, stdin)) != -1 && historyState->length < MAX_STDIN_ENTRIES) {
if (read > 0 && line[read-1] == '\n') {
line[read-1] = '\0';
}

historyState->entries[historyState->length] = malloc(sizeof(HIST_ENTRY));
if (!historyState->entries[historyState->length]) {
fprintf(stderr, "Failed to allocate memory for history entry\n");
free(line);
free_history_state(historyState);
hstr_exit(EXIT_FAILURE);
}

historyState->entries[historyState->length]->line = strdup(line);
if (!historyState->entries[historyState->length]->line) {
fprintf(stderr, "Failed to allocate memory for history line\n");
free(historyState->entries[historyState->length]);
free(line);
free_history_state(historyState);
hstr_exit(EXIT_FAILURE);
}

historyState->entries[historyState->length]->timestamp = NULL;
historyState->entries[historyState->length]->data = NULL;

historyState->length++;
}
free(line); // free(NULL) is safe per C standard

fclose(fp);
}
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid calling fclose(stdin) here. Closing the global stdin stream can have surprising side effects (and fp is always stdin), especially if other code/libraries read from stdin later. Prefer just reading until EOF without closing, and remove the dead fp == NULL check.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +44
if(ttyInit) {
SCREEN *screen;

tty_in = fopen("/dev/tty", "r+");
if (!tty_in) {
perror("Could not open /dev/tty prior to curses initialization");
return NULL;
}

screen = newterm(NULL, stdout, tty_in);
if (!screen) {
fprintf(stderr, "Error initializing curses when HSTR is reading from pipe");
fclose(tty_in);
return NULL;
}
set_term(screen);
} else {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the ttyInit path, newterm() creates a new SCREEN* but it’s stored only in a local variable and never released (e.g., via delscreen() after endwin()), which can leak ncurses resources. Consider storing the SCREEN* somewhere reachable by hstr_curses_stop() (or returning it alongside tty_in) and cleaning it up on shutdown.

Copilot uses AI. Check for mistakes.
Comment on lines +189 to +193
Browse output of any command interactively:

```bash
ls -la | hh
```
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new example uses hh, but earlier in the document the pipe example uses ls | hstr and other examples were switched to hstr. To avoid confusion, make the examples consistent (either use hstr everywhere, or consistently use hh and explicitly state it’s an alias).

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +57
<Program_Release_Month>5</Program_Release_Month>
<Program_Release_Day>9</Program_Release_Day>
<Program_Release_Year>2025</Program_Release_Year>
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PAD release date (2025-05-09) doesn’t match the changelog entry for v3.2.0 (2026-01-25). If v3.2.0 is being released now, update the PAD date to match; otherwise align the changelog/versioning so downstream consumers don’t get conflicting release metadata.

Suggested change
<Program_Release_Month>5</Program_Release_Month>
<Program_Release_Day>9</Program_Release_Day>
<Program_Release_Year>2025</Program_Release_Year>
<Program_Release_Month>1</Program_Release_Month>
<Program_Release_Day>25</Program_Release_Day>
<Program_Release_Year>2026</Program_Release_Year>

Copilot uses AI. Check for mistakes.
Comment on lines +164 to 171
if(!historyState) {
using_history();
if(!history_mgmt_load_history_file()) {
return NULL;
}

historyState=history_get_history_state();
}
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prioritized_history_create() allocates a HISTORY_STATE via history_get_history_state() when the caller passes NULL, but it is never freed anymore. Since history_get_history_state() returns heap memory, this introduces a leak on each call. Consider tracking ownership (e.g., bool ownsHistoryState = (historyState == NULL) before assignment) and free(historyState) before returning when it was internally allocated.

Copilot uses AI. Check for mistakes.

screen = newterm(NULL, stdout, tty_in);
if (!screen) {
fprintf(stderr, "Error initializing curses when HSTR is reading from pipe");
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message is missing a trailing newline, which can make it harder to read when mixed with subsequent output. Consider adding \n (and possibly including strerror(errno)/perror style details for consistency with the /dev/tty open failure path).

Suggested change
fprintf(stderr, "Error initializing curses when HSTR is reading from pipe");
fprintf(stderr, "Error initializing curses when HSTR is reading from pipe\n");

Copilot uses AI. Check for mistakes.
Comment on lines +8 to 11
Minor release bringing ability to read input
from the pipe (instead of history) and filter to print it using HSTR;
and interactive missing configuration detection for TIOCSTI enforcement.

Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The release notes for v3.2.0 are duplicated/redundant (the unbulleted paragraph repeats the bullet point). Consider keeping a single concise entry to avoid confusion in downstream packaging/changelog parsing.

Suggested change
Minor release bringing ability to read input
from the pipe (instead of history) and filter to print it using HSTR;
and interactive missing configuration detection for TIOCSTI enforcement.

Copilot uses AI. Check for mistakes.
Comment on lines +1801 to +1861
if(!isatty(fileno(stdin))) {
// load history (or alternative content) from stdin
hstr->readingFromPipe=true;
int MAX_STDIN_ENTRIES = 2000;

historyState = malloc(sizeof(HISTORY_STATE));
if (!historyState) {
fprintf(stderr, "Failed to allocate memory for history state\n");
hstr_exit(EXIT_FAILURE);
}
memset(historyState, 0, sizeof(HISTORY_STATE));

historyState->entries = malloc(sizeof(HIST_ENTRY*) * MAX_STDIN_ENTRIES);
if (!historyState->entries) {
fprintf(stderr, "Failed to allocate memory for history entries\n");
free_history_state(historyState);
hstr_exit(EXIT_FAILURE);
}

FILE *fp = stdin;
if(fp == NULL) {
fprintf(stderr, "Error opening stdin\n");
free_history_state(historyState);
hstr_exit(EXIT_FAILURE);
}

char *line = NULL;
size_t len = 0;
ssize_t read;
historyState->length=0;
while ((read = getline(&line, &len, stdin)) != -1 && historyState->length < MAX_STDIN_ENTRIES) {
if (read > 0 && line[read-1] == '\n') {
line[read-1] = '\0';
}

historyState->entries[historyState->length] = malloc(sizeof(HIST_ENTRY));
if (!historyState->entries[historyState->length]) {
fprintf(stderr, "Failed to allocate memory for history entry\n");
free(line);
free_history_state(historyState);
hstr_exit(EXIT_FAILURE);
}

historyState->entries[historyState->length]->line = strdup(line);
if (!historyState->entries[historyState->length]->line) {
fprintf(stderr, "Failed to allocate memory for history line\n");
free(historyState->entries[historyState->length]);
free(line);
free_history_state(historyState);
hstr_exit(EXIT_FAILURE);
}

historyState->entries[historyState->length]->timestamp = NULL;
historyState->entries[historyState->length]->data = NULL;

historyState->length++;
}
free(line); // free(NULL) is safe per C standard

fclose(fp);
}
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new stdin/pipe parsing path introduces substantial logic (getline loop, max-entry truncation, blank-line handling, and memory management) but doesn’t appear to be covered by the existing Unity test suite. Consider adding unit tests around parsing piped input into a history list (including blank lines and >MAX_STDIN_ENTRIES) to prevent regressions like dangling pointers/uninitialized entries.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve HSTR to read input from pipe

2 participants