1/* Copyright (C) 2021 Free Software Foundation, Inc.
2   Contributed by Oracle.
3
4   This file is part of 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, or (at your option)
9   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, 51 Franklin Street - Fifth Floor, Boston,
19   MA 02110-1301, USA.  */
20
21#include "config.h"
22#include <assert.h>
23#include <ctype.h>
24#include <sys/param.h>
25#include <unistd.h>
26
27#include "gp-defs.h"
28#include "util.h"
29#include "collctrl.h"
30#include "collect.h"
31#include "StringBuilder.h"
32#include "Settings.h"
33
34#define	STDEBUFSIZE	24000
35
36#define LIBGP_COLLECTOR             "libgp-collector.so"
37#define GPROFNG_PRELOAD_LIBDIRS     "GPROFNG_PRELOAD_LIBDIRS"
38#define SP_COLLECTOR_EXPNAME        "SP_COLLECTOR_EXPNAME"
39#define SP_COLLECTOR_FOLLOW_SPEC    "SP_COLLECTOR_FOLLOW_SPEC"
40#define SP_COLLECTOR_PARAMS         "SP_COLLECTOR_PARAMS"
41#define SP_COLLECTOR_FOUNDER        "SP_COLLECTOR_FOUNDER"
42#define SP_COLLECTOR_ORIGIN_COLLECT "SP_COLLECTOR_ORIGIN_COLLECT"
43
44static const char *LD_AUDIT[] = {
45  //    "LD_AUDIT",	Do not set LD_AUDIT on Linux
46  NULL
47};
48
49static const char *LD_PRELOAD[] = {
50  "LD_PRELOAD",
51  NULL
52};
53
54static const char *SP_PRELOAD[] = {
55  "SP_COLLECTOR_PRELOAD",
56  NULL
57};
58
59static const char *LD_LIBRARY_PATH[] = {
60  "LD_LIBRARY_PATH",
61  NULL,
62};
63
64static int
65add_env (char *ev)
66{
67  int r = putenv (ev);
68  if (r != 0)
69    {
70      dbe_write (2, GTXT ("Can't putenv of %s: run aborted\n"), ev);
71      free (ev);
72    }
73  return r;
74}
75
76int
77collect::putenv_libcollector_ld_audits ()
78{
79  StringBuilder sb;
80  for (unsigned int ii = 0; ii < ARR_SIZE (LD_AUDIT) && LD_AUDIT[ii]; ++ii)
81    {
82      sb.sprintf ("%s=%s", LD_AUDIT[ii], SP_LIBAUDIT_NAME);
83      // Append the current value. Check if already set
84      char *old_val = getenv (LD_AUDIT[ii]);
85      if (old_val != NULL)
86	{
87	  while (isspace (*old_val))
88	    ++old_val;
89	  if (*old_val != (char) 0)
90	    {
91	      int fromIdx = sb.length ();
92	      sb.append (" ");
93	      sb.append (old_val);
94	      if (sb.indexOf (SP_LIBAUDIT_NAME, fromIdx) >= 0)
95		continue;       // Already set. Do nothing.
96	    }
97	}
98      if (add_env (sb.toString ()))
99	return 1;
100    }
101  return 0;
102}
103
104int
105collect::putenv_libcollector_ld_preloads ()
106{
107  // for those data types that get extra libs LD_PRELOAD'd, add them
108  if (cc->get_synctrace_mode () != 0)
109    add_ld_preload ("libgp-sync.so");
110  if (cc->get_heaptrace_mode () != 0)
111    add_ld_preload ("libgp-heap.so");
112  if (cc->get_iotrace_mode () != 0)
113    add_ld_preload ("libgp-iotrace.so");
114  add_ld_preload (SP_LIBCOLLECTOR_NAME);
115
116  // --- putenv SP_COLLECTOR_PRELOAD*
117  int ii;
118  for (ii = 0; SP_PRELOAD[ii]; ii++)
119    {
120      // construct the SP_PRELOAD_* environment variables
121      // and put them into the environment
122      if (add_env (dbe_sprintf ("%s=%s", SP_PRELOAD[ii], sp_preload_list[ii])))
123	return 1;
124    }
125  // --- putenv LD_PRELOADS
126  /* purge LD_PRELOAD* of values containing contents of SP_LIBCOLLECTOR_NAME */
127  if (putenv_purged_ld_preloads (SP_LIBCOLLECTOR_NAME))
128    dbe_write (2, GTXT ("Warning: %s is already defined in one or more LD_PRELOAD environment variables\n"),
129	       SP_LIBCOLLECTOR_NAME);
130  if (putenv_ld_preloads ())
131    return 1;
132  return 0;
133}
134
135int
136collect::putenv_libcollector_ld_misc ()
137{
138#if 0 // XXX 1 turns on LD_DEBUG
139  putenv (strdup ("LD_DEBUG=audit,bindings,detail"));
140#endif
141  // workaround to have the dynamic linker use absolute names
142  if (add_env (dbe_strdup ("LD_ORIGIN=yes")))
143    return 1;
144
145  // On Linux we have to provide SP_COLLECTOR_LIBRARY_PATH and LD_LIBRARY_PATH
146  // so that -agentlib:gp-collector works
147  // and so that collect -F works with 32/64-bit mix of processes
148
149  // Set GPROFNG_PRELOAD_LIBDIRS
150  char *ev = getenv (GPROFNG_PRELOAD_LIBDIRS);
151  char *libpath_list = NULL;
152  if (ev == NULL && settings->preload_libdirs == NULL)
153    {
154      settings->read_rc (false);
155      ev = settings->preload_libdirs;
156    }
157  ev = dbe_strdup (ev);
158  StringBuilder sb;
159  sb.appendf ("%s=", "SP_COLLECTOR_LIBRARY_PATH");
160  int len = sb.length ();
161  int cnt = 0;
162  for (char *s = ev; s;)
163    {
164      char *s1 = strchr (s, ':');
165      if (s1)
166	*(s1++) = 0;
167      char *fname;
168      if (*s == '/')
169	{
170	  fname = dbe_sprintf ("%s/%s/%s", s, PACKAGE, LIBGP_COLLECTOR);
171	  if (access (fname, R_OK | F_OK) == 0)
172	    {
173	      if (++cnt != 1)
174		sb.append (':');
175	      sb.appendf ("%s", s);
176	    }
177	}
178      else
179	{
180	  fname = dbe_sprintf ("%s/%s/%s/%s", run_dir, s, PACKAGE, LIBGP_COLLECTOR);
181	  if (access (fname, R_OK | F_OK) == 0)
182	    {
183	      if (++cnt != 1)
184		sb.append (':');
185	      sb.appendf ("%s/%s/%s", run_dir, s, PACKAGE);
186	    }
187	}
188      free (fname);
189      s = s1;
190    }
191  free (ev);
192  if (cnt == 0)
193    {
194      dbe_write (2, GTXT ("configuration error: can not find %s. run aborted\n"),
195		 LIBGP_COLLECTOR);
196      return 1;
197    }
198  libpath_list = sb.toString ();
199  if (add_env (libpath_list))
200    return 1;
201  libpath_list += len;
202
203  // --- set LD_LIBRARY_PATH using libpath_list
204  char *old = getenv (LD_LIBRARY_PATH[0]);
205  if (old)
206    ev = dbe_sprintf ("%s=%s:%s", LD_LIBRARY_PATH[0], libpath_list, old);
207  else
208    ev = dbe_sprintf ("%s=%s", LD_LIBRARY_PATH[0], libpath_list);
209  if (add_env (ev))
210    return 1;
211  return 0;
212}
213
214void
215collect::add_ld_preload (const char *lib)
216{
217  for (int ii = 0; SP_PRELOAD[ii]; ii++)
218    {
219      char *old_sp = sp_preload_list[ii];
220      if (old_sp == NULL)
221	sp_preload_list[ii] = strdup (lib);
222      else
223	{
224	  sp_preload_list[ii] = dbe_sprintf ("%s %s", old_sp, lib);
225	  free (old_sp);
226	}
227    }
228}
229
230int
231collect::putenv_memso ()
232{
233  // Set environment variable "MEM_USE_LOG" to 1, to keep it out of stderr
234  if (add_env (dbe_strdup ("MEM_USE_LOG=1")))
235    return 1;
236  // Set environment variable "MEM_ABORT_ON_ERROR", to force a core dump
237  if (add_env (dbe_strdup ("MEM_ABORT_ON_ERROR=1")))
238    return 1;
239  add_ld_preload ("mem.so");
240  return putenv_ld_preloads ();
241}
242
243// set LD_PRELOAD and friends to prepend the given library or libraries
244
245int
246collect::putenv_ld_preloads ()
247{
248  for (int ii = 0; LD_PRELOAD[ii]; ii++)
249    {
250      char *old_val = getenv (LD_PRELOAD[ii]);
251      int sp_num = ii;
252      assert (SP_PRELOAD[sp_num]);
253      char *preload_def;
254      if (old_val)
255	preload_def = dbe_sprintf ("%s=%s %s", LD_PRELOAD[ii], sp_preload_list[sp_num], old_val);
256      else
257	preload_def = dbe_sprintf ("%s=%s", LD_PRELOAD[ii], sp_preload_list[sp_num]);
258      if (add_env (preload_def))
259	return 1;
260    }
261  return 0;
262}
263
264/* copied from linetrace.c */
265/*
266   function: env_strip()
267     Finds str in env; Removes
268     all characters from previous ':' or ' '
269     up to and including any trailing ':' or ' '.
270   params:
271     env: environment variable
272     str: substring to find
273     return: count of instances removed from env
274 */
275int
276collect::env_strip (char *env, const char *str)
277{
278  int removed = 0;
279  char *p, *q;
280  if (env == NULL || str == NULL || *str == 0)
281    return 0;
282  size_t maxlen = strlen (env);
283  size_t len = strlen (str);
284  q = env;
285  while ((p = strstr (q, str)) != NULL)
286    {
287      q = p;
288      p += len;
289      if (*p)
290	{
291	  while ((*p) && (*p != ':') && (*p != ' '))
292	    p++;      /* skip the rest of the name*/
293	  while ((*p == ':') || (*p == ' '))
294	    p++; /* strip trailing separator */
295	}
296      while (*q != ':' && *q != ' ' && *q != '=' && q != env)
297	q--; /* strip path */
298      if (*p)
299	{ /* copy the rest of the string */
300	  if (q != env)
301	    q++; /* restore leading separator (if any) */
302	  size_t n = (maxlen - (q - env));
303	  strncpy (q, p, n);
304	}
305      else
306	*q = 0;
307      removed++;
308    }
309  return removed;
310}
311/*
312   function: putenv_purged_ld_preloads()
313     Remove selected preload strings from all LD_PRELOAD* env vars.
314   params:
315     var: executable name (leading characters don't have to match)
316     return: number of instances removed from all PRELOAD vars.
317 */
318int
319collect::putenv_purged_ld_preloads (const char *var)
320{
321  int total_removed = 0;
322  if (!var || *var == 0)
323    return 0;
324  for (int ii = 0; LD_PRELOAD[ii]; ii++)
325    {
326      char *ev = getenv (LD_PRELOAD[ii]);
327      int removed = 0;
328      if (!ev)
329	continue;
330      removed = env_strip (ev, var);
331      if (!removed)
332	continue;
333      if (putenv (ev) != 0)
334	dbe_write (2, GTXT ("Can't putenv of %s\n"), ev);
335      total_removed += removed;
336    }
337  return total_removed;
338}
339/*
340   function: putenv_append()
341     append string to current enviroment variable setting and then do a putenv()
342   params:
343     var: environment variable name
344     val: string to append
345 */
346int
347collect::putenv_append (const char *var, const char *val)
348{
349  char *ev;
350  if (!var || !val)
351    return 1;
352  const char *old_val = getenv (var);
353  if (old_val == NULL || *old_val == 0)
354    ev = dbe_sprintf ("%s=%s", var, val);
355  else
356    ev = dbe_sprintf ("%s=%s %s", var, old_val, val);
357
358  // now put the new variable into the environment
359  if (add_env (ev))
360    return 1;
361  return 0;
362}
363
364int
365collect::putenv_libcollector (void)
366{
367  char buf[MAXPATHLEN + 1];
368  // --- set SP_COLLECTOR_EXPNAME
369  // fetch the experiment name and CWD
370  char *exp = cc->get_experiment ();
371  char *cwd = getcwd (buf, MAXPATHLEN);
372  char *ev;
373
374  // format the environment variable for the experiment directory name
375  if (cwd != NULL && exp[0] != '/')  // experiment is a relative path
376    ev = dbe_sprintf ("%s=%s/%s", SP_COLLECTOR_EXPNAME, cwd, exp);
377  else      // getcwd failed or experiment is a fullpath
378    ev = dbe_sprintf ("%s=%s", SP_COLLECTOR_EXPNAME, exp);
379
380  // set the experiment directory name
381  if (add_env (ev))
382    return 1;
383
384  // --- set SP_COLLECTOR_PARAMS
385  // set the data descriptor
386  exp = cc->get_data_desc ();
387  if (add_env (dbe_sprintf ("%s=%s", SP_COLLECTOR_PARAMS, exp)))
388    return 1;
389
390  // --- set SP_COLLECTOR_FOLLOW_SPEC
391  const char *follow_spec = cc->get_follow_cmp_spec ();
392  if (follow_spec)
393    // selective following has been enabled
394    if (add_env (dbe_sprintf ("%s=%s", SP_COLLECTOR_FOLLOW_SPEC, follow_spec)))
395      return 1;
396
397  if (add_env (dbe_sprintf ("%s=%d", SP_COLLECTOR_FOUNDER, getpid ())))
398    return 1;
399  if (add_env (dbe_sprintf ("%s=1", SP_COLLECTOR_ORIGIN_COLLECT)))
400    return 1;
401
402  // --- set LD_*
403  if (putenv_libcollector_ld_misc ())
404    return 1;
405
406  // --- set LD_PRELOAD*
407  if (putenv_libcollector_ld_preloads () != 0)
408    return 1;
409
410  // --- set JAVA_TOOL_OPTIONS
411  if (cc->get_java_mode () == 1)
412    if (putenv_append ("JAVA_TOOL_OPTIONS", "-agentlib:gp-collector"))
413	exit (1);
414#if 0
415  // --- set LD_AUDIT*
416  if (putenv_libcollector_ld_audits () != 0)
417    return 1;
418#endif
419  return 0;
420}
421