1libtalloc_bestpractices(3)          talloc          libtalloc_bestpractices(3)
2
3
4

NAME

6       libtalloc_bestpractices - Chapter 7: Best practises
7
8
9       The following sections contain several best practices and good manners
10       that were found by the Samba and SSSD developers over the years.
11
12       These will help you to write code which is better, easier to debug and
13       with as few (hopefully none) memory leaks as possible.
14

Keep the context hierarchy steady

16       The talloc is a hierarchy memory allocator. The hierarchy nature is
17       what makes the programming more error proof. It makes the memory easier
18       to manage and to free. Therefore, the first thing we should have on our
19       mind is: always project your data structures into the talloc context
20       hierarchy.
21
22       That means if we have a structure, we should always use it as a parent
23       context for its elements. This way we will not encounter any troubles
24       when freeing the structure or when changing its parent. The same rule
25       applies for arrays.
26
27       For example, the structure user from section Hierarchy of talloc
28       context should be created with the context hierarchy illustrated on the
29       next image.
30

Every function should use its own context

32       It is a good practice to create a temporary talloc context at the
33       function beginning and free the context just before the return
34       statement. All the data must be allocated on this context or on its
35       children. This ensures that no memory leaks are created as long as we
36       do not forget to free the temporary context.
37
38       This pattern applies to both situations - when a function does not
39       return any dynamically allocated value and when it does. However, it
40       needs a little extension for the latter case.
41
42   Functions that do not return any dynamically allocated
43       value
44
45       If the function does not return any value created on the heap, we will
46       just obey the aforementioned pattern.
47
48       int bar()
49       {
50         int ret;
51         TALLOC_CTX *tmp_ctx = talloc_new(NULL);
52         if (tmp_ctx == NULL) {
53           ret = ENOMEM;
54           goto done;
55         }
56         /* allocate data on tmp_ctx or on its descendants */
57         ret = EOK;
58       done:
59         talloc_free(tmp_ctx);
60         return ret;
61       }
62
63   Functions returning dynamically allocated values
64       If our function returns any dynamically allocated data, its first
65       parameter should always be the destination talloc context. This context
66       serves as a parent for the output values. But again, we will create the
67       output values as the descendants of the temporary context. If
68       everything goes well, we will change the parent of the output values
69       from the temporary to the destination talloc context.
70
71       This pattern ensures that if an error occurs (e.g. I/O error or
72       insufficient amount of the memory), all allocated data is freed and no
73       garbage appears on the destination context.
74
75       int struct_foo_init(TALLOC_CTX *mem_ctx, struct foo **_foo)
76       {
77         int ret;
78         struct foo *foo = NULL;
79         TALLOC_CTX *tmp_ctx = talloc_new(NULL);
80         if (tmp_ctx == NULL) {
81           ret = ENOMEM;
82           goto done;
83         }
84         foo = talloc_zero(tmp_ctx, struct foo);
85         /* ... */
86         *_foo = talloc_steal(mem_ctx, foo);
87         ret = EOK;
88       done:
89         talloc_free(tmp_ctx);
90         return ret;
91       }
92

Allocate temporary contexts on NULL

94       As it can be seen on the previous listing, instead of allocating the
95       temporary context directly on mem_ctx, we created a new top level
96       context using NULL as the parameter for talloc_new() function. Take a
97       look at the following example:
98
99       char *create_user_filter(TALLOC_CTX *mem_ctx,
100                                uid_t uid, const char *username)
101       {
102         char *filter = NULL;
103         char *sanitized_username = NULL;
104         /* tmp_ctx is a child of mem_ctx */
105         TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
106         if (tmp_ctx == NULL) {
107           return NULL;
108         }
109
110         sanitized_username = sanitize_string(tmp_ctx, username);
111         if (sanitized_username == NULL) {
112           talloc_free(tmp_ctx);
113           return NULL;
114         }
115
116         filter = talloc_aprintf(tmp_ctx,"(|(uid=%llu)(uname=%s))",
117                                 uid, sanitized_username);
118         if (filter == NULL) {
119           return NULL; /* tmp_ctx is not freed */ (*@el{lst:tmp-ctx-3:leak}@*)
120         }
121
122         /* filter becomes a child of mem_ctx */
123         filter = talloc_steal(mem_ctx, filter);
124         talloc_free(tmp_ctx);
125         return filter;
126       }
127
128       We forgot to free tmp_ctx before the return statement in the filter ==
129       NULL condition. However, it is created as a child of mem_ctx context
130       and as such it will be freed as soon as the mem_ctx is freed.
131       Therefore, no detectable memory leak is created.
132
133       On the other hand, we do not have any way to access the allocated data
134       and for all we know mem_ctx may exist for the lifetime of our
135       application. For these reasons this should be considered as a memory
136       leak. How can we detect if it is unreferenced but still attached to its
137       parent context? The only way is to notice the mistake in the source
138       code.
139
140       But if we create the temporary context as a top level context, it will
141       not be freed and memory diagnostic tools (e.g. valgrind) are able to do
142       their job.
143

Temporary contexts and the talloc pool

145       If we want to take the advantage of the talloc pool but also keep to
146       the pattern introduced in the previous section, we are unable to do it
147       directly. The best thing to do is to create a conditional build where
148       we can decide how do we want to create the temporary context. For
149       example, we can create the following macros:
150
151       #ifdef USE_POOL_CONTEXT
152         #define CREATE_POOL_CTX(ctx, size) talloc_pool(ctx, size)
153         #define CREATE_TMP_CTX(ctx)        talloc_new(ctx)
154       #else
155         #define CREATE_POOL_CTX(ctx, size) talloc_new(ctx)
156         #define CREATE_TMP_CTX(ctx)        talloc_new(NULL)
157       #endif
158
159       Now if our application is under development, we will build it with
160       macro USE_POOL_CONTEXT undefined. This way, we can use memory
161       diagnostic utilities to detect memory leaks.
162
163       The release version will be compiled with the macro defined. This will
164       enable pool contexts and therefore reduce the malloc() calls, which
165       will end up in a little bit faster processing.
166
167       int struct_foo_init(TALLOC_CTX *mem_ctx, struct foo **_foo)
168       {
169         int ret;
170         struct foo *foo = NULL;
171         TALLOC_CTX *tmp_ctx = CREATE_TMP_CTX(mem_ctx);
172         /* ... */
173       }
174
175       errno_t handle_request(TALLOC_CTX mem_ctx)
176       {
177         int ret;
178         struct foo *foo = NULL;
179         TALLOC_CTX *pool_ctx = CREATE_POOL_CTX(NULL, 1024);
180         ret = struct_foo_init(mem_ctx, &foo);
181         /* ... */
182       }
183
184Version 2.0                     Sat May 11 2019     libtalloc_bestpractices(3)
Impressum