/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ #include #include #include #include #include #include #include "trace_helpers.h" #include "btf_helpers.h" extern unsigned char _binary_min_core_btfs_tar_gz_start[] __attribute__((weak)); extern unsigned char _binary_min_core_btfs_tar_gz_end[] __attribute__((weak)); #define FIELD_LEN 65 #define ID_FMT "ID=%64s" #define VERSION_FMT "VERSION_ID=\"%64s" struct os_info { char id[FIELD_LEN]; char version[FIELD_LEN]; char arch[FIELD_LEN]; char kernel_release[FIELD_LEN]; }; static struct os_info * get_os_info() { struct os_info *info = NULL; struct utsname u; size_t len = 0; ssize_t read; char *line = NULL; FILE *f; if (uname(&u) == -1) return NULL; f = fopen("/etc/os-release", "r"); if (!f) return NULL; info = calloc(1, sizeof(*info)); if (!info) goto out; strncpy(info->kernel_release, u.release, FIELD_LEN); strncpy(info->arch, u.machine, FIELD_LEN); while ((read = getline(&line, &len, f)) != -1) { if (sscanf(line, ID_FMT, info->id) == 1) continue; if (sscanf(line, VERSION_FMT, info->version) == 1) { /* remove '"' suffix */ info->version[strlen(info->version) - 1] = 0; continue; } } out: free(line); fclose(f); return info; } #define INITIAL_BUF_SIZE (1024 * 1024 * 4) /* 4MB */ /* adapted from https://zlib.net/zlib_how.html */ static int inflate_gz(unsigned char *src, int src_size, unsigned char **dst, int *dst_size) { size_t size = INITIAL_BUF_SIZE; size_t next_size = size; z_stream strm; void *tmp; int ret; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = 0; strm.next_in = Z_NULL; ret = inflateInit2(&strm, 16 + MAX_WBITS); if (ret != Z_OK) return -EINVAL; *dst = malloc(size); if (!*dst) return -ENOMEM; strm.next_in = src; strm.avail_in = src_size; /* run inflate() on input until it returns Z_STREAM_END */ do { strm.next_out = *dst + strm.total_out; strm.avail_out = next_size; ret = inflate(&strm, Z_NO_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) goto out_err; /* we need more space */ if (strm.avail_out == 0) { next_size = size; size *= 2; tmp = realloc(*dst, size); if (!tmp) { ret = -ENOMEM; goto out_err; } *dst = tmp; } } while (ret != Z_STREAM_END); *dst_size = strm.total_out; /* clean up and return */ ret = inflateEnd(&strm); if (ret != Z_OK) { ret = -EINVAL; goto out_err; } return 0; out_err: free(*dst); *dst = NULL; return ret; } /* tar header from https://github.com/tklauser/libtar/blob/v1.2.20/lib/libtar.h#L39-L60 */ struct tar_header { char name[100]; char mode[8]; char uid[8]; char gid[8]; char size[12]; char mtime[12]; char chksum[8]; char typeflag; char linkname[100]; char magic[6]; char version[2]; char uname[32]; char gname[32]; char devmajor[8]; char devminor[8]; char prefix[155]; char padding[12]; }; static char *tar_file_start(struct tar_header *tar, const char *name, int *length) { while (tar->name[0]) { sscanf(tar->size, "%o", length); if (!strcmp(tar->name, name)) return (char *)(tar + 1); tar += 1 + (*length + 511)/512; } return NULL; } int ensure_core_btf(struct bpf_object_open_opts *opts) { char name_fmt[] = "./%s/%s/%s/%s.btf"; char btf_path[] = "/tmp/bcc-libbpf-tools.btf.XXXXXX"; struct os_info *info = NULL; unsigned char *dst_buf = NULL; char *file_start; int dst_size = 0; char name[100]; FILE *dst = NULL; int ret; /* do nothing if the system provides BTF */ if (vmlinux_btf_exists()) return 0; /* compiled without min core btfs */ if (!_binary_min_core_btfs_tar_gz_start) return -EOPNOTSUPP; info = get_os_info(); if (!info) return -errno; ret = mkstemp(btf_path); if (ret < 0) { ret = -errno; goto out; } dst = fdopen(ret, "wb"); if (!dst) { ret = -errno; goto out; } ret = snprintf(name, sizeof(name), name_fmt, info->id, info->version, info->arch, info->kernel_release); if (ret < 0 || ret == sizeof(name)) { ret = -EINVAL; goto out; } ret = inflate_gz(_binary_min_core_btfs_tar_gz_start, _binary_min_core_btfs_tar_gz_end - _binary_min_core_btfs_tar_gz_start, &dst_buf, &dst_size); if (ret < 0) goto out; ret = 0; file_start = tar_file_start((struct tar_header *)dst_buf, name, &dst_size); if (!file_start) { ret = -EINVAL; goto out; } if (fwrite(file_start, 1, dst_size, dst) != dst_size) { ret = -ferror(dst); goto out; } opts->btf_custom_path = strdup(btf_path); if (!opts->btf_custom_path) ret = -ENOMEM; out: free(info); fclose(dst); free(dst_buf); return ret; } void cleanup_core_btf(struct bpf_object_open_opts *opts) { if (!opts) return; if (!opts->btf_custom_path) return; unlink(opts->btf_custom_path); free((void *)opts->btf_custom_path); }