1 /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
2
3 #include <errno.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <sys/utsname.h>
8 #include <zlib.h>
9
10 #include "trace_helpers.h"
11 #include "btf_helpers.h"
12
13 extern unsigned char _binary_min_core_btfs_tar_gz_start[] __attribute__((weak));
14 extern unsigned char _binary_min_core_btfs_tar_gz_end[] __attribute__((weak));
15
16 #define FIELD_LEN 65
17 #define ID_FMT "ID=%64s"
18 #define VERSION_FMT "VERSION_ID=\"%64s"
19
20 struct os_info {
21 char id[FIELD_LEN];
22 char version[FIELD_LEN];
23 char arch[FIELD_LEN];
24 char kernel_release[FIELD_LEN];
25 };
26
get_os_info()27 static struct os_info * get_os_info()
28 {
29 struct os_info *info = NULL;
30 struct utsname u;
31 size_t len = 0;
32 ssize_t read;
33 char *line = NULL;
34 FILE *f;
35
36 if (uname(&u) == -1)
37 return NULL;
38
39 f = fopen("/etc/os-release", "r");
40 if (!f)
41 return NULL;
42
43 info = calloc(1, sizeof(*info));
44 if (!info)
45 goto out;
46
47 strncpy(info->kernel_release, u.release, FIELD_LEN);
48 strncpy(info->arch, u.machine, FIELD_LEN);
49
50 while ((read = getline(&line, &len, f)) != -1) {
51 if (sscanf(line, ID_FMT, info->id) == 1)
52 continue;
53
54 if (sscanf(line, VERSION_FMT, info->version) == 1) {
55 /* remove '"' suffix */
56 info->version[strlen(info->version) - 1] = 0;
57 continue;
58 }
59 }
60
61 out:
62 free(line);
63 fclose(f);
64
65 return info;
66 }
67
68 #define INITIAL_BUF_SIZE (1024 * 1024 * 4) /* 4MB */
69
70 /* adapted from https://zlib.net/zlib_how.html */
71 static int
inflate_gz(unsigned char * src,int src_size,unsigned char ** dst,int * dst_size)72 inflate_gz(unsigned char *src, int src_size, unsigned char **dst, int *dst_size)
73 {
74 size_t size = INITIAL_BUF_SIZE;
75 size_t next_size = size;
76 z_stream strm;
77 void *tmp;
78 int ret;
79
80 strm.zalloc = Z_NULL;
81 strm.zfree = Z_NULL;
82 strm.opaque = Z_NULL;
83 strm.avail_in = 0;
84 strm.next_in = Z_NULL;
85
86 ret = inflateInit2(&strm, 16 + MAX_WBITS);
87 if (ret != Z_OK)
88 return -EINVAL;
89
90 *dst = malloc(size);
91 if (!*dst)
92 return -ENOMEM;
93
94 strm.next_in = src;
95 strm.avail_in = src_size;
96
97 /* run inflate() on input until it returns Z_STREAM_END */
98 do {
99 strm.next_out = *dst + strm.total_out;
100 strm.avail_out = next_size;
101 ret = inflate(&strm, Z_NO_FLUSH);
102 if (ret != Z_OK && ret != Z_STREAM_END)
103 goto out_err;
104 /* we need more space */
105 if (strm.avail_out == 0) {
106 next_size = size;
107 size *= 2;
108 tmp = realloc(*dst, size);
109 if (!tmp) {
110 ret = -ENOMEM;
111 goto out_err;
112 }
113 *dst = tmp;
114 }
115 } while (ret != Z_STREAM_END);
116
117 *dst_size = strm.total_out;
118
119 /* clean up and return */
120 ret = inflateEnd(&strm);
121 if (ret != Z_OK) {
122 ret = -EINVAL;
123 goto out_err;
124 }
125 return 0;
126
127 out_err:
128 free(*dst);
129 *dst = NULL;
130 return ret;
131 }
132
133 /* tar header from https://github.com/tklauser/libtar/blob/v1.2.20/lib/libtar.h#L39-L60 */
134 struct tar_header {
135 char name[100];
136 char mode[8];
137 char uid[8];
138 char gid[8];
139 char size[12];
140 char mtime[12];
141 char chksum[8];
142 char typeflag;
143 char linkname[100];
144 char magic[6];
145 char version[2];
146 char uname[32];
147 char gname[32];
148 char devmajor[8];
149 char devminor[8];
150 char prefix[155];
151 char padding[12];
152 };
153
tar_file_start(struct tar_header * tar,const char * name,int * length)154 static char *tar_file_start(struct tar_header *tar, const char *name, int *length)
155 {
156 while (tar->name[0]) {
157 sscanf(tar->size, "%o", length);
158 if (!strcmp(tar->name, name))
159 return (char *)(tar + 1);
160 tar += 1 + (*length + 511)/512;
161 }
162 return NULL;
163 }
164
ensure_core_btf(struct bpf_object_open_opts * opts)165 int ensure_core_btf(struct bpf_object_open_opts *opts)
166 {
167 char name_fmt[] = "./%s/%s/%s/%s.btf";
168 char btf_path[] = "/tmp/bcc-libbpf-tools.btf.XXXXXX";
169 struct os_info *info = NULL;
170 unsigned char *dst_buf = NULL;
171 char *file_start;
172 int dst_size = 0;
173 char name[100];
174 FILE *dst = NULL;
175 int ret;
176
177 /* do nothing if the system provides BTF */
178 if (vmlinux_btf_exists())
179 return 0;
180
181 /* compiled without min core btfs */
182 if (!_binary_min_core_btfs_tar_gz_start)
183 return -EOPNOTSUPP;
184
185 info = get_os_info();
186 if (!info)
187 return -errno;
188
189 ret = mkstemp(btf_path);
190 if (ret < 0) {
191 ret = -errno;
192 goto out;
193 }
194
195 dst = fdopen(ret, "wb");
196 if (!dst) {
197 ret = -errno;
198 goto out;
199 }
200
201 ret = snprintf(name, sizeof(name), name_fmt, info->id, info->version,
202 info->arch, info->kernel_release);
203 if (ret < 0 || ret == sizeof(name)) {
204 ret = -EINVAL;
205 goto out;
206 }
207
208 ret = inflate_gz(_binary_min_core_btfs_tar_gz_start,
209 _binary_min_core_btfs_tar_gz_end - _binary_min_core_btfs_tar_gz_start,
210 &dst_buf, &dst_size);
211 if (ret < 0)
212 goto out;
213
214 ret = 0;
215 file_start = tar_file_start((struct tar_header *)dst_buf, name, &dst_size);
216 if (!file_start) {
217 ret = -EINVAL;
218 goto out;
219 }
220
221 if (fwrite(file_start, 1, dst_size, dst) != dst_size) {
222 ret = -ferror(dst);
223 goto out;
224 }
225
226 opts->btf_custom_path = strdup(btf_path);
227 if (!opts->btf_custom_path)
228 ret = -ENOMEM;
229
230 out:
231 free(info);
232 fclose(dst);
233 free(dst_buf);
234
235 return ret;
236 }
237
cleanup_core_btf(struct bpf_object_open_opts * opts)238 void cleanup_core_btf(struct bpf_object_open_opts *opts) {
239 if (!opts)
240 return;
241
242 if (!opts->btf_custom_path)
243 return;
244
245 unlink(opts->btf_custom_path);
246 free((void *)opts->btf_custom_path);
247 }
248