src/cmd/acme: write dump file atomically

On a few occasions, I've found my acme.dump file overwritten
with zero-length content. I suspect it happens when the machine
is going down or under severe memory pressure, ending
with the file created but not written to.

Instead of opening and writing the file in place, create a temporary
file alongside the destination, write to that, then rename it over
the original. We take care to preserve the original permissions,
although ownership might change (that's probably not an issue in
practice). The underlying `mkstemp` call creates the temporary
file without any access rights for group or other, so there
shouldn't be any window of opportunity for an attacker to open it
before the permissions are changed.

Change-Id: Id0bc0e76c0acf5671c94b1b454cf23632b675586
This commit is contained in:
Roger Peppe
2026-03-25 10:26:54 +01:00
committed by Dan Cross
parent 4975178519
commit 31b61f0fca

View File

@@ -313,36 +313,18 @@ rowclean(Row *row)
return clean; return clean;
} }
void static void
rowdump(Row *row, char *file) rowdump1(Row *row, Biobuf *b)
{ {
int i, j, fd, m, n, start, dumped; int i, j, m, n, start, dumped;
uint q0, q1; uint q0, q1;
Biobuf *b;
char *buf, *a, *fontname, *fontfmt, *fontnamelo, *fontnamehi; char *buf, *a, *fontname, *fontfmt, *fontnamelo, *fontnamehi;
Rune *r; Rune *r;
Column *c; Column *c;
Window *w, *w1; Window *w, *w1;
Text *t; Text *t;
if(row->ncol == 0)
return;
buf = fbufalloc(); buf = fbufalloc();
if(file == nil){
if(home == nil){
warning(nil, "can't find file for dump: $home not defined\n");
goto Rescue;
}
sprint(buf, "%s/acme.dump", home);
file = buf;
}
fd = create(file, OWRITE, 0600);
if(fd < 0){
warning(nil, "can't open %s: %r\n", file);
goto Rescue;
}
b = emalloc(sizeof(Biobuf));
Binit(b, fd, OWRITE);
r = fbufalloc(); r = fbufalloc();
Bprint(b, "%s\n", wdir); Bprint(b, "%s\n", wdir);
Bprint(b, "%s\n", fontnames[0]); Bprint(b, "%s\n", fontnames[0]);
@@ -475,12 +457,57 @@ rowdump(Row *row, char *file)
Continue2:; Continue2:;
} }
} }
fbuffree(r);
fbuffree(buf);
}
void
rowdump(Row *row, char *file)
{
int fd;
Biobuf *b;
Dir d, *od;
char *buf, *tmp, *p;
if(row->ncol == 0)
return;
tmp = nil;
buf = nil;
if(file == nil){
if(home == nil){
warning(nil, "can't find file for dump: $home not defined\n");
goto Rescue;
}
buf = fbufalloc();
sprint(buf, "%s/acme.dump", home);
file = buf;
}
tmp = smprint("%s.XXXXXX", file);
fd = opentemp(tmp, OWRITE);
if(fd < 0){
warning(nil, "can't create temp file for %s: %r\n", file);
goto Rescue;
}
b = emalloc(sizeof(Biobuf));
Binit(b, fd, OWRITE);
rowdump1(row, b);
Bterm(b); Bterm(b);
close(fd); close(fd);
free(b); free(b);
fbuffree(r); nulldir(&d);
od = dirstat(file);
if(od != nil){
d.mode = od->mode;
free(od);
}
p = strrchr(file, '/');
d.name = p != nil ? p+1 : file;
if(dirwstat(tmp, &d) < 0){
warning(nil, "can't rename %s to %s: %r\n", tmp, file);
}
Rescue: Rescue:
free(tmp);
fbuffree(buf); fbuffree(buf);
} }