1/* libunwind - a platform-independent unwind library
2   Copyright (C) 2003-2004 Hewlett-Packard Co
3   Copyright (C) 2007 David Mosberger-Tang
4        Contributed by David Mosberger-Tang <dmosberger@gmail.com>
5
6This file is part of libunwind.
7
8Permission is hereby granted, free of charge, to any person obtaining
9a copy of this software and associated documentation files (the
10"Software"), to deal in the Software without restriction, including
11without limitation the rights to use, copy, modify, merge, publish,
12distribute, sublicense, and/or sell copies of the Software, and to
13permit persons to whom the Software is furnished to do so, subject to
14the following conditions:
15
16The above copyright notice and this permission notice shall be
17included in all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
26
27#ifndef os_linux_h
28#define os_linux_h
29
30struct map_iterator
31  {
32    off_t offset;
33    int fd;
34    size_t buf_size;
35    char *buf;
36    char *buf_end;
37    char *path;
38  };
39
40static inline char *
41ltoa (char *buf, long val)
42{
43  char *cp = buf, tmp;
44  ssize_t i, len;
45
46  do
47    {
48      *cp++ = '0' + (val % 10);
49      val /= 10;
50    }
51  while (val);
52
53  /* reverse the order of the digits: */
54  len = cp - buf;
55  --cp;
56  for (i = 0; i < len / 2; ++i)
57    {
58      tmp = buf[i];
59      buf[i] = cp[-i];
60      cp[-i] = tmp;
61    }
62  return buf + len;
63}
64
65static inline int
66maps_init (struct map_iterator *mi, pid_t pid)
67{
68  char path[sizeof ("/proc/0123456789/maps")], *cp;
69
70  memcpy (path, "/proc/", 6);
71  cp = ltoa (path + 6, pid);
72  assert (cp + 6 < path + sizeof (path));
73  memcpy (cp, "/maps", 6);
74
75  mi->fd = open (path, O_RDONLY);
76  if (mi->fd >= 0)
77    {
78      /* Try to allocate a page-sized buffer.  */
79      mi->buf_size = getpagesize ();
80      cp = mmap (NULL, mi->buf_size, PROT_READ | PROT_WRITE,
81                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
82      if (cp == MAP_FAILED)
83        {
84          close(mi->fd);
85          mi->fd = -1;
86          return -1;
87        }
88      else
89        {
90          mi->offset = 0;
91          mi->buf = mi->buf_end = cp + mi->buf_size;
92          return 0;
93        }
94    }
95  return -1;
96}
97
98static inline char *
99skip_whitespace (char *cp)
100{
101  if (!cp)
102    return NULL;
103
104  while (*cp == ' ' || *cp == '\t')
105    ++cp;
106  return cp;
107}
108
109static inline char *
110scan_hex (char *cp, unsigned long *valp)
111{
112  unsigned long num_digits = 0, digit, val = 0;
113
114  cp = skip_whitespace (cp);
115  if (!cp)
116    return NULL;
117
118  while (1)
119    {
120      digit = *cp;
121      if ((digit - '0') <= 9)
122        digit -= '0';
123      else if ((digit - 'a') < 6)
124        digit -= 'a' - 10;
125      else if ((digit - 'A') < 6)
126        digit -= 'A' - 10;
127      else
128        break;
129      val = (val << 4) | digit;
130      ++num_digits;
131      ++cp;
132    }
133  if (!num_digits)
134    return NULL;
135  *valp = val;
136  return cp;
137}
138
139static inline char *
140scan_dec (char *cp, unsigned long *valp)
141{
142  unsigned long num_digits = 0, digit, val = 0;
143
144  if (!(cp = skip_whitespace (cp)))
145    return NULL;
146
147  while (1)
148    {
149      digit = *cp;
150      if ((digit - '0') <= 9)
151        {
152          digit -= '0';
153          ++cp;
154        }
155      else
156        break;
157      val = (10 * val) + digit;
158      ++num_digits;
159    }
160  if (!num_digits)
161    return NULL;
162  *valp = val;
163  return cp;
164}
165
166static inline char *
167scan_char (char *cp, char *valp)
168{
169  if (!cp)
170    return NULL;
171
172  *valp = *cp;
173
174  /* don't step over NUL terminator */
175  if (*cp)
176    ++cp;
177  return cp;
178}
179
180/* Scan a string delimited by white-space.  Fails on empty string or
181   if string doesn't fit in the specified buffer.  */
182static inline char *
183scan_string (char *cp, char *valp, size_t buf_size)
184{
185  size_t i = 0;
186
187  if (!(cp = skip_whitespace (cp)))
188    return NULL;
189
190  while (*cp != ' ' && *cp != '\t' && *cp != '\0')
191    {
192      if ((valp != NULL) && (i < buf_size - 1))
193        valp[i++] = *cp;
194      ++cp;
195    }
196  if (i == 0 || i >= buf_size)
197    return NULL;
198  if (valp != NULL)
199    valp[i] = '\0';
200  return cp;
201}
202
203static inline int
204maps_next (struct map_iterator *mi,
205           unsigned long *low, unsigned long *high, unsigned long *offset)
206{
207  char perm[16], dash = 0, colon = 0, *cp;
208  unsigned long major, minor, inum;
209  ssize_t i, nread;
210
211  if (mi->fd < 0)
212    return 0;
213
214  while (1)
215    {
216      ssize_t bytes_left = mi->buf_end - mi->buf;
217      char *eol = NULL;
218
219      for (i = 0; i < bytes_left; ++i)
220        {
221          if (mi->buf[i] == '\n')
222            {
223              eol = mi->buf + i;
224              break;
225            }
226          else if (mi->buf[i] == '\0')
227            break;
228        }
229      if (!eol)
230        {
231          /* copy down the remaining bytes, if any */
232          if (bytes_left > 0)
233            memmove (mi->buf_end - mi->buf_size, mi->buf, bytes_left);
234
235          mi->buf = mi->buf_end - mi->buf_size;
236          nread = read (mi->fd, mi->buf + bytes_left,
237                        mi->buf_size - bytes_left);
238          if (nread <= 0)
239            return 0;
240          else if ((size_t) (nread + bytes_left) < mi->buf_size)
241            {
242              /* Move contents to the end of the buffer so we
243                 maintain the invariant that all bytes between
244                 mi->buf and mi->buf_end are valid.  */
245              memmove (mi->buf_end - nread - bytes_left, mi->buf,
246                       nread + bytes_left);
247              mi->buf = mi->buf_end - nread - bytes_left;
248            }
249
250          eol = mi->buf + bytes_left + nread - 1;
251
252          for (i = bytes_left; i < bytes_left + nread; ++i)
253            if (mi->buf[i] == '\n')
254              {
255                eol = mi->buf + i;
256                break;
257              }
258        }
259      cp = mi->buf;
260      mi->buf = eol + 1;
261      *eol = '\0';
262
263      /* scan: "LOW-HIGH PERM OFFSET MAJOR:MINOR INUM PATH" */
264      cp = scan_hex (cp, low);
265      cp = scan_char (cp, &dash);
266      cp = scan_hex (cp, high);
267      cp = scan_string (cp, perm, sizeof (perm));
268      cp = scan_hex (cp, offset);
269      cp = scan_hex (cp, &major);
270      cp = scan_char (cp, &colon);
271      cp = scan_hex (cp, &minor);
272      cp = scan_dec (cp, &inum);
273      cp = mi->path = skip_whitespace (cp);
274      if (!cp)
275        continue;
276      cp = scan_string (cp, NULL, 0);
277      if (dash != '-' || colon != ':')
278        continue;       /* skip line with unknown or bad format */
279      return 1;
280    }
281  return 0;
282}
283
284static inline void
285maps_close (struct map_iterator *mi)
286{
287  if (mi->fd < 0)
288    return;
289  close (mi->fd);
290  mi->fd = -1;
291  if (mi->buf)
292    {
293      munmap (mi->buf_end - mi->buf_size, mi->buf_size);
294      mi->buf = mi->buf_end = NULL;
295    }
296}
297
298#endif /* os_linux_h */
299