aboutsummaryrefslogtreecommitdiff
path: root/src/block.c
blob: a6c919dfa07216bddd2f117f3e63defdd3e30775 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include "block.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "config.h"
#include "util.h"

block block_new(const char *const icon, const char *const command,
                const unsigned int interval, const int signal) {
    block block = {
        .icon = icon,
        .command = command,
        .interval = interval,
        .signal = signal,

        .output = {[0] = '\0'},
        .fork_pid = -1,
    };

    return block;
}

int block_init(block *const block) {
    if (pipe(block->pipe) != 0) {
        (void)fprintf(stderr,
                      "error: could not create a pipe for \"%s\" block\n",
                      block->command);
        return 1;
    }

    return 0;
}

int block_deinit(block *const block) {
    int status = close(block->pipe[READ_END]);
    status |= close(block->pipe[WRITE_END]);
    if (status != 0) {
        (void)fprintf(stderr, "error: could not close \"%s\" block's pipe\n",
                      block->command);
        return 1;
    }

    return 0;
}

int block_execute(block *const block, const uint8_t button) {
    // Ensure only one child process exists per block at an instance.
    if (block->fork_pid != -1) {
        return 0;
    }

    block->fork_pid = fork();
    if (block->fork_pid == -1) {
        (void)fprintf(
            stderr, "error: could not create a subprocess for \"%s\" block\n",
            block->command);
        return 1;
    }

    if (block->fork_pid == 0) {
        const int write_fd = block->pipe[WRITE_END];
        int status = close(block->pipe[READ_END]);

        if (button != 0) {
            char button_str[4];
            (void)snprintf(button_str, LEN(button_str), "%hhu", button);
            status |= setenv("BLOCK_BUTTON", button_str, 1);
        }

        const char null = '\0';
        if (status != 0) {
            (void)write(write_fd, &null, sizeof(null));
            exit(EXIT_FAILURE);
        }

        FILE *const file = popen(block->command, "r");
        if (file == NULL) {
            (void)write(write_fd, &null, sizeof(null));
            exit(EXIT_FAILURE);
        }

        // Ensure null-termination since fgets() will leave buffer untouched on
        // no output.
        char buffer[LEN(block->output)] = {[0] = null};
        (void)fgets(buffer, LEN(buffer), file);

        // Remove trailing newlines.
        const size_t length = strcspn(buffer, "\n");
        buffer[length] = null;

        // Exit if command execution failed or if file could not be closed.
        if (pclose(file) != 0) {
            (void)write(write_fd, &null, sizeof(null));
            exit(EXIT_FAILURE);
        }

        const size_t output_size =
            truncate_utf8_string(buffer, LEN(buffer), MAX_BLOCK_OUTPUT_LENGTH);
        (void)write(write_fd, buffer, output_size);

        exit(EXIT_SUCCESS);
    }

    return 0;
}

int block_update(block *const block) {
    char buffer[LEN(block->output)];

    const ssize_t bytes_read =
        read(block->pipe[READ_END], buffer, LEN(buffer));
    if (bytes_read == -1) {
        (void)fprintf(stderr,
                      "error: could not fetch output of \"%s\" block\n",
                      block->command);
        return 2;
    }

    // Collect exit-status of the subprocess to avoid zombification.
    int fork_status = 0;
    if (waitpid(block->fork_pid, &fork_status, 0) == -1) {
        (void)fprintf(stderr,
                      "error: could not obtain exit status for \"%s\" block\n",
                      block->command);
        return 2;
    }
    block->fork_pid = -1;

    if (fork_status != 0) {
        (void)fprintf(stderr,
                      "error: \"%s\" block exited with non-zero status\n",
                      block->command);
        return 1;
    }

    (void)strncpy(block->output, buffer, LEN(buffer));

    return 0;
}