The prior pages established the building blocks: read() supplies raw
bytes in chunks; static char *leftover preserves bytes between calls;
tci_strndup handles line extraction. This page assembles them into
tci_getline — and introduces one more private helper.
Helper: strjoin
tci_getline needs to concatenate leftover with each new chunk from
read(). This is a single use-case in a single file — there is no
other caller in libtci, and the operation has no libc equivalent to
mirror. A function used in exactly one place with no standard
counterpart stays static. It does not belong in libtci, and it
does not yet belong in a library. Add it as a private helper in
tci_getline.c:
static char *strjoin(const char *s1, const char *s2)
{
char *result;
size_t len; /* combined length of both strings */
size_t i;
size_t j;
if (!s1 || !s2)
return (NULL);
len = tci_strlen(s1) + tci_strlen(s2);
result = tci_calloc(len + 1, 1); /* +1 for null terminator */
if (!result)
return (NULL);
i = 0;
while (s1[i]) {
result[i] = s1[i];
i++;
}
j = 0;
while (s2[j]) { /* append s2 after s1 */
result[i + j] = s2[j];
j++;
}
return (result); /* caller owns this allocation */
}When a helper like this starts appearing in multiple files, or proves useful beyond its original context, that is the moment to promote it to a library with its own name and prefix.
Helper: extract_line
When tci_strchr(leftover, '\n') finds a newline, extract_line splits
leftover at that position. It returns the line (up to and including
the '\n') and updates leftover to whatever comes after:
static char *extract_line(char **leftover, char *nl)
{
char *line;
char *remainder;
line = tci_strndup(*leftover, nl - *leftover + 1); /* up to and including '\n' */
remainder = tci_strdup(nl + 1); /* everything after the '\n' */
free(*leftover); /* old leftover is replaced */
*leftover = remainder;
return (line); /* caller owns this allocation */
}nl - *leftover + 1 is the length of the prefix including the '\n'.
The remainder starts at nl + 1 — the character after the newline.
Helper: flush_leftover
When read() returns 0 or −1, there is no more data. If leftover is
non-empty it is the last line — a file that ends without '\n'. Return
it as-is; the caller gets the string and the allocation:
static char *flush_leftover(char **leftover)
{
char *last;
if (!*leftover || !**leftover) { /* NULL or empty string — nothing left */
free(*leftover);
*leftover = NULL;
return (NULL);
}
last = *leftover; /* last line has no trailing '\n' */
*leftover = NULL; /* next call starts clean */
return (last); /* caller owns this allocation */
}!**leftover checks whether the string is empty — leftover points to
an empty "" after the last '\n' in a well-terminated file. In that
case there is no more content to return.
The main function
Replace the stub in tci_getline.c with the complete implementation:
#include "libtci.h"
#include <unistd.h> /* read */
#include <stdlib.h> /* free */
#ifndef BUFFER_SIZE
# define BUFFER_SIZE 32 /* fallback if not set via -D */
#endif
static char *leftover; /* persists between calls */
static char *strjoin(const char *s1, const char *s2);
static char *extract_line(char **leftover, char *nl);
static char *flush_leftover(char **leftover);
char *tci_getline(int fd)
{
char buf[BUFFER_SIZE + 1]; /* +1 for null terminator */
char *nl;
char *tmp;
ssize_t bytes;
if (!leftover)
leftover = tci_strdup(""); /* initialise on first call */
while (1) {
nl = tci_strchr(leftover, '\n');
if (nl)
return (extract_line(&leftover, nl)); /* line ready — no read needed */
bytes = read(fd, buf, BUFFER_SIZE);
if (bytes <= 0)
return (flush_leftover(&leftover)); /* EOF or error */
buf[bytes] = '\0'; /* read() does not null-terminate */
tmp = leftover;
leftover = strjoin(leftover, buf); /* accumulate new chunk */
free(tmp); /* free old leftover after joining */
}
}#ifndef BUFFER_SIZE provides a fallback if the file is compiled without
-D BUFFER_SIZE=N. The tester always passes an explicit value; the
fallback is a safety net for manual compilation.
The forward declarations above tci_getline let the compiler see each
helper's signature before the call sites inside tci_getline and
extract_line. Without them the compiler encounters an unknown identifier
and stops — the same constraint you saw with dispatch in
c02/02.
Run the tester
Build and run:
make re
bash test.shThe short file and long file suites should pass. Edge cases — empty file,
no trailing newline, BUFFER_SIZE=1 — are covered on the next page.