Delete node from linked list, using offsetof to set the tail pointer
Can be simplified

You chose to use a "pointer to pointer" technique to find and delete your node, but this led to using an offsetof macro in order to back up your tail pointer to the previous node.  If you instead used a "previous pointer" technique, your code would be much simpler to read and you wouldn't have to use any strange pointer subtraction tricks in order to find the previous node.  Here is how it would look like:

lrsll_node *lrsll_delete(lrsll_list *list, const char *data)
{
    lrsll_node *prev = NULL;
    lrsll_node *node = list-&gt;head;

    while (node != NULL &amp;&amp; strcmp(node-&gt;data, data) != 0) {
        prev = node;
        node = node-&gt;next;
    }

    if (node == NULL)
        return NULL;

    if (prev == NULL)
        list-&gt;head = node-&gt;next;
    else
        prev-&gt;next = node-&gt;next;

    if (node == list-&gt;tail)
        list-&gt;tail = prev;

    return node;
}


Addendum: Linus Torvalds' comments

Apparently this whole code was motivated by something Linus Torvalds wrote about using pointers to pointers.  Here is the example demonstrating what he meant, taken from this blog post:

Given this list type:

typedef struct list_entry {
    int val;
    struct list_entry *next;
} list_entry;


This is the "wrong way" to delete a node:

list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;

while (entry) {
    if (entry-&gt;val == to_remove)     /* this is the one to remove */
        if (prev)
           prev-&gt;next = entry-&gt;next; /* remove the entry */
        else
           head = entry-&gt;next;      /* special case - first entry */

    /* move on to the next entry */
    prev = entry;
    entry = entry-&gt;next;
}


And here is what Linus considered the "correct way":

list_entry **pp = &amp;head; /* pointer to a pointer */
list_entry *entry = head;

while (entry) {
    if (entry-&gt;val == to_remove)
        *pp = entry-&gt;next;

    pp = &amp;entry-&gt;next;
    entry = entry-&gt;next;
}


And I will agree with Linus that for this case, the "double pointer" solution is better than the "prev pointer" version.  But there is an important difference between this example and the OP's code, which is that the list in this example has no special List structure containing a head and tail pointer.

Because you may need to set the tail pointer to the previous node, the benefit of using the double pointer disappears, because now you need to convert the double pointer back into a node pointer using the awful offsetof trick.  So for the particular data structure used by the OP, I would argue that the "prev pointer" solution is preferable.

In other words, you shouldn't always use the double pointer solution just because Linus said to use double pointers.  You should consider whether or not your code would benefit from using double pointers and then use them if they provide a net benefit.