ARCHIVE_ENTRY_LINKIFY(3)

πŸ“¦ version 15.1, in freebsd 14.5, freebsd 15.1

πŸ’– ARCHIVE_ENTRY_LINKIFY πŸ’–

okay bestie, we're doing the archive_entry_linkify(3) page today β€” it's a libarchive joint, and it's literally the whole hardlink-resolver friend group: archive_entry_linkresolver, archive_entry_linkresolver_new, archive_entry_linkresolver_set_strategy, archive_entry_linkresolver_free, and archive_entry_linkify. yes, that's a lot of names, but they're all on the same team, I promise 🫢


πŸ“š LIBRARY

Streaming Archive Library (libarchive, -larchive) β€” the icon herself πŸ’…


✨ SYNOPSIS ✨

#include <archive_entry.h>

struct archive_entry_linkresolver *
archive_entry_linkresolver_new(void);

void
archive_entry_linkresolver_set_strategy(struct archive_entry_linkresolver *resolver,
    int format);

void
archive_entry_linkresolver_free(struct archive_entry_linkresolver *resolver);

void
archive_entry_linkify(struct archive_entry_linkresolver *resolver,
    struct archive_entry **entry, struct archive_entry **sparse);

🌸 DESCRIPTION 🌸

so. if your program wants to make archives, you gotta deal with hardlinks. no way around it. and here's the tea: every archive format handles hardlinks its own way, and honestly? they're all a little different. the basic strategies are:

  1. Ignore hardlinks and store the body for each reference (old cpio, zip).
  2. Store the body the first time an inode is seen (ustar, pax).
  3. Store the body the last time an inode is seen (new cpio).

and the archive_entry_linkresolver functions are here to save you from that chaos β€” they give you one unified interface and do all the complicated behind-the-scenes stuff. main character energy, fr fr πŸ‘‘

okay, important: these functions assume that your archive_entry instances have valid nlinks, inode and device values. inode and device are what gets used to match entries up with each other. nlinks is what tells the resolver whether all the references have been found yet, and whether the internal references can be recycled. no valid values, no matching β€” that's the vibe check.

archive_entry_linkresolver_new() β€” allocates you a brand new link resolver. once you're done with it, free it with archive_entry_linkresolver_free(), which flushes all the deferred entries and frees the internal storage. clean up after yourself, bestie 🧹✨

archive_entry_linkresolver_set_strategy() β€” picks the optimal hardlink strategy for whatever format you're on. you can grab that format code from archive_format(3). and yes, you can call this more than once, but the recommendation is to flush all deferred entries first. lowkey just do that.

archive_entry_linkify() β€” this is the core, the moment, the whole point πŸŽ€. the entry() argument points to the archive_entry that should be written. depending on the strategy, one of these three things happens:

  1. For the simple archive formats, *entry is left unmodified and *sparse is set to NULL.
  2. For tar like archive formats, *sparse is set to NULL. If *entry is NULL, no action is taken. If the hardlink count of *entry is larger than 1 and the file type is a regular file or symbolic link, the internal list is searched for a matching inode. If such an inode is found, the link count is decremented and the file size of *entry is set to 0 to notify that no body should be written. If no such inode is found, a copy of the entry is added to the internal cache with a link count reduced by one.
  3. For new cpio like archive formats, a value for *entry of NULL is used to flush deferred entries. In that case *entry is set to an arbitrary deferred entry and the entry itself is removed from the internal list. If the internal list is empty, *entry is set to NULL. In either case, *sparse is set to NULL and the function returns. If the hardlink count of *entry is one or the file type is a directory or device, *sparse is set to NULL and no further action is taken. Otherwise, the internal list is searched for a matching inode. If such an inode is not found, the entry is added to the internal list, both *entry and *sparse are set to NULL and the function returns. If such an inode is found, the link count is decremented. If it remains larger than one, the existing entry on the internal list is swapped with *entry after retaining the link count. The existing entry is returned in *entry. If the link count reached one, the new entry is also removed from the internal list and returned in *sparse. Otherwise *sparse is set to NULL.

okay so that was a lot. here's the general usage, step by step, so you literally cannot mess it up πŸ’–

  1. For each new archive entry, call archive_entry_linkify().
  2. Keep in mind that the entries returned may have a size of 0 now.
  3. If *entry is not NULL, archive it.
  4. If *sparse is not NULL, archive it.
  5. After all entries have been written to disk, call archive_entry_linkify() with *entry set to NULL and archive the returned entry as long as it is not NULL.
🚨 bestie heads up: those returned entries can have a size of 0 now. that's not a bug, that's the whole point. do not panic, do not write a body. πŸ’€

πŸŽ€ RETURN VALUES πŸŽ€

archive_entry_linkresolver_new() returns NULL on malloc(3) failures. that's it, that's the list 😭


πŸ‘€ SEE ALSO πŸ‘€

archive_entry(3)


and that's the page!! this one came out of Debian, dated February 2, 2012 β€” vintage but still serving πŸ’œ slay, go write some archives, and remember: flush your deferred entries before you switch strategies πŸ¦‹βœ¨

πŸ“¦ where this page comes from