| 1 | /* |
| 2 | * CDDL HEADER START |
| 3 | * |
| 4 | * The contents of this file are subject to the terms of the |
| 5 | * Common Development and Distribution License, Version 1.0 only |
| 6 | * (the "License"). You may not use this file except in compliance |
| 7 | * with the License. |
| 8 | * |
| 9 | * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| 10 | * or http://www.opensolaris.org/os/licensing. |
| 11 | * See the License for the specific language governing permissions |
| 12 | * and limitations under the License. |
| 13 | * |
| 14 | * When distributing Covered Code, include this CDDL HEADER in each |
| 15 | * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| 16 | * If applicable, add the following below this CDDL HEADER, with the |
| 17 | * fields enclosed by brackets "[]" replaced with your own identifying |
| 18 | * information: Portions Copyright [yyyy] [name of copyright owner] |
| 19 | * |
| 20 | * CDDL HEADER END |
| 21 | */ |
| 22 | /* |
| 23 | * Copyright 2003 Sun Microsystems, Inc. All rights reserved. |
| 24 | * Use is subject to license terms. |
| 25 | */ |
| 26 | |
| 27 | #pragma ident "%Z%%M% %I% %E% SMI" |
| 28 | |
| 29 | #if HAVE_NBTOOL_CONFIG_H |
| 30 | # include "nbtool_config.h" |
| 31 | #endif |
| 32 | |
| 33 | #include <sys/types.h> |
| 34 | #include <sys/stat.h> |
| 35 | #include <sys/mman.h> |
| 36 | #include <sys/zmod.h> |
| 37 | #include <ctf_impl.h> |
| 38 | #include <unistd.h> |
| 39 | #include <fcntl.h> |
| 40 | #include <errno.h> |
| 41 | #ifdef illumos |
| 42 | #include <dlfcn.h> |
| 43 | #else |
| 44 | #include <zlib.h> |
| 45 | #endif |
| 46 | #include <gelf.h> |
| 47 | |
| 48 | #ifdef illumos |
| 49 | #ifdef _LP64 |
| 50 | static const char *_libctf_zlib = "/usr/lib/64/libz.so" ; |
| 51 | #else |
| 52 | static const char *_libctf_zlib = "/usr/lib/libz.so" ; |
| 53 | #endif |
| 54 | #endif |
| 55 | |
| 56 | static struct { |
| 57 | int (*z_uncompress)(uchar_t *, ulong_t *, const uchar_t *, ulong_t); |
| 58 | const char *(*z_error)(int); |
| 59 | void *z_dlp; |
| 60 | } zlib; |
| 61 | |
| 62 | static size_t _PAGESIZE; |
| 63 | static size_t _PAGEMASK; |
| 64 | |
| 65 | #ifdef illumos |
| 66 | #pragma init(_libctf_init) |
| 67 | #else |
| 68 | void _libctf_init(void) __attribute__ ((constructor)); |
| 69 | #endif |
| 70 | void |
| 71 | _libctf_init(void) |
| 72 | { |
| 73 | #ifdef illumos |
| 74 | const char *p = getenv("LIBCTF_DECOMPRESSOR" ); |
| 75 | |
| 76 | if (p != NULL) |
| 77 | _libctf_zlib = p; /* use alternate decompression library */ |
| 78 | #endif |
| 79 | |
| 80 | _libctf_debug = getenv("LIBCTF_DEBUG" ) != NULL; |
| 81 | |
| 82 | _PAGESIZE = getpagesize(); |
| 83 | _PAGEMASK = ~(_PAGESIZE - 1); |
| 84 | } |
| 85 | |
| 86 | /* |
| 87 | * Attempt to dlopen the decompression library and locate the symbols of |
| 88 | * interest that we will need to call. This information in cached so |
| 89 | * that multiple calls to ctf_bufopen() do not need to reopen the library. |
| 90 | */ |
| 91 | void * |
| 92 | ctf_zopen(int *errp) |
| 93 | { |
| 94 | #ifdef illumos |
| 95 | ctf_dprintf("decompressing CTF data using %s\n" , _libctf_zlib); |
| 96 | |
| 97 | if (zlib.z_dlp != NULL) |
| 98 | return (zlib.z_dlp); /* library is already loaded */ |
| 99 | |
| 100 | if (access(_libctf_zlib, R_OK) == -1) |
| 101 | return (ctf_set_open_errno(errp, ECTF_ZMISSING)); |
| 102 | |
| 103 | if ((zlib.z_dlp = dlopen(_libctf_zlib, RTLD_LAZY | RTLD_LOCAL)) == NULL) |
| 104 | return (ctf_set_open_errno(errp, ECTF_ZINIT)); |
| 105 | |
| 106 | zlib.z_uncompress = (int (*)(uchar_t *, ulong_t *, const uchar_t *, ulong_t)) dlsym(zlib.z_dlp, "uncompress" ); |
| 107 | zlib.z_error = (const char *(*)(int)) dlsym(zlib.z_dlp, "zError" ); |
| 108 | |
| 109 | if (zlib.z_uncompress == NULL || zlib.z_error == NULL) { |
| 110 | (void) dlclose(zlib.z_dlp); |
| 111 | bzero(&zlib, sizeof (zlib)); |
| 112 | return (ctf_set_open_errno(errp, ECTF_ZINIT)); |
| 113 | } |
| 114 | #else |
| 115 | zlib.z_uncompress = uncompress; |
| 116 | zlib.z_error = zError; |
| 117 | |
| 118 | /* Dummy return variable as 'no error' */ |
| 119 | zlib.z_dlp = (void *) (uintptr_t) 1; |
| 120 | #endif |
| 121 | |
| 122 | return (zlib.z_dlp); |
| 123 | } |
| 124 | |
| 125 | /* |
| 126 | * The ctf_bufopen() routine calls these subroutines, defined by <sys/zmod.h>, |
| 127 | * which we then patch through to the functions in the decompression library. |
| 128 | */ |
| 129 | int |
| 130 | z_uncompress(void *dst, size_t *dstlen, const void *src, size_t srclen) |
| 131 | { |
| 132 | return (zlib.z_uncompress(dst, (ulong_t *)dstlen, src, srclen)); |
| 133 | } |
| 134 | |
| 135 | const char * |
| 136 | z_strerror(int err) |
| 137 | { |
| 138 | return (zlib.z_error(err)); |
| 139 | } |
| 140 | |
| 141 | /* |
| 142 | * Convert a 32-bit ELF file header into GElf. |
| 143 | */ |
| 144 | static void |
| 145 | ehdr_to_gelf(const Elf32_Ehdr *src, GElf_Ehdr *dst) |
| 146 | { |
| 147 | bcopy(src->e_ident, dst->e_ident, EI_NIDENT); |
| 148 | dst->e_type = src->e_type; |
| 149 | dst->e_machine = src->e_machine; |
| 150 | dst->e_version = src->e_version; |
| 151 | dst->e_entry = (Elf64_Addr)src->e_entry; |
| 152 | dst->e_phoff = (Elf64_Off)src->e_phoff; |
| 153 | dst->e_shoff = (Elf64_Off)src->e_shoff; |
| 154 | dst->e_flags = src->e_flags; |
| 155 | dst->e_ehsize = src->e_ehsize; |
| 156 | dst->e_phentsize = src->e_phentsize; |
| 157 | dst->e_phnum = src->e_phnum; |
| 158 | dst->e_shentsize = src->e_shentsize; |
| 159 | dst->e_shnum = src->e_shnum; |
| 160 | dst->e_shstrndx = src->e_shstrndx; |
| 161 | } |
| 162 | |
| 163 | /* |
| 164 | * Convert a 32-bit ELF section header into GElf. |
| 165 | */ |
| 166 | static void |
| 167 | shdr_to_gelf(const Elf32_Shdr *src, GElf_Shdr *dst) |
| 168 | { |
| 169 | dst->sh_name = src->sh_name; |
| 170 | dst->sh_type = src->sh_type; |
| 171 | dst->sh_flags = src->sh_flags; |
| 172 | dst->sh_addr = src->sh_addr; |
| 173 | dst->sh_offset = src->sh_offset; |
| 174 | dst->sh_size = src->sh_size; |
| 175 | dst->sh_link = src->sh_link; |
| 176 | dst->sh_info = src->sh_info; |
| 177 | dst->sh_addralign = src->sh_addralign; |
| 178 | dst->sh_entsize = src->sh_entsize; |
| 179 | } |
| 180 | |
| 181 | /* |
| 182 | * In order to mmap a section from the ELF file, we must round down sh_offset |
| 183 | * to the previous page boundary, and mmap the surrounding page. We store |
| 184 | * the pointer to the start of the actual section data back into sp->cts_data. |
| 185 | */ |
| 186 | const void * |
| 187 | ctf_sect_mmap(ctf_sect_t *sp, int fd) |
| 188 | { |
| 189 | size_t pageoff = sp->cts_offset & ~_PAGEMASK; |
| 190 | |
| 191 | caddr_t base = mmap64(NULL, sp->cts_size + pageoff, PROT_READ, |
| 192 | MAP_PRIVATE, fd, sp->cts_offset & _PAGEMASK); |
| 193 | |
| 194 | if (base != MAP_FAILED) |
| 195 | sp->cts_data = base + pageoff; |
| 196 | |
| 197 | return (base); |
| 198 | } |
| 199 | |
| 200 | /* |
| 201 | * Since sp->cts_data has the adjusted offset, we have to again round down |
| 202 | * to get the actual mmap address and round up to get the size. |
| 203 | */ |
| 204 | void |
| 205 | ctf_sect_munmap(const ctf_sect_t *sp) |
| 206 | { |
| 207 | uintptr_t addr = (uintptr_t)sp->cts_data; |
| 208 | uintptr_t pageoff = addr & ~_PAGEMASK; |
| 209 | |
| 210 | (void) munmap((void *)(addr - pageoff), sp->cts_size + pageoff); |
| 211 | } |
| 212 | |
| 213 | /* |
| 214 | * Open the specified file descriptor and return a pointer to a CTF container. |
| 215 | * The file can be either an ELF file or raw CTF file. The caller is |
| 216 | * responsible for closing the file descriptor when it is no longer needed. |
| 217 | */ |
| 218 | ctf_file_t * |
| 219 | ctf_fdopen(int fd, int *errp) |
| 220 | { |
| 221 | ctf_sect_t ctfsect, symsect, strsect; |
| 222 | ctf_file_t *fp = NULL; |
| 223 | size_t shstrndx, shnum; |
| 224 | |
| 225 | struct stat64 st; |
| 226 | ssize_t nbytes; |
| 227 | |
| 228 | union { |
| 229 | ctf_preamble_t ctf; |
| 230 | Elf32_Ehdr e32; |
| 231 | GElf_Ehdr e64; |
| 232 | } hdr; |
| 233 | |
| 234 | bzero(&ctfsect, sizeof (ctf_sect_t)); |
| 235 | bzero(&symsect, sizeof (ctf_sect_t)); |
| 236 | bzero(&strsect, sizeof (ctf_sect_t)); |
| 237 | bzero(&hdr.ctf, sizeof (hdr)); |
| 238 | |
| 239 | if (fstat64(fd, &st) == -1) |
| 240 | return (ctf_set_open_errno(errp, errno)); |
| 241 | |
| 242 | if ((nbytes = pread64(fd, &hdr.ctf, sizeof (hdr), 0)) <= 0) |
| 243 | return (ctf_set_open_errno(errp, nbytes < 0? errno : ECTF_FMT)); |
| 244 | |
| 245 | /* |
| 246 | * If we have read enough bytes to form a CTF header and the magic |
| 247 | * string matches, attempt to interpret the file as raw CTF. |
| 248 | */ |
| 249 | if (nbytes >= (ssize_t) sizeof (ctf_preamble_t) && |
| 250 | hdr.ctf.ctp_magic == CTF_MAGIC) { |
| 251 | if (hdr.ctf.ctp_version > CTF_VERSION) |
| 252 | return (ctf_set_open_errno(errp, ECTF_CTFVERS)); |
| 253 | |
| 254 | ctfsect.cts_data = mmap64(NULL, st.st_size, PROT_READ, |
| 255 | MAP_PRIVATE, fd, 0); |
| 256 | |
| 257 | if (ctfsect.cts_data == MAP_FAILED) |
| 258 | return (ctf_set_open_errno(errp, errno)); |
| 259 | |
| 260 | ctfsect.cts_name = _CTF_SECTION; |
| 261 | ctfsect.cts_type = SHT_PROGBITS; |
| 262 | ctfsect.cts_flags = SHF_ALLOC; |
| 263 | ctfsect.cts_size = (size_t)st.st_size; |
| 264 | ctfsect.cts_entsize = 1; |
| 265 | ctfsect.cts_offset = 0; |
| 266 | |
| 267 | if ((fp = ctf_bufopen(&ctfsect, NULL, NULL, errp)) == NULL) |
| 268 | ctf_sect_munmap(&ctfsect); |
| 269 | |
| 270 | return (fp); |
| 271 | } |
| 272 | |
| 273 | /* |
| 274 | * If we have read enough bytes to form an ELF header and the magic |
| 275 | * string matches, attempt to interpret the file as an ELF file. We |
| 276 | * do our own largefile ELF processing, and convert everything to |
| 277 | * GElf structures so that clients can operate on any data model. |
| 278 | */ |
| 279 | if (nbytes >= (ssize_t) sizeof (Elf32_Ehdr) && |
| 280 | bcmp(&hdr.e32.e_ident[EI_MAG0], ELFMAG, SELFMAG) == 0) { |
| 281 | #if BYTE_ORDER == _BIG_ENDIAN |
| 282 | uchar_t order = ELFDATA2MSB; |
| 283 | #else |
| 284 | uchar_t order = ELFDATA2LSB; |
| 285 | #endif |
| 286 | GElf_Shdr *sp; |
| 287 | |
| 288 | void *strs_map; |
| 289 | size_t strs_mapsz, i; |
| 290 | char *strs; |
| 291 | |
| 292 | if (hdr.e32.e_ident[EI_DATA] != order) |
| 293 | return (ctf_set_open_errno(errp, ECTF_ENDIAN)); |
| 294 | if (hdr.e32.e_version != EV_CURRENT) |
| 295 | return (ctf_set_open_errno(errp, ECTF_ELFVERS)); |
| 296 | |
| 297 | if (hdr.e32.e_ident[EI_CLASS] == ELFCLASS64) { |
| 298 | if (nbytes < (ssize_t) sizeof (GElf_Ehdr)) |
| 299 | return (ctf_set_open_errno(errp, ECTF_FMT)); |
| 300 | } else { |
| 301 | Elf32_Ehdr e32 = hdr.e32; |
| 302 | ehdr_to_gelf(&e32, &hdr.e64); |
| 303 | } |
| 304 | |
| 305 | shnum = hdr.e64.e_shnum; |
| 306 | shstrndx = hdr.e64.e_shstrndx; |
| 307 | |
| 308 | /* Extended ELF sections */ |
| 309 | if ((shstrndx == SHN_XINDEX) || (shnum == 0)) { |
| 310 | if (hdr.e32.e_ident[EI_CLASS] == ELFCLASS32) { |
| 311 | Elf32_Shdr x32; |
| 312 | |
| 313 | if (pread64(fd, &x32, sizeof (x32), |
| 314 | hdr.e64.e_shoff) != sizeof (x32)) |
| 315 | return (ctf_set_open_errno(errp, |
| 316 | errno)); |
| 317 | |
| 318 | shnum = x32.sh_size; |
| 319 | shstrndx = x32.sh_link; |
| 320 | } else { |
| 321 | Elf64_Shdr x64; |
| 322 | |
| 323 | if (pread64(fd, &x64, sizeof (x64), |
| 324 | hdr.e64.e_shoff) != sizeof (x64)) |
| 325 | return (ctf_set_open_errno(errp, |
| 326 | errno)); |
| 327 | |
| 328 | shnum = x64.sh_size; |
| 329 | shstrndx = x64.sh_link; |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | if (shstrndx >= shnum) |
| 334 | return (ctf_set_open_errno(errp, ECTF_CORRUPT)); |
| 335 | |
| 336 | nbytes = sizeof (GElf_Shdr) * shnum; |
| 337 | |
| 338 | if ((sp = malloc(nbytes)) == NULL) |
| 339 | return (ctf_set_open_errno(errp, errno)); |
| 340 | |
| 341 | /* |
| 342 | * Read in and convert to GElf the array of Shdr structures |
| 343 | * from e_shoff so we can locate sections of interest. |
| 344 | */ |
| 345 | if (hdr.e32.e_ident[EI_CLASS] == ELFCLASS32) { |
| 346 | Elf32_Shdr *sp32; |
| 347 | |
| 348 | nbytes = sizeof (Elf32_Shdr) * shnum; |
| 349 | |
| 350 | if ((sp32 = malloc(nbytes)) == NULL || pread64(fd, |
| 351 | sp32, nbytes, hdr.e64.e_shoff) != nbytes) { |
| 352 | free(sp); |
| 353 | free(sp32); |
| 354 | return (ctf_set_open_errno(errp, errno)); |
| 355 | } |
| 356 | |
| 357 | for (i = 0; i < shnum; i++) |
| 358 | shdr_to_gelf(&sp32[i], &sp[i]); |
| 359 | |
| 360 | free(sp32); |
| 361 | |
| 362 | } else if (pread64(fd, sp, nbytes, hdr.e64.e_shoff) != nbytes) { |
| 363 | free(sp); |
| 364 | return (ctf_set_open_errno(errp, errno)); |
| 365 | } |
| 366 | |
| 367 | /* |
| 368 | * Now mmap the section header strings section so that we can |
| 369 | * perform string comparison on the section names. |
| 370 | */ |
| 371 | strs_mapsz = sp[shstrndx].sh_size + |
| 372 | (sp[shstrndx].sh_offset & ~_PAGEMASK); |
| 373 | |
| 374 | strs_map = mmap64(NULL, strs_mapsz, PROT_READ, MAP_PRIVATE, |
| 375 | fd, sp[shstrndx].sh_offset & _PAGEMASK); |
| 376 | |
| 377 | strs = (char *)strs_map + |
| 378 | (sp[shstrndx].sh_offset & ~_PAGEMASK); |
| 379 | |
| 380 | if (strs_map == MAP_FAILED) { |
| 381 | free(sp); |
| 382 | return (ctf_set_open_errno(errp, ECTF_MMAP)); |
| 383 | } |
| 384 | |
| 385 | /* |
| 386 | * Iterate over the section header array looking for the CTF |
| 387 | * section and symbol table. The strtab is linked to symtab. |
| 388 | */ |
| 389 | for (i = 0; i < shnum; i++) { |
| 390 | const GElf_Shdr *shp = &sp[i]; |
| 391 | const GElf_Shdr *lhp = &sp[shp->sh_link]; |
| 392 | |
| 393 | if (shp->sh_link >= shnum) |
| 394 | continue; /* corrupt sh_link field */ |
| 395 | |
| 396 | if (shp->sh_name >= sp[shstrndx].sh_size || |
| 397 | lhp->sh_name >= sp[shstrndx].sh_size) |
| 398 | continue; /* corrupt sh_name field */ |
| 399 | |
| 400 | if (shp->sh_type == SHT_PROGBITS && |
| 401 | strcmp(strs + shp->sh_name, _CTF_SECTION) == 0) { |
| 402 | ctfsect.cts_name = strs + shp->sh_name; |
| 403 | ctfsect.cts_type = shp->sh_type; |
| 404 | ctfsect.cts_flags = shp->sh_flags; |
| 405 | ctfsect.cts_size = shp->sh_size; |
| 406 | ctfsect.cts_entsize = shp->sh_entsize; |
| 407 | ctfsect.cts_offset = (off64_t)shp->sh_offset; |
| 408 | |
| 409 | } else if (shp->sh_type == SHT_SYMTAB) { |
| 410 | symsect.cts_name = strs + shp->sh_name; |
| 411 | symsect.cts_type = shp->sh_type; |
| 412 | symsect.cts_flags = shp->sh_flags; |
| 413 | symsect.cts_size = shp->sh_size; |
| 414 | symsect.cts_entsize = shp->sh_entsize; |
| 415 | symsect.cts_offset = (off64_t)shp->sh_offset; |
| 416 | |
| 417 | strsect.cts_name = strs + lhp->sh_name; |
| 418 | strsect.cts_type = lhp->sh_type; |
| 419 | strsect.cts_flags = lhp->sh_flags; |
| 420 | strsect.cts_size = lhp->sh_size; |
| 421 | strsect.cts_entsize = lhp->sh_entsize; |
| 422 | strsect.cts_offset = (off64_t)lhp->sh_offset; |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | free(sp); /* free section header array */ |
| 427 | |
| 428 | if (ctfsect.cts_type == SHT_NULL) { |
| 429 | (void) munmap(strs_map, strs_mapsz); |
| 430 | return (ctf_set_open_errno(errp, ECTF_NOCTFDATA)); |
| 431 | } |
| 432 | |
| 433 | /* |
| 434 | * Now mmap the CTF data, symtab, and strtab sections and |
| 435 | * call ctf_bufopen() to do the rest of the work. |
| 436 | */ |
| 437 | if (ctf_sect_mmap(&ctfsect, fd) == MAP_FAILED) { |
| 438 | (void) munmap(strs_map, strs_mapsz); |
| 439 | return (ctf_set_open_errno(errp, ECTF_MMAP)); |
| 440 | } |
| 441 | |
| 442 | if (symsect.cts_type != SHT_NULL && |
| 443 | strsect.cts_type != SHT_NULL) { |
| 444 | if (ctf_sect_mmap(&symsect, fd) == MAP_FAILED || |
| 445 | ctf_sect_mmap(&strsect, fd) == MAP_FAILED) { |
| 446 | (void) ctf_set_open_errno(errp, ECTF_MMAP); |
| 447 | goto bad; /* unmap all and abort */ |
| 448 | } |
| 449 | fp = ctf_bufopen(&ctfsect, &symsect, &strsect, errp); |
| 450 | } else |
| 451 | fp = ctf_bufopen(&ctfsect, NULL, NULL, errp); |
| 452 | bad: |
| 453 | if (fp == NULL) { |
| 454 | ctf_sect_munmap(&ctfsect); |
| 455 | ctf_sect_munmap(&symsect); |
| 456 | ctf_sect_munmap(&strsect); |
| 457 | } else |
| 458 | fp->ctf_flags |= LCTF_MMAP; |
| 459 | |
| 460 | (void) munmap(strs_map, strs_mapsz); |
| 461 | return (fp); |
| 462 | } |
| 463 | |
| 464 | return (ctf_set_open_errno(errp, ECTF_FMT)); |
| 465 | } |
| 466 | |
| 467 | /* |
| 468 | * Open the specified file and return a pointer to a CTF container. The file |
| 469 | * can be either an ELF file or raw CTF file. This is just a convenient |
| 470 | * wrapper around ctf_fdopen() for callers. |
| 471 | */ |
| 472 | ctf_file_t * |
| 473 | ctf_open(const char *filename, int *errp) |
| 474 | { |
| 475 | ctf_file_t *fp; |
| 476 | int fd; |
| 477 | |
| 478 | if ((fd = open64(filename, O_RDONLY)) == -1) { |
| 479 | if (errp != NULL) |
| 480 | *errp = errno; |
| 481 | return (NULL); |
| 482 | } |
| 483 | |
| 484 | fp = ctf_fdopen(fd, errp); |
| 485 | (void) close(fd); |
| 486 | return (fp); |
| 487 | } |
| 488 | |
| 489 | /* |
| 490 | * Write the uncompressed CTF data stream to the specified file descriptor. |
| 491 | * This is useful for saving the results of dynamic CTF containers. |
| 492 | */ |
| 493 | int |
| 494 | ctf_write(ctf_file_t *fp, int fd) |
| 495 | { |
| 496 | const uchar_t *buf = fp->ctf_base; |
| 497 | ssize_t resid = fp->ctf_size; |
| 498 | ssize_t len; |
| 499 | |
| 500 | while (resid != 0) { |
| 501 | if ((len = write(fd, buf, resid)) <= 0) |
| 502 | return (ctf_set_errno(fp, errno)); |
| 503 | resid -= len; |
| 504 | buf += len; |
| 505 | } |
| 506 | |
| 507 | return (0); |
| 508 | } |
| 509 | |
| 510 | /* |
| 511 | * Set the CTF library client version to the specified version. If version is |
| 512 | * zero, we just return the default library version number. |
| 513 | */ |
| 514 | int |
| 515 | ctf_version(int version) |
| 516 | { |
| 517 | if (version < 0) { |
| 518 | errno = EINVAL; |
| 519 | return (-1); |
| 520 | } |
| 521 | |
| 522 | if (version > 0) { |
| 523 | if (version > CTF_VERSION) { |
| 524 | errno = ENOTSUP; |
| 525 | return (-1); |
| 526 | } |
| 527 | ctf_dprintf("ctf_version: client using version %d\n" , version); |
| 528 | _libctf_version = version; |
| 529 | } |
| 530 | |
| 531 | return (_libctf_version); |
| 532 | } |
| 533 | |