1/* libdeps plugin for the GNU linker.
2   Copyright (C) 2020-2022 Free Software Foundation, Inc.
3
4   This file is part of the GNU Binutils.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
19   MA 02110-1301, USA.  */
20
21#include "sysdep.h"
22#include "bfd.h"
23#if BFD_SUPPORTS_PLUGINS
24#include "plugin-api.h"
25
26#include <ctype.h> /* For isspace.  */
27
28extern enum ld_plugin_status onload (struct ld_plugin_tv *tv);
29
30/* Helper for calling plugin api message function.  */
31#define TV_MESSAGE if (tv_message) (*tv_message)
32
33/* Function pointers to cache hooks passed at onload time.  */
34static ld_plugin_register_claim_file tv_register_claim_file = 0;
35static ld_plugin_register_all_symbols_read tv_register_all_symbols_read = 0;
36static ld_plugin_register_cleanup tv_register_cleanup = 0;
37static ld_plugin_message tv_message = 0;
38static ld_plugin_add_input_library tv_add_input_library = 0;
39static ld_plugin_set_extra_library_path tv_set_extra_library_path = 0;
40
41/* Handle/record information received in a transfer vector entry.  */
42static enum ld_plugin_status
43parse_tv_tag (struct ld_plugin_tv *tv)
44{
45#define SETVAR(x) x = tv->tv_u.x
46  switch (tv->tv_tag)
47    {
48      case LDPT_REGISTER_CLAIM_FILE_HOOK:
49	SETVAR(tv_register_claim_file);
50	break;
51      case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
52	SETVAR(tv_register_all_symbols_read);
53	break;
54      case LDPT_REGISTER_CLEANUP_HOOK:
55	SETVAR(tv_register_cleanup);
56	break;
57      case LDPT_MESSAGE:
58	SETVAR(tv_message);
59	break;
60      case LDPT_ADD_INPUT_LIBRARY:
61	SETVAR(tv_add_input_library);
62	break;
63      case LDPT_SET_EXTRA_LIBRARY_PATH:
64	SETVAR(tv_set_extra_library_path);
65	break;
66      default:
67	break;
68    }
69#undef SETVAR
70  return LDPS_OK;
71}
72
73/* Defs for archive parsing.  */
74#define ARMAGSIZE	8
75typedef struct arhdr
76{
77  char ar_name[16];
78  char ar_date[12];
79  char ar_uid[6];
80  char ar_gid[6];
81  char ar_mode[8];
82  char ar_size[10];
83  char ar_fmag[2];
84} arhdr;
85
86typedef struct linerec
87{
88  struct linerec *next;
89  char line[];
90} linerec;
91
92#define LIBDEPS "__.LIBDEP/ "
93
94static linerec *line_head, **line_tail = &line_head;
95
96static enum ld_plugin_status
97get_libdeps (int fd)
98{
99  arhdr ah;
100  int len;
101  unsigned long mlen;
102  size_t amt;
103  linerec *lr;
104  enum ld_plugin_status rc = LDPS_NO_SYMS;
105
106  lseek (fd, ARMAGSIZE, SEEK_SET);
107  for (;;)
108    {
109      len = read (fd, (void *) &ah, sizeof (ah));
110      if (len != sizeof (ah))
111	break;
112      mlen = strtoul (ah.ar_size, NULL, 10);
113      if (!mlen || strncmp (ah.ar_name, LIBDEPS, sizeof (LIBDEPS)-1))
114	{
115	  lseek (fd, mlen, SEEK_CUR);
116	  continue;
117	}
118      amt = mlen + sizeof (linerec);
119      if (amt <= mlen)
120	return LDPS_ERR;
121      lr = malloc (amt);
122      if (!lr)
123	return LDPS_ERR;
124      lr->next = NULL;
125      len = read (fd, lr->line, mlen);
126      lr->line[mlen-1] = '\0';
127      *line_tail = lr;
128      line_tail = &lr->next;
129      rc = LDPS_OK;
130      break;
131    }
132  return rc;
133}
134
135/* Turn a string into an argvec.  */
136static char **
137str2vec (char *in)
138{
139  char **res;
140  char *s, *first, *end;
141  char *sq, *dq;
142  int i;
143
144  end = in + strlen (in);
145  s = in;
146  while (isspace ((unsigned char) *s)) s++;
147  first = s;
148
149  i = 1;
150  while ((s = strchr (s, ' ')))
151    {
152      s++;
153      i++;
154    }
155  res = (char **)malloc ((i+1) * sizeof (char *));
156  if (!res)
157    return res;
158
159  i = 0;
160  sq = NULL;
161  dq = NULL;
162  res[0] = first;
163  for (s = first; *s; s++)
164    {
165      if (*s == '\\')
166	{
167	  memmove (s, s+1, end-s-1);
168	  end--;
169	}
170      if (isspace ((unsigned char) *s))
171	{
172	  if (sq || dq)
173	    continue;
174	  *s++ = '\0';
175	  while (isspace ((unsigned char) *s)) s++;
176	  if (*s)
177	    res[++i] = s;
178	}
179      if (*s == '\'' && !dq)
180	{
181	  if (sq)
182	    {
183	      memmove (sq, sq+1, s-sq-1);
184	      memmove (s-2, s+1, end-s-1);
185	      end -= 2;
186	      s--;
187	      sq = NULL;
188	    }
189	  else
190	    {
191	      sq = s;
192	    }
193	}
194      if (*s == '"' && !sq)
195	{
196	  if (dq)
197	    {
198	      memmove (dq, dq+1, s-dq-1);
199	      memmove (s-2, s+1, end-s-1);
200	      end -= 2;
201	      s--;
202	      dq = NULL;
203	    }
204	  else
205	    {
206	      dq = s;
207	    }
208	}
209    }
210  res[++i] = NULL;
211  return res;
212}
213
214static char *prevfile;
215
216/* Standard plugin API registerable hook.  */
217static enum ld_plugin_status
218onclaim_file (const struct ld_plugin_input_file *file, int *claimed)
219{
220  enum ld_plugin_status rv;
221
222  *claimed = 0;
223
224  /* If we've already seen this file, ignore it.  */
225  if (prevfile && !strcmp (file->name, prevfile))
226    return LDPS_OK;
227
228  /* If it's not an archive member, ignore it.  */
229  if (!file->offset)
230    return LDPS_OK;
231
232  if (prevfile)
233    free (prevfile);
234
235  prevfile = strdup (file->name);
236  if (!prevfile)
237    return LDPS_ERR;
238
239  /* This hook only gets called on actual object files.
240   * We have to examine the archive ourselves, to find
241   * our LIBDEPS member.  */
242  rv = get_libdeps (file->fd);
243  if (rv == LDPS_ERR)
244    return rv;
245
246  if (rv == LDPS_OK)
247    {
248      linerec *lr = (linerec *)line_tail;
249      /* Inform the user/testsuite.  */
250      TV_MESSAGE (LDPL_INFO, "got deps for library %s: %s",
251		  file->name, lr->line);
252      fflush (NULL);
253    }
254
255  return LDPS_OK;
256}
257
258/* Standard plugin API registerable hook.  */
259static enum ld_plugin_status
260onall_symbols_read (void)
261{
262  linerec *lr;
263  char **vec;
264  enum ld_plugin_status rv = LDPS_OK;
265
266  while ((lr = line_head))
267    {
268      line_head = lr->next;
269      vec = str2vec (lr->line);
270      if (vec)
271	{
272	  int i;
273	  for (i = 0; vec[i]; i++)
274	    {
275	      if (vec[i][0] != '-')
276		{
277		  TV_MESSAGE (LDPL_WARNING, "ignoring libdep argument %s",
278			      vec[i]);
279		  fflush (NULL);
280		  continue;
281		}
282	      if (vec[i][1] == 'l')
283		rv = tv_add_input_library (vec[i]+2);
284	      else if (vec[i][1] == 'L')
285		rv = tv_set_extra_library_path (vec[i]+2);
286	      else
287		{
288		  TV_MESSAGE (LDPL_WARNING, "ignoring libdep argument %s",
289			      vec[i]);
290		  fflush (NULL);
291		}
292	      if (rv != LDPS_OK)
293		break;
294	    }
295	  free (vec);
296	}
297      free (lr);
298    }
299  line_tail = NULL;
300  return rv;
301}
302
303/* Standard plugin API registerable hook.  */
304static enum ld_plugin_status
305oncleanup (void)
306{
307  if (prevfile)
308    {
309      free (prevfile);
310      prevfile = NULL;
311    }
312  if (line_head)
313    {
314      linerec *lr;
315      while ((lr = line_head))
316	{
317	  line_head = lr->next;
318	  free (lr);
319	}
320      line_tail = NULL;
321    }
322  return LDPS_OK;
323}
324
325/* Standard plugin API entry point.  */
326enum ld_plugin_status
327onload (struct ld_plugin_tv *tv)
328{
329  enum ld_plugin_status rv;
330
331  /* This plugin requires a valid tv array.  */
332  if (!tv)
333    return LDPS_ERR;
334
335  /* First entry should always be LDPT_MESSAGE, letting us get
336     hold of it easily so we can send output straight away.  */
337  if (tv[0].tv_tag == LDPT_MESSAGE)
338    tv_message = tv[0].tv_u.tv_message;
339
340  do
341    if ((rv = parse_tv_tag (tv)) != LDPS_OK)
342      return rv;
343  while ((tv++)->tv_tag != LDPT_NULL);
344
345  /* Register hooks.  */
346  if (tv_register_claim_file
347      && tv_register_all_symbols_read
348      && tv_register_cleanup)
349    {
350      (*tv_register_claim_file) (onclaim_file);
351      (*tv_register_all_symbols_read) (onall_symbols_read);
352      (*tv_register_cleanup) (oncleanup);
353    }
354  fflush (NULL);
355  return LDPS_OK;
356}
357#endif /* BFD_SUPPORTS_PLUGINS */
358