about summary refs log tree commit diff
path: root/dlfcn/dlerror.c
blob: 5047b140662bc33eb511c9b3b53c4464de932160 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/* Return error detail for failing <dlfcn.h> functions.
   Copyright (C) 1995-2021 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <https://www.gnu.org/licenses/>.  */

#include <dlfcn.h>
#include <libintl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libc-lock.h>
#include <ldsodefs.h>
#include <libc-symbols.h>
#include <assert.h>
#include <dlerror.h>

char *
__dlerror (void)
{
# ifdef SHARED
  if (!rtld_active ())
    return GLRO (dl_dlfcn_hook)->dlerror ();
# endif

  struct dl_action_result *result = __libc_dlerror_result;

  /* No libdl function has been called.  No error is possible.  */
  if (result == NULL)
    return NULL;

  /* For an early malloc failure, clear the error flag and return the
     error message.  This marks the error as delivered.  */
  if (result == dl_action_result_malloc_failed)
    {
      __libc_dlerror_result = NULL;
      return (char *) "out of memory";
    }

  /* Placeholder object.  This can be observed in a recursive call,
     e.g. from an ELF constructor.  */
  if (result->errstring == NULL)
    return NULL;

  /* If we have already reported the error, we can free the result and
     return NULL.  See __libc_dlerror_result_free.  */
  if (result->returned)
    {
      __libc_dlerror_result = NULL;
      dl_action_result_errstring_free (result);
      free (result);
      return NULL;
    }

  assert (result->errstring != NULL);

  /* Create the combined error message.  */
  char *buf;
  int n;
  if (result->errcode == 0)
    n = __asprintf (&buf, "%s%s%s",
		    result->objname,
		    result->objname[0] == '\0' ? "" : ": ",
		    _(result->errstring));
  else
    {
      __set_errno (result->errcode);
      n = __asprintf (&buf, "%s%s%s: %m",
		      result->objname,
		      result->objname[0] == '\0' ? "" : ": ",
		      _(result->errstring));
      /* Set errno again in case asprintf clobbered it.  */
      __set_errno (result->errcode);
    }

  /* Mark the error as delivered.  */
  result->returned = true;

  if (n >= 0)
    {
      /* Replace the error string with the newly allocated one.  */
      dl_action_result_errstring_free (result);
      result->errstring = buf;
      result->errstring_source = dl_action_result_errstring_local;
      return buf;
    }
  else
    /* We could not create the combined error message, so use the
       existing string as a fallback.  */
    return result->errstring;
}
versioned_symbol (libc, __dlerror, dlerror, GLIBC_2_34);

#if OTHER_SHLIB_COMPAT (libdl, GLIBC_2_0, GLIBC_2_34)
compat_symbol (libdl, __dlerror, dlerror, GLIBC_2_0);
#endif

int
_dlerror_run (void (*operate) (void *), void *args)
{
  struct dl_action_result *result = __libc_dlerror_result;
  if (result != NULL)
    {
      if (result == dl_action_result_malloc_failed)
	{
	  /* Clear the previous error.  */
	  __libc_dlerror_result = NULL;
	  result = NULL;
	}
      else
	{
	  /* There is an existing object.  Free its error string, but
	     keep the object.  */
	  dl_action_result_errstring_free (result);
	  /* Mark the object as not containing an error.  This ensures
	     that call to dlerror from, for example, an ELF
	     constructor will not notice this result object.  */
	  result->errstring = NULL;
	}
    }

  const char *objname;
  const char *errstring;
  bool malloced;
  int errcode = GLRO (dl_catch_error) (&objname, &errstring, &malloced,
				       operate, args);

  /* ELF constructors or destructors may have indirectly altered the
     value of __libc_dlerror_result, therefore reload it.  */
  result = __libc_dlerror_result;

  if (errstring == NULL)
    {
      /* There is no error.  We no longer need the result object if it
	 does not contain an error.  However, a recursive call may
	 have added an error even if this call did not cause it.  Keep
	 the other error.  */
      if (result != NULL && result->errstring == NULL)
	{
	  __libc_dlerror_result = NULL;
	  free (result);
	}
      return 0;
    }
  else
    {
      /* A new error occurred.  Check if a result object has to be
	 allocated.  */
      if (result == NULL || result == dl_action_result_malloc_failed)
	{
	  /* Allocating storage for the error message after the fact
	     is not ideal.  But this avoids an infinite recursion in
	     case malloc itself calls libdl functions (without
	     triggering errors).  */
	  result = malloc (sizeof (*result));
	  if (result == NULL)
	    {
	      /* Assume that the dlfcn failure was due to a malloc
		 failure, too.  */
	      if (malloced)
		dl_error_free ((char *) errstring);
	      __libc_dlerror_result = dl_action_result_malloc_failed;
	      return 1;
	    }
	  __libc_dlerror_result = result;
	}
      else
	/* Deallocate the existing error message from a recursive
	   call, but reuse the result object.  */
	dl_action_result_errstring_free (result);

      result->errcode = errcode;
      result->objname = objname;
      result->errstring = (char *) errstring;
      result->returned = false;
      /* In case of an error, the malloced flag indicates whether the
	 error string is constant or not.  */
      if (malloced)
	result->errstring_source = dl_action_result_errstring_rtld;
      else
	result->errstring_source = dl_action_result_errstring_constant;

      return 1;
    }
}