Skip to content

lib: use tmpfile when writing out bpfilter state#412

Open
etsal wants to merge 1 commit intofacebook:mainfrom
etsal:atomic-file-update
Open

lib: use tmpfile when writing out bpfilter state#412
etsal wants to merge 1 commit intofacebook:mainfrom
etsal:atomic-file-update

Conversation

@etsal
Copy link

@etsal etsal commented Feb 3, 2026

The bpfilter daemon updates persistent state by reopening the existing file, truncating it, then overwriting the state. If the daemon crashes during this process, when it restarts it will find either no data, or malformed data. Turn the state update atomic by instead writing the new persistent data to a temporary file, then moving it to the correct location.

@etsal etsal requested a review from qdeslandes as a code owner February 3, 2026 20:47
@meta-cla meta-cla bot added the cla signed label Feb 3, 2026
Comment on lines 119 to 121
if (ret >= len) {
return bf_err_r(-ENAMETOOLONG, "tmpfile name too long (%lu bytes)", ret);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No braces for a single statement.

static int bf_tmp_name(char *tmpname, size_t len, const char *path)
{
char template[] = "/bpfilter_XXXXXX";
char *tmp, *dir;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_cleanup_free_ char *tmp = NULL; for automatic cleanup.

}


static int bf_tmp_name(char *tmpname, size_t len, const char *path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static symbols are prefixed with _.

bf_assert(buf);

fd = open(path, O_TRUNC | O_CREAT | O_WRONLY, OPEN_MODE_644);
err = bf_tmp_name(tmpname, sizeof(tmpname), path);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bf_tmp_name does not abide by its contract, it creates a temporary file template name, not a temporary file name.

Additionally, it would be more convenient to have bf_get_tmpfile_fd(const char *base, char tmpfile_path[static PATH_MAX])

  • base: base path to create the tmp file into (after applying dirname), so we can pass path directly. The final tmpfile will be dirname(base)/"bpfilter.tmp.XXXXXX"
  • tmpfile_path: path to the new tmp file
  • Returns a file descriptor to the tmp file

fd = open(path, O_TRUNC | O_CREAT | O_WRONLY, OPEN_MODE_644);
err = bf_tmp_name(tmpname, sizeof(tmpname), path);
if (err < 0)
return bf_err_r(errno, "failed to get temporary file name");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bf_tmp_name doesn't set errno, but returns -errno on error.

return 0;
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra blank line.

The bpfilter daemon updates persistent state by reopening the existing
file, truncating it, then overwriting the state. If the daemon crashes
during this process, when it restarts it will find either no data, or
malformed data. Turn the state update atomic by instead writing the new
persistent data to a temporary file, then moving it to the correct
location.
@etsal etsal force-pushed the atomic-file-update branch from 7d7d019 to f423b83 Compare February 4, 2026 17:11
@etsal
Copy link
Author

etsal commented Feb 4, 2026

New version addressing the feedback.

Copy link
Contributor

@qdeslandes qdeslandes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few nits but otherwise should be OK. Also, please change the MR status when ready for a review: currently I see it as "changes requested", so I might overlook it.

static int _bf_get_tmpfile_fd(const char *base, char tmpfile_path[PATH_MAX])
{
char template[] = "/bpfilter.tmp.XXXXXX";
_cleanup_free_ char *tmp;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tmp = NULL to ensure we don't try to cleanup a garbage point if we exit early.

return 0;
}

static int _bf_get_tmpfile_fd(const char *base, char tmpfile_path[PATH_MAX])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[static PATH_MAX]


dir = dirname(tmp);

path_len = snprintf(tmpfile_path, PATH_MAX, "%s%s", dir, template);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to have a variable for template here: "%s/bpfilter.tmp.XXXXXX" should do it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants