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 is 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  valp[i] = '\0';
199  return cp;
200}
201
202static inline int
203maps_next (struct map_iterator *mi,
204           unsigned long *low, unsigned long *high, unsigned long *offset)
205{
206  char perm[16], dash = 0, colon = 0, *cp;
207  unsigned long major, minor, inum;
208  ssize_t i, nread;
209
210  if (mi->fd < 0)
211    return 0;
212
213  while (1)
214    {
215      ssize_t bytes_left = mi->buf_end - mi->buf;
216      char *eol = NULL;
217
218      for (i = 0; i < bytes_left; ++i)
219        {
220          if (mi->buf[i] == '\n')
221            {
222              eol = mi->buf + i;
223              break;
224            }
225          else if (mi->buf[i] == '\0')
226            break;
227        }
228      if (!eol)
229        {
230          /* copy down the remaining bytes, if any */
231          if (bytes_left > 0)
232            memmove (mi->buf_end - mi->buf_size, mi->buf, bytes_left);
233
234          mi->buf = mi->buf_end - mi->buf_size;
235          nread = read (mi->fd, mi->buf + bytes_left,
236                        mi->buf_size - bytes_left);
237          if (nread <= 0)
238            return 0;
239          else if ((size_t) (nread + bytes_left) < mi->buf_size)
240            {
241              /* Move contents to the end of the buffer so we
242                 maintain the invariant that all bytes between
243                 mi->buf and mi->buf_end are valid.  */
244              memmove (mi->buf_end - nread - bytes_left, mi->buf,
245                       nread + bytes_left);
246              mi->buf = mi->buf_end - nread - bytes_left;
247            }
248
249          eol = mi->buf + bytes_left + nread - 1;
250
251          for (i = bytes_left; i < bytes_left + nread; ++i)
252            if (mi->buf[i] == '\n')
253              {
254                eol = mi->buf + i;
255                break;
256              }
257        }
258      cp = mi->buf;
259      mi->buf = eol + 1;
260      *eol = '\0';
261
262      /* scan: "LOW-HIGH PERM OFFSET MAJOR:MINOR INUM PATH" */
263      cp = scan_hex (cp, low);
264      cp = scan_char (cp, &dash);
265      cp = scan_hex (cp, high);
266      cp = scan_string (cp, perm, sizeof (perm));
267      cp = scan_hex (cp, offset);
268      cp = scan_hex (cp, &major);
269      cp = scan_char (cp, &colon);
270      cp = scan_hex (cp, &minor);
271      cp = scan_dec (cp, &inum);
272      cp = mi->path = skip_whitespace (cp);
273      if (!cp)
274        continue;
275      cp = scan_string (cp, NULL, 0);
276      if (dash != '-' || colon != ':')
277        continue;       /* skip line with unknown or bad format */
278      return 1;
279    }
280  return 0;
281}
282
283static inline void
284maps_close (struct map_iterator *mi)
285{
286  if (mi->fd < 0)
287    return;
288  close (mi->fd);
289  mi->fd = -1;
290  if (mi->buf)
291    {
292      munmap (mi->buf_end - mi->buf_size, mi->buf_size);
293      mi->buf = mi->buf_end = NULL;
294    }
295}
296
297#endif /* os_linux_h */
298