2 * tl-append: time-logger appending shell
3 * Copyright (C) 2023 Scott Worley <scottworley@scottworley.com>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 #define _POSIX_C_SOURCE 199309L
31 const int BUF_SIZE
= 1024;
33 const char PROMPT
[] = "\33[H" /* Move cursor 'home' */
34 "\33[J" /* Clear screen */
36 const char ACKNOWLEDGE
[] = "[OK]";
37 const char WAITING
[] = "[Waiting on lock...]\n";
38 const struct timespec ACKNOWLEDGE_DELAY
= {0, 300000000};
47 die("usage: tl-append [-i] [-C dir] [--no-fnctl-lock] [--no-flock-lock]");
50 conf_t
parse_command_line(int argc
, char *argv
[]) {
56 for (int i
= 1; i
< argc
; i
++) {
57 if (strcmp(argv
[i
], "-i") == 0 && isatty(2))
59 else if (strcmp(argv
[i
], "--no-fnctl-lock") == 0)
61 else if (strcmp(argv
[i
], "--no-flock-lock") == 0)
63 else if (strcmp(argv
[i
], "-C") == 0) {
65 die("-C requires a directory");
66 if (chdir(argv
[i
+ 1]) == -1)
67 die_err("Couldn't change directory");
76 static void read_line(conf_t
*conf
, char *buf
) {
77 if (conf
->interactive
)
78 if (fputs(PROMPT
, stderr
) == EOF
)
79 die("I/O error writing prompt");
80 if (fgets(buf
, BUF_SIZE
, stdin
) == NULL
) {
82 die("I/O error reading line");
84 buf
[0] = '\0'; /* Unclear if fgets does this already */
87 die("Unexpected error reading line");
91 static void write_line(const char *now
, FILE *f
, const char *line
) {
93 die_err("Error opening output file");
94 if (fputs(now
, f
) == EOF
)
95 die("Error writing to output file");
96 if (fputc(' ', f
) == EOF
)
97 die("Error writing to output file");
98 if (fputs(line
, f
) == EOF
)
99 die("Error writing to output file");
102 static void take_fcntl_lock(conf_t
*conf
, FILE *f
) {
103 if (!conf
->fcntl_lock
)
106 lock
.l_type
= F_WRLCK
;
107 lock
.l_whence
= SEEK_SET
;
112 die_err("Couldn't get file descriptor for locking");
113 if (fcntl(fd
, F_SETLK
, &lock
) == 0)
115 if (errno
!= EACCES
&& errno
!= EAGAIN
)
116 die_err("Couldn't take fcntl lock");
117 if (fputs(WAITING
, stderr
) == EOF
)
118 die("Error writing waiting message");
119 if (fcntl(fd
, F_SETLKW
, &lock
) == 0)
121 die_err("Couldn't take fcntl lock");
124 static void take_flock_lock(conf_t
*conf
, FILE *f
) {
125 if (!conf
->flock_lock
)
129 die_err("Couldn't get file descriptor for locking");
130 if (flock(fd
, LOCK_EX
| LOCK_NB
) == 0)
132 if (errno
!= EWOULDBLOCK
)
133 die_err("Couldn't take flock lock");
134 if (fputs(WAITING
, stderr
) == EOF
)
135 die("Error writing waiting message");
136 if (flock(fd
, LOCK_EX
) == 0)
138 die_err("Couldn't take flock lock");
141 static void take_lock(conf_t
*conf
, FILE *f
) {
142 take_fcntl_lock(conf
, f
);
143 take_flock_lock(conf
, f
);
146 static void write_acknowledgment(conf_t
*conf
) {
147 if (conf
->interactive
) {
148 if (fputs(ACKNOWLEDGE
, stderr
) == EOF
)
149 die("Error writing acknowledgment");
150 if (nanosleep(&ACKNOWLEDGE_DELAY
, NULL
) == -1 && errno
!= EINTR
)
151 die_err("Error sleeping");
155 static void lock_and_write_line(conf_t
*conf
, const char *line
) {
156 char *now
= encode_time(time(NULL
));
157 FILE *f
= fopen(FILENAME
, "a");
160 write_line(now
, f
, line
);
163 die_err("Error closing output file");
166 write_acknowledgment(conf
);
169 int main(int argc
, char *argv
[]) {
170 conf_t conf
= parse_command_line(argc
, argv
);
172 for (read_line(&conf
, buf
); buf
[0]; read_line(&conf
, buf
)) {
173 lock_and_write_line(&conf
, buf
);