openlang

getline rows garbage, plus a leak

Pretty much stuck on this. Which bug first?

while (getline(&line, &linecap, fp) != -1) {
	char *name, *role, *port;

	lineno++;
	line[strcspn(line, "\r\n")] = '\0';
	if (line[0] == '#' || line[0] == '\0')
		continue;
	name = strtok(line, " \t");
	role = strtok(NULL, " \t");
	port = strtok(NULL, " \t");
	if (!name || !role) {
		fprintf(stderr, "%s:%d: expected 'name role [port]'\n",
			path, lineno);
		fclose(fp);
		return -1;
	}
	if (inv->count == inv->cap && grow(inv) != 0) {
		fclose(fp);
		return -1;
	}
	inv->hosts[inv->count].name = name;
	inv->hosts[inv->count].role = role;
	inv->hosts[inv->count].port = port ? atoi(port) : 22;
	inv->count++;
}
free(line);
fclose(fp);
return 0;

hosts.txt:

# prod inventory
web-01 web 8080
web-02 web 8080
db-01 db 5432
cache-01 cache

hosts_bad.txt:

web-01 web 8080
db-01
cache-01 cache 6379

The conversation continues 6

Follow the reasoning
  1. Permalink

    Garbage first, the leak second.

    name, role and port are pointers into line. strtok just drops NULs into that buffer and hands back addresses inside it. getline then reuses the same buffer for the next row. So every host ends up pointing at the same memory, and you see whatever the last line left there. If line gets realloc'd it's worse, because the old pointers dangle.

    Each row needs its own copy:

    inv->hosts[inv->count].name = strdup(name);
    inv->hosts[inv->count].role = strdup(role);

    Check for NULL, and free both when you free the inventory. That's the lifetime boundary: line lives for one iteration, the host strings live as long as inv.

    The leak: the early return -1 paths (bad row, failed grow) skip free(line). Only the happy path frees it. Either free it before each return or goto done to a single cleanup.

    getline allocates the buffer, so the caller owns it. The POSIX page spells that out: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html

Other sides of the same question

  1. The dangling pointer warning went away, the pointer did not

    Passing a name into a C++ library through a wrapper, and the first version was one line, CString::new(name).unwrap().as_ptr()…

    Replies 2Latest activity rustcpp
  2. Same sink call, a copy on one line and a move on the next

    Nim 2 decides copy or move per call site. Not per proc. Same argument, same proc. The first call copies, because a is still n…

    Replies 2Latest activity nim
  3. Interface references and object references to the same instance

    Hello, I have a CSV sink behind an interface, and the sink is created in a class-typed variable because I want Flush and Rows…

    Replies 1Latest activity object-pascal