util: Fix a possible null pointer dereference
[samba.git] / lib / util / talloc_report.c
1 /*
2  * talloc_report into a string
3  *
4  * Copyright Volker Lendecke <vl@samba.org> 2015
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, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "replace.h"
21 #include "talloc_report.h"
22
23 /*
24  * talloc_vasprintf into a buffer that doubles its size. The real string
25  * length is maintained in "pstr_len".
26  */
27
28 static char *talloc_vasprintf_append_largebuf(char *buf, ssize_t *pstr_len,
29                                               const char *fmt, va_list ap)
30 {
31         ssize_t str_len = *pstr_len;
32         size_t buflen, needed, space;
33         char *start, *tmpbuf;
34         va_list ap2;
35         int printlen;
36
37         if (str_len == -1) {
38                 return NULL;
39         }
40         if (buf == NULL) {
41                 return NULL;
42         }
43         if (fmt == NULL) {
44                 return NULL;
45         }
46         buflen = talloc_get_size(buf);
47
48         if (buflen > str_len) {
49                 start = buf + str_len;
50                 space = buflen - str_len;
51         } else {
52                 start = NULL;
53                 space = 0;
54         }
55
56         va_copy(ap2, ap);
57         printlen = vsnprintf(start, space, fmt, ap2);
58         va_end(ap2);
59
60         if (printlen < 0) {
61                 goto fail;
62         }
63
64         needed = str_len + printlen + 1;
65
66         if (needed > buflen) {
67                 buflen = MAX(128, buflen);
68
69                 while (buflen < needed) {
70                         buflen *= 2;
71                 }
72
73                 tmpbuf = talloc_realloc(NULL, buf, char, buflen);
74                 if (tmpbuf == NULL) {
75                         goto fail;
76                 }
77                 buf = tmpbuf;
78
79                 va_copy(ap2, ap);
80                 vsnprintf(buf + str_len, buflen - str_len, fmt, ap2);
81                 va_end(ap2);
82         }
83         *pstr_len = (needed - 1);
84         return buf;
85 fail:
86         *pstr_len = -1;
87         return buf;
88 }
89
90 static char *talloc_asprintf_append_largebuf(char *buf, ssize_t *pstr_len,
91                                              const char *fmt, ...)
92 {
93         va_list ap;
94
95         va_start(ap, fmt);
96         buf = talloc_vasprintf_append_largebuf(buf, pstr_len, fmt, ap);
97         va_end(ap);
98         return buf;
99 }
100
101 struct talloc_report_str_state {
102         ssize_t str_len;
103         char *s;
104 };
105
106 static void talloc_report_str_helper(const void *ptr, int depth, int max_depth,
107                                      int is_ref, void *private_data)
108 {
109         struct talloc_report_str_state *state = private_data;
110         const char *name = talloc_get_name(ptr);
111
112         if (ptr == state->s) {
113                 return;
114         }
115
116         if (is_ref) {
117                 state->s = talloc_asprintf_append_largebuf(
118                         state->s, &state->str_len,
119                         "%*sreference to: %s\n", depth*4, "", name);
120                 return;
121         }
122
123         if (depth == 0) {
124                 state->s = talloc_asprintf_append_largebuf(
125                         state->s, &state->str_len,
126                         "%stalloc report on '%s' "
127                         "(total %6lu bytes in %3lu blocks)\n",
128                         (max_depth < 0 ? "full " :""), name,
129                         (unsigned long)talloc_total_size(ptr),
130                         (unsigned long)talloc_total_blocks(ptr));
131                 return;
132         }
133
134         if (strcmp(name, "char") == 0) {
135                 /*
136                  * Print out the first 50 bytes of the string
137                  */
138                 state->s = talloc_asprintf_append_largebuf(
139                         state->s, &state->str_len,
140                         "%*s%-30s contains %6lu bytes in %3lu blocks "
141                         "(ref %d): %*s\n", depth*4, "", name,
142                         (unsigned long)talloc_total_size(ptr),
143                         (unsigned long)talloc_total_blocks(ptr),
144                         talloc_reference_count(ptr),
145                         MIN(50, talloc_get_size(ptr)),
146                         (const char *)ptr);
147                 return;
148         }
149
150         state->s = talloc_asprintf_append_largebuf(
151                 state->s, &state->str_len,
152                 "%*s%-30s contains %6lu bytes in %3lu blocks (ref %d) %p\n",
153                 depth*4, "", name,
154                 (unsigned long)talloc_total_size(ptr),
155                 (unsigned long)talloc_total_blocks(ptr),
156                 talloc_reference_count(ptr), ptr);
157 }
158
159 char *talloc_report_str(TALLOC_CTX *mem_ctx, TALLOC_CTX *root)
160 {
161         struct talloc_report_str_state state;
162
163         state.s = talloc_strdup(mem_ctx, "");
164         if (state.s == NULL) {
165                 return NULL;
166         }
167         state.str_len = 0;
168
169         talloc_report_depth_cb(root, 0, -1, talloc_report_str_helper, &state);
170
171         if (state.str_len == -1) {
172                 talloc_free(state.s);
173                 return NULL;
174         }
175
176         return talloc_realloc(mem_ctx, state.s, char, state.str_len+1);
177 }