1*7e30afceSmillert /* $OpenBSD: targequiv.c,v 1.11 2024/06/18 02:11:04 millert Exp $ */
26c2dbbdaSespie /*
36c2dbbdaSespie * Copyright (c) 2007-2008 Marc Espie.
46c2dbbdaSespie *
56c2dbbdaSespie * Extensive code changes for the OpenBSD project.
66c2dbbdaSespie *
76c2dbbdaSespie * Redistribution and use in source and binary forms, with or without
86c2dbbdaSespie * modification, are permitted provided that the following conditions
96c2dbbdaSespie * are met:
106c2dbbdaSespie * 1. Redistributions of source code must retain the above copyright
116c2dbbdaSespie * notice, this list of conditions and the following disclaimer.
126c2dbbdaSespie * 2. Redistributions in binary form must reproduce the above copyright
136c2dbbdaSespie * notice, this list of conditions and the following disclaimer in the
146c2dbbdaSespie * documentation and/or other materials provided with the distribution.
156c2dbbdaSespie *
166c2dbbdaSespie * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
176c2dbbdaSespie * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
186c2dbbdaSespie * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
196c2dbbdaSespie * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
206c2dbbdaSespie * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
216c2dbbdaSespie * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
226c2dbbdaSespie * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
236c2dbbdaSespie * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
246c2dbbdaSespie * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
256c2dbbdaSespie * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
266c2dbbdaSespie * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
276c2dbbdaSespie */
286c2dbbdaSespie
296c2dbbdaSespie /* Compute equivalence tables of targets, helpful for VPATH and parallel
306c2dbbdaSespie * make.
316c2dbbdaSespie */
326c2dbbdaSespie
331f89b472Sespie #include <stddef.h>
341f89b472Sespie #include <stdint.h>
351f89b472Sespie #include <stdio.h>
361f89b472Sespie #include <stdlib.h>
376c2dbbdaSespie #include <string.h>
381f89b472Sespie #include <ohash.h>
39452f2662Sderaadt #include <limits.h>
406c2dbbdaSespie #include "defines.h"
416c2dbbdaSespie #include "memory.h"
426c2dbbdaSespie #include "gnode.h"
436c2dbbdaSespie #include "lst.h"
446c2dbbdaSespie #include "suff.h"
456c2dbbdaSespie #include "dir.h"
466c2dbbdaSespie #include "targ.h"
476c2dbbdaSespie #include "targequiv.h"
486c2dbbdaSespie
496c2dbbdaSespie struct equiv_list {
506c2dbbdaSespie GNode *first, *last;
516c2dbbdaSespie char name[1];
526c2dbbdaSespie };
536c2dbbdaSespie
546c2dbbdaSespie static struct ohash_info equiv_info = {
55e2ff9f51Sespie offsetof(struct equiv_list, name), NULL, hash_calloc, hash_free,
566c2dbbdaSespie element_alloc
576c2dbbdaSespie };
586c2dbbdaSespie
596c2dbbdaSespie static void attach_node(GNode *, GNode *);
606c2dbbdaSespie static void build_equivalence(void);
616c2dbbdaSespie static void add_to_equiv_list(struct ohash *, GNode *);
626c2dbbdaSespie static char *names_match(GNode *, GNode *);
636c2dbbdaSespie static char *names_match_with_dir(const char *, const char *, char *,
646c2dbbdaSespie char *, const char *);
656c2dbbdaSespie static char *names_match_with_dirs(const char *, const char *, char *,
666c2dbbdaSespie char *, const char *, const char *);
676c2dbbdaSespie static char *relative_reduce(const char *, const char *);
686c2dbbdaSespie static char *relative_reduce2(const char *, const char *, const char *);
696c2dbbdaSespie static char *absolute_reduce(const char *);
706c2dbbdaSespie static size_t parse_reduce(size_t, const char *);
716c2dbbdaSespie static void find_siblings(GNode *);
726c2dbbdaSespie
736c2dbbdaSespie /* These functions build `equivalence lists' of targets with the same
746c2dbbdaSespie * basename, as circular lists. They use an intermediate ohash as scaffold,
756c2dbbdaSespie * to insert same-basename targets in a simply linked list. Then they make
766c2dbbdaSespie * those lists circular, to the exception of lone elements, since they can't
776c2dbbdaSespie * alias to anything.
786c2dbbdaSespie *
796c2dbbdaSespie * This structure is used to simplify alias-lookup to a great extent: two
806c2dbbdaSespie * nodes can only alias each other if they're part of the same equivalence
816c2dbbdaSespie * set. Most nodes either don't belong to an alias set, or to a very simple
826c2dbbdaSespie * alias set, thus removing most possibilities.
836c2dbbdaSespie */
846c2dbbdaSespie static void
add_to_equiv_list(struct ohash * equiv,GNode * gn)856c2dbbdaSespie add_to_equiv_list(struct ohash *equiv, GNode *gn)
866c2dbbdaSespie {
876c2dbbdaSespie const char *end = NULL;
886c2dbbdaSespie struct equiv_list *e;
896c2dbbdaSespie unsigned int slot;
906c2dbbdaSespie
916c2dbbdaSespie if (!should_have_file(gn))
926c2dbbdaSespie return;
936c2dbbdaSespie
946c2dbbdaSespie gn->basename = strrchr(gn->name, '/');
956c2dbbdaSespie if (gn->basename == NULL)
966c2dbbdaSespie gn->basename = gn->name;
976c2dbbdaSespie else
986c2dbbdaSespie gn->basename++;
996c2dbbdaSespie slot = ohash_qlookupi(equiv, gn->basename, &end);
1006c2dbbdaSespie e = ohash_find(equiv, slot);
1016c2dbbdaSespie if (e == NULL) {
1026c2dbbdaSespie e = ohash_create_entry(&equiv_info, gn->basename, &end);
1036c2dbbdaSespie e->first = NULL;
1046c2dbbdaSespie e->last = gn;
1056c2dbbdaSespie ohash_insert(equiv, slot, e);
1066c2dbbdaSespie }
1076c2dbbdaSespie gn->next = e->first;
1086c2dbbdaSespie e->first = gn;
1096c2dbbdaSespie }
1106c2dbbdaSespie
1116c2dbbdaSespie static void
build_equivalence(void)112*7e30afceSmillert build_equivalence(void)
1136c2dbbdaSespie {
1146c2dbbdaSespie unsigned int i;
1156c2dbbdaSespie GNode *gn;
1166c2dbbdaSespie struct equiv_list *e;
1176c2dbbdaSespie struct ohash equiv;
1186c2dbbdaSespie struct ohash *t = targets_hash();
1196c2dbbdaSespie
1206c2dbbdaSespie
1216c2dbbdaSespie ohash_init(&equiv, 10, &equiv_info);
1226c2dbbdaSespie
1236c2dbbdaSespie for (gn = ohash_first(t, &i); gn != NULL; gn = ohash_next(t, &i))
1246c2dbbdaSespie add_to_equiv_list(&equiv, gn);
1256c2dbbdaSespie
1266c2dbbdaSespie /* finish making the lists circular */
1276c2dbbdaSespie for (e = ohash_first(&equiv, &i); e != NULL;
1286c2dbbdaSespie e = ohash_next(&equiv, &i)) {
1296c2dbbdaSespie if (e->last != e->first)
1306c2dbbdaSespie e->last->next = e->first;
1316c2dbbdaSespie free(e);
1326c2dbbdaSespie }
1336c2dbbdaSespie ohash_delete(&equiv);
1346c2dbbdaSespie }
1356c2dbbdaSespie
1366c2dbbdaSespie static const char *curdir, *objdir;
1376c2dbbdaSespie static char *kobjdir;
1386c2dbbdaSespie static size_t objdir_len;
1396c2dbbdaSespie
1406c2dbbdaSespie void
Targ_setdirs(const char * c,const char * o)1416c2dbbdaSespie Targ_setdirs(const char *c, const char *o)
1426c2dbbdaSespie {
1436c2dbbdaSespie curdir = c;
1446c2dbbdaSespie objdir = o;
1456c2dbbdaSespie
1466c2dbbdaSespie objdir_len = strlen(o);
1476c2dbbdaSespie kobjdir = emalloc(objdir_len+2);
1486c2dbbdaSespie memcpy(kobjdir, o, objdir_len);
1496c2dbbdaSespie kobjdir[objdir_len++] = '/';
1506c2dbbdaSespie kobjdir[objdir_len] = 0;
1516c2dbbdaSespie }
1526c2dbbdaSespie
1536c2dbbdaSespie
1546c2dbbdaSespie void
kludge_look_harder_for_target(GNode * gn)1556c2dbbdaSespie kludge_look_harder_for_target(GNode *gn)
1566c2dbbdaSespie {
1576c2dbbdaSespie GNode *extra, *cgn;
1586c2dbbdaSespie LstNode ln;
1596c2dbbdaSespie
1606c2dbbdaSespie if (strncmp(gn->name, kobjdir, objdir_len) == 0) {
1616c2dbbdaSespie extra = Targ_FindNode(gn->name + objdir_len, TARG_NOCREATE);
1626c2dbbdaSespie if (extra != NULL) {
1636c2dbbdaSespie if (Lst_IsEmpty(&gn->commands))
1646c2dbbdaSespie Lst_Concat(&gn->commands, &extra->commands);
1656c2dbbdaSespie for (ln = Lst_First(&extra->children); ln != NULL;
1666c2dbbdaSespie ln = Lst_Adv(ln)) {
167568f6c05Sespie cgn = Lst_Datum(ln);
1686c2dbbdaSespie
1696c2dbbdaSespie if (Lst_AddNew(&gn->children, cgn)) {
1706c2dbbdaSespie Lst_AtEnd(&cgn->parents, gn);
171194f6eccSespie gn->children_left++;
1726c2dbbdaSespie }
1736c2dbbdaSespie }
1746c2dbbdaSespie }
1756c2dbbdaSespie }
1766c2dbbdaSespie }
1776c2dbbdaSespie
1786c2dbbdaSespie static void
attach_node(GNode * gn,GNode * extra)1796c2dbbdaSespie attach_node(GNode *gn, GNode *extra)
1806c2dbbdaSespie {
1816c2dbbdaSespie /* XXX normally extra->sibling == extra, but this is not
1826c2dbbdaSespie * always the case yet, so we merge the two lists
1836c2dbbdaSespie */
1846c2dbbdaSespie GNode *tmp;
1856c2dbbdaSespie
1866c2dbbdaSespie tmp = gn->sibling;
1876c2dbbdaSespie gn->sibling = extra->sibling;
1886c2dbbdaSespie extra->sibling = tmp;
1896c2dbbdaSespie }
1906c2dbbdaSespie
1916c2dbbdaSespie static char *buffer = NULL;
192452f2662Sderaadt static size_t bufsize = PATH_MAX;
1936c2dbbdaSespie
1946c2dbbdaSespie static size_t
parse_reduce(size_t i,const char * src)1956c2dbbdaSespie parse_reduce(size_t i, const char *src)
1966c2dbbdaSespie {
1976c2dbbdaSespie while (src[0] != 0) {
1986c2dbbdaSespie while (src[0] == '/')
1996c2dbbdaSespie src++;
2006c2dbbdaSespie /* special cases */
2016c2dbbdaSespie if (src[0] == '.') {
2026c2dbbdaSespie if (src[1] == '/') {
2036c2dbbdaSespie src += 2;
2046c2dbbdaSespie continue;
2056c2dbbdaSespie }
2066c2dbbdaSespie if (src[1] == '.' && src[2] == '/') {
2076c2dbbdaSespie src += 3;
2086c2dbbdaSespie i--;
2096c2dbbdaSespie while (i > 0 && buffer[i-1] != '/')
2106c2dbbdaSespie i--;
2116c2dbbdaSespie if (i == 0)
2126c2dbbdaSespie i = 1;
2136c2dbbdaSespie continue;
2146c2dbbdaSespie }
2156c2dbbdaSespie }
2166c2dbbdaSespie while (src[0] != '/' && src[0] != 0) {
2176c2dbbdaSespie if (i > bufsize - 2) {
2186c2dbbdaSespie bufsize *= 2;
2196c2dbbdaSespie buffer = erealloc(buffer, bufsize);
2206c2dbbdaSespie }
2216c2dbbdaSespie buffer[i++] = *src++;
2226c2dbbdaSespie }
2234ef28080Sespie if (src[0] == '/')
2244ef28080Sespie buffer[i++] = *src++;
2256c2dbbdaSespie }
2264ef28080Sespie buffer[i++] = 0;
2276c2dbbdaSespie return i;
2286c2dbbdaSespie }
2296c2dbbdaSespie
2306c2dbbdaSespie static char *
absolute_reduce(const char * src)2316c2dbbdaSespie absolute_reduce(const char *src)
2326c2dbbdaSespie {
2336c2dbbdaSespie size_t i = 0;
2346c2dbbdaSespie
2356c2dbbdaSespie if (buffer == NULL)
2366c2dbbdaSespie buffer = emalloc(bufsize);
2376c2dbbdaSespie
2386c2dbbdaSespie buffer[i++] = '/';
2396c2dbbdaSespie i = parse_reduce(i, src);
2406c2dbbdaSespie return estrdup(buffer);
2416c2dbbdaSespie }
2426c2dbbdaSespie
2436c2dbbdaSespie static char *
relative_reduce(const char * dir,const char * src)2446c2dbbdaSespie relative_reduce(const char *dir, const char *src)
2456c2dbbdaSespie {
2466c2dbbdaSespie size_t i = 0;
2476c2dbbdaSespie
2486c2dbbdaSespie if (buffer == NULL)
2496c2dbbdaSespie buffer = emalloc(bufsize);
2506c2dbbdaSespie
2516c2dbbdaSespie buffer[i++] = '/';
2526c2dbbdaSespie i = parse_reduce(i, dir);
2536c2dbbdaSespie i--;
2546c2dbbdaSespie
2556c2dbbdaSespie if (buffer[i-1] != '/')
2566c2dbbdaSespie buffer[i++] = '/';
2576c2dbbdaSespie i = parse_reduce(i, src);
2586c2dbbdaSespie return estrdup(buffer);
2596c2dbbdaSespie }
2606c2dbbdaSespie
2616c2dbbdaSespie static char *
relative_reduce2(const char * dir1,const char * dir2,const char * src)2626c2dbbdaSespie relative_reduce2(const char *dir1, const char *dir2, const char *src)
2636c2dbbdaSespie {
2646c2dbbdaSespie size_t i = 0;
2656c2dbbdaSespie
2666c2dbbdaSespie if (buffer == NULL)
2676c2dbbdaSespie buffer = emalloc(bufsize);
2686c2dbbdaSespie
2696c2dbbdaSespie buffer[i++] = '/';
2706c2dbbdaSespie i = parse_reduce(i, dir1);
2716c2dbbdaSespie i--;
2726c2dbbdaSespie if (buffer[i-1] != '/')
2736c2dbbdaSespie buffer[i++] = '/';
2746c2dbbdaSespie
2756c2dbbdaSespie i = parse_reduce(i, dir2);
2766c2dbbdaSespie i--;
2776c2dbbdaSespie if (buffer[i-1] != '/')
2786c2dbbdaSespie buffer[i++] = '/';
2796c2dbbdaSespie
2806c2dbbdaSespie i = parse_reduce(i, src);
2816c2dbbdaSespie return estrdup(buffer);
2826c2dbbdaSespie }
2836c2dbbdaSespie
2846c2dbbdaSespie static char *
names_match_with_dir(const char * a,const char * b,char * ra,char * rb,const char * dir)2856c2dbbdaSespie names_match_with_dir(const char *a, const char *b, char *ra,
2866c2dbbdaSespie char *rb, const char *dir)
2876c2dbbdaSespie {
2886c2dbbdaSespie bool r;
2896c2dbbdaSespie bool free_a, free_b;
2906c2dbbdaSespie
2916c2dbbdaSespie if (ra == NULL) {
2926c2dbbdaSespie ra = relative_reduce(dir, a);
2936c2dbbdaSespie free_a = true;
2946c2dbbdaSespie } else {
2956c2dbbdaSespie free_a = false;
2966c2dbbdaSespie }
2976c2dbbdaSespie
2986c2dbbdaSespie if (rb == NULL) {
2996c2dbbdaSespie rb = relative_reduce(dir, b);
3006c2dbbdaSespie free_b = true;
3016c2dbbdaSespie } else {
3026c2dbbdaSespie free_b = false;
3036c2dbbdaSespie }
3046c2dbbdaSespie r = strcmp(ra, rb) == 0;
3056c2dbbdaSespie if (free_a)
3066c2dbbdaSespie free(ra);
3076c2dbbdaSespie if (r)
3086c2dbbdaSespie return rb;
3096c2dbbdaSespie else {
3106c2dbbdaSespie if (free_b)
3116c2dbbdaSespie free(rb);
3126c2dbbdaSespie return NULL;
3136c2dbbdaSespie }
3146c2dbbdaSespie }
3156c2dbbdaSespie
3166c2dbbdaSespie static char *
names_match_with_dirs(const char * a,const char * b,char * ra,char * rb,const char * dir1,const char * dir2)3176c2dbbdaSespie names_match_with_dirs(const char *a, const char *b, char *ra,
3186c2dbbdaSespie char *rb, const char *dir1, const char *dir2)
3196c2dbbdaSespie {
3206c2dbbdaSespie bool r;
3216c2dbbdaSespie bool free_a, free_b;
3226c2dbbdaSespie
3236c2dbbdaSespie if (ra == NULL) {
3246c2dbbdaSespie ra = relative_reduce2(dir1, dir2, a);
3256c2dbbdaSespie free_a = true;
3266c2dbbdaSespie } else {
3276c2dbbdaSespie free_a = false;
3286c2dbbdaSespie }
3296c2dbbdaSespie
3306c2dbbdaSespie if (rb == NULL) {
3316c2dbbdaSespie rb = relative_reduce2(dir1, dir2, b);
3326c2dbbdaSespie free_b = true;
3336c2dbbdaSespie } else {
3346c2dbbdaSespie free_b = false;
3356c2dbbdaSespie }
3366c2dbbdaSespie r = strcmp(ra, rb) == 0;
3376c2dbbdaSespie if (free_a)
3386c2dbbdaSespie free(ra);
3396c2dbbdaSespie if (r)
3406c2dbbdaSespie return rb;
3416c2dbbdaSespie else {
3426c2dbbdaSespie if (free_b)
3436c2dbbdaSespie free(rb);
3446c2dbbdaSespie return NULL;
3456c2dbbdaSespie }
3466c2dbbdaSespie }
3476c2dbbdaSespie
3486c2dbbdaSespie static char *
names_match(GNode * a,GNode * b)3496c2dbbdaSespie names_match(GNode *a, GNode *b)
3506c2dbbdaSespie {
3516c2dbbdaSespie char *ra = NULL , *rb = NULL;
3526c2dbbdaSespie char *r;
3536c2dbbdaSespie
3546c2dbbdaSespie if (a->name[0] == '/')
3556c2dbbdaSespie ra = absolute_reduce(a->name);
3566c2dbbdaSespie if (b->name[0] == '/')
3576c2dbbdaSespie rb = absolute_reduce(b->name);
3586c2dbbdaSespie if (ra && rb) {
3596c2dbbdaSespie if (strcmp(ra, rb) == 0)
3606c2dbbdaSespie r = rb;
3616c2dbbdaSespie else
3626c2dbbdaSespie r = NULL;
3636c2dbbdaSespie } else {
3646c2dbbdaSespie r = names_match_with_dir(a->name, b->name, ra, rb, objdir);
3656c2dbbdaSespie if (!r)
3666c2dbbdaSespie r = names_match_with_dir(a->name, b->name, ra, rb,
3676c2dbbdaSespie curdir);
3686c2dbbdaSespie if (!r) {
3696c2dbbdaSespie /* b has necessarily the same one */
3706c2dbbdaSespie Lst l = find_suffix_path(a);
3716c2dbbdaSespie LstNode ln;
3726c2dbbdaSespie
3736c2dbbdaSespie for (ln = Lst_First(l); ln != NULL; ln = Lst_Adv(ln)) {
3746c2dbbdaSespie const char *p = PathEntry_name(Lst_Datum(ln));
3756c2dbbdaSespie if (p[0] == '/') {
3766c2dbbdaSespie r = names_match_with_dir(a->name,
3776c2dbbdaSespie b->name, ra, rb, p);
3786c2dbbdaSespie if (r)
3796c2dbbdaSespie break;
3806c2dbbdaSespie } else {
3816c2dbbdaSespie r = names_match_with_dirs(a->name,
3826c2dbbdaSespie b->name, ra, rb, p, objdir);
3836c2dbbdaSespie if (r)
3846c2dbbdaSespie break;
3856c2dbbdaSespie r = names_match_with_dirs(a->name,
3866c2dbbdaSespie b->name, ra, rb, p, curdir);
3876c2dbbdaSespie if (r)
3886c2dbbdaSespie break;
3896c2dbbdaSespie }
3906c2dbbdaSespie }
3916c2dbbdaSespie }
3926c2dbbdaSespie }
3936c2dbbdaSespie free(ra);
3946c2dbbdaSespie if (r != rb)
3956c2dbbdaSespie free(rb);
3966c2dbbdaSespie return r;
3976c2dbbdaSespie }
3986c2dbbdaSespie
3996c2dbbdaSespie static void
find_siblings(GNode * gn)4006c2dbbdaSespie find_siblings(GNode *gn)
4016c2dbbdaSespie {
4026c2dbbdaSespie GNode *gn2;
4036c2dbbdaSespie char *fullpath;
4046c2dbbdaSespie
4056c2dbbdaSespie /* not part of an equivalence class: can't alias */
4066c2dbbdaSespie if (gn->next == NULL)
4076c2dbbdaSespie return;
4086c2dbbdaSespie /* already resolved, actually */
4096c2dbbdaSespie if (gn->sibling != gn)
4106c2dbbdaSespie return;
4116c2dbbdaSespie if (DEBUG(NAME_MATCHING))
4126c2dbbdaSespie fprintf(stderr, "Matching for %s:", gn->name);
4136c2dbbdaSespie /* look through the aliases */
4146c2dbbdaSespie for (gn2 = gn->next; gn2 != gn; gn2 = gn2->next) {
4156c2dbbdaSespie fullpath = names_match(gn, gn2);
4166c2dbbdaSespie if (fullpath) {
4176c2dbbdaSespie attach_node(gn, gn2);
4186c2dbbdaSespie } else {
4196c2dbbdaSespie if (DEBUG(NAME_MATCHING))
4206c2dbbdaSespie fputc('!', stderr);
4216c2dbbdaSespie }
4226c2dbbdaSespie if (DEBUG(NAME_MATCHING))
4236c2dbbdaSespie fprintf(stderr, "%s ", gn2->name);
4246c2dbbdaSespie }
4256c2dbbdaSespie if (DEBUG(NAME_MATCHING))
4266c2dbbdaSespie fputc('\n', stderr);
4276c2dbbdaSespie }
4286c2dbbdaSespie
4296c2dbbdaSespie void
look_harder_for_target(GNode * gn)4306c2dbbdaSespie look_harder_for_target(GNode *gn)
4316c2dbbdaSespie {
4326c2dbbdaSespie static bool equiv_was_built = false;
4336c2dbbdaSespie
4346c2dbbdaSespie if (!equiv_was_built) {
4356c2dbbdaSespie build_equivalence();
4366c2dbbdaSespie equiv_was_built = true;
4376c2dbbdaSespie }
4386c2dbbdaSespie
4396c2dbbdaSespie if (gn->type & (OP_RESOLVED | OP_PHONY))
4406c2dbbdaSespie return;
4416c2dbbdaSespie gn->type |= OP_RESOLVED;
4426c2dbbdaSespie find_siblings(gn);
4436c2dbbdaSespie }
4446c2dbbdaSespie
4456c2dbbdaSespie bool
is_sibling(GNode * gn,GNode * gn2)4466c2dbbdaSespie is_sibling(GNode *gn, GNode *gn2)
4476c2dbbdaSespie {
4486c2dbbdaSespie GNode *sibling;
4496c2dbbdaSespie
4506c2dbbdaSespie sibling = gn;
4516c2dbbdaSespie do {
4526c2dbbdaSespie if (sibling == gn2)
4536c2dbbdaSespie return true;
4546c2dbbdaSespie sibling = sibling->sibling;
4556c2dbbdaSespie } while (sibling != gn);
4566c2dbbdaSespie
4576c2dbbdaSespie return false;
4586c2dbbdaSespie }
459