diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:30 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:30 -0800 |
commit | 10e23eebca4175a8dfe3a788b2bebacb1fcfce54 (patch) | |
tree | e7b15dddb89338d148474c4e4c16acb087e322c7 | |
parent | dfb3f050a7cebd2030ea23dc6fa8964530e4ddcc (diff) | |
download | oprofile-10e23eebca4175a8dfe3a788b2bebacb1fcfce54.tar.gz |
auto import from //depot/cupcake/@135843android-sdk-tools_r2android-sdk-1.6_r2android-sdk-1.6_r1android-sdk-1.6-docs_r1android-sdk-1.5_r3android-sdk-1.5_r1android-sdk-1.5-preandroid-1.6_r2android-1.6_r1.5android-1.6_r1.4android-1.6_r1.3android-1.6_r1.2android-1.6_r1.1android-1.6_r1android-1.5r4android-1.5r3android-1.5r2android-1.5donut-release2donut-releasedonutcupcake-releasecupcake
103 files changed, 15275 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..582ddc9 --- /dev/null +++ b/Android.mk @@ -0,0 +1,3 @@ +ifeq ($(TARGET_ARCH),arm) +include $(call all-subdir-makefiles) +endif diff --git a/MODULE_LICENSE_GPL b/MODULE_LICENSE_GPL new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_GPL @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/config.h b/config.h new file mode 100644 index 0000000..c220da5 --- /dev/null +++ b/config.h @@ -0,0 +1,101 @@ +/* config.h. Generated by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* whether popt prototype takes a const char ** */ +#define CONST_POPT 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `bfd' library (-lbfd). */ +#define HAVE_LIBBFD 1 + +/* Define to 1 if you have the `iberty' library (-liberty). */ +#define HAVE_LIBIBERTY 1 + +/* Define to 1 if you have the <libiberty.h> header file. */ +/* #undef HAVE_LIBIBERTY_H */ + +/* Define to 1 if you have the `popt' library (-lpopt). */ +#define HAVE_LIBPOPT 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `perfmonctl' function. */ +/* #undef HAVE_PERFMONCTL */ + +/* Define to 1 if you have the `sched_setaffinity' function. */ +/* #undef HAVE_SCHED_SETAFFINITY */ + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `xcalloc' function. */ +#undef HAVE_XCALLOC + +/* Define to 1 if you have the `xmemdup' function. */ +#undef HAVE_XMEMDUP + +/* whether malloc attribute is understood */ +#define MALLOC_ATTRIBUTE_OK 1 + +/* whether to build ABI tools */ +#define OPROF_ABI 1 + +/* package binary directory */ +#define OP_BINDIR "/usr/local/bin/" + +/* package data directory */ +#define OP_DATADIR "/usr/local/share/oprofile/" + +/* Name of package */ +#define PACKAGE "oprofile" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Synthesize special symbols when needed */ +/* #undef SYNTHESIZE_SYMBOLS */ + +/* whether bfd.h defines bool values */ +/* #undef TRUE_FALSE_ALREADY_DEFINED */ + +/* Version number of package */ +#define VERSION "0.9.1" + +/* Define to 1 if the X Window System is missing or not being used. */ +/* #undef X_DISPLAY_MISSING */ diff --git a/daemon/Android.mk b/daemon/Android.mk new file mode 100644 index 0000000..abee74c --- /dev/null +++ b/daemon/Android.mk @@ -0,0 +1,33 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + init.c \ + opd_anon.c \ + opd_cookie.c \ + opd_events.c \ + opd_kernel.c \ + opd_mangling.c \ + opd_perfmon.c \ + opd_pipe.c \ + opd_sfile.c \ + opd_spu.c \ + opd_stats.c \ + opd_trans.c \ + oprofiled.c + +LOCAL_STATIC_LIBRARIES := \ + libpopt libutil libdb libabi libop + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + $(LOCAL_PATH)/../libdb \ + $(LOCAL_PATH)/../libutil \ + $(LOCAL_PATH)/../libop \ + $(LOCAL_PATH)/../libabi + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE:= oprofiled + +include $(BUILD_EXECUTABLE) diff --git a/daemon/init.c b/daemon/init.c new file mode 100644 index 0000000..be0b9da --- /dev/null +++ b/daemon/init.c @@ -0,0 +1,373 @@ +/** + * @file daemon/init.c + * Daemon set up and main loop for 2.6 + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * @Modifications Daniel Hansel + * Modified by Aravind Menon for Xen + * These modifications are: + * Copyright (C) 2005 Hewlett-Packard Co. + */ + +#include "config.h" + +#include "oprofiled.h" +#include "opd_stats.h" +#include "opd_sfile.h" +#include "opd_pipe.h" +#include "opd_kernel.h" +#include "opd_trans.h" +#include "opd_anon.h" +#include "opd_perfmon.h" +#include "opd_printf.h" + +#include "op_version.h" +#include "op_config.h" +#include "op_deviceio.h" +#include "op_get_time.h" +#include "op_libiberty.h" +#include "op_fileio.h" + +#include <fcntl.h> +#include <stdio.h> +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <sys/time.h> +#if ANDROID +#include <sys/wait.h> +#else +#include <wait.h> +#endif +#include <string.h> + +size_t kernel_pointer_size; + +static fd_t devfd; +static char * sbuf; +static size_t s_buf_bytesize; +extern char * session_dir; +static char start_time_str[32]; +static int jit_conversion_running; + +static void opd_sighup(void); +static void opd_alarm(void); +static void opd_sigterm(void); +static void opd_sigchild(void); +static void opd_do_jitdumps(void); + +/** + * opd_open_files - open necessary files + * + * Open the device files and the log file, + * and mmap() the hash map. + */ +static void opd_open_files(void) +{ + devfd = op_open_device("/dev/oprofile/buffer"); + if (devfd == -1) { + if (errno == EINVAL) + fprintf(stderr, "Failed to open device. Possibly you have passed incorrect\n" + "parameters. Check /var/log/messages."); + else + perror("Failed to open profile device"); + exit(EXIT_FAILURE); + } + + /* give output before re-opening stdout as the logfile */ + printf("Using log file %s\n", op_log_file); + + /* set up logfile */ + close(0); + close(1); + + if (open("/dev/null", O_RDONLY) == -1) { + perror("oprofiled: couldn't re-open stdin as /dev/null: "); + exit(EXIT_FAILURE); + } + + opd_open_logfile(); + opd_create_pipe(); + + printf("oprofiled started %s", op_get_time()); + printf("kernel pointer size: %lu\n", + (unsigned long)kernel_pointer_size); + fflush(stdout); +} + + +/** Done writing out the samples, indicate with complete_dump file */ +static void complete_dump(void) +{ + FILE * status_file; + +retry: + status_file = fopen(op_dump_status, "w"); + + if (!status_file && errno == EMFILE) { + if (sfile_lru_clear()) { + printf("LRU cleared but file open fails for %s.\n", + op_dump_status); + abort(); + } + goto retry; + } + + if (!status_file) { + perror("warning: couldn't set complete_dump: "); + return; + } + + fprintf(status_file, "1\n"); + fclose(status_file); +} + + +/** + * opd_do_samples - process a sample buffer + * @param opd_buf buffer to process + * + * Process a buffer of samples. + * + * If the sample could be processed correctly, it is written + * to the relevant sample file. + */ +static void opd_do_samples(char const * opd_buf, ssize_t count) +{ + size_t num = count / kernel_pointer_size; + + opd_stats[OPD_DUMP_COUNT]++; + + verbprintf(vmisc, "Read buffer of %d entries.\n", (unsigned int)num); + + opd_process_samples(opd_buf, num); + + complete_dump(); +} + +static void opd_do_jitdumps(void) +{ + pid_t childpid; + int arg_num; + unsigned long long end_time = 0ULL; + struct timeval tv; + char end_time_str[32]; + char opjitconv_path[PATH_MAX + 1]; + char * exec_args[6]; + + if (jit_conversion_running) + return; + jit_conversion_running = 1; + + childpid = fork(); + switch (childpid) { + case -1: + perror("Error forking JIT dump process!"); + break; + case 0: + gettimeofday(&tv, NULL); + end_time = tv.tv_sec; + sprintf(end_time_str, "%llu", end_time); + sprintf(opjitconv_path, "%s/%s", OP_BINDIR, "opjitconv"); + arg_num = 0; + exec_args[arg_num++] = opjitconv_path; + if (vmisc) + exec_args[arg_num++] = "-d"; + exec_args[arg_num++] = session_dir; + exec_args[arg_num++] = start_time_str; + exec_args[arg_num++] = end_time_str; + exec_args[arg_num] = (char *) NULL; + execvp("opjitconv", exec_args); + fprintf(stderr, "Failed to exec %s: %s\n", + exec_args[0], strerror(errno)); + /* We don't want any cleanup in the child */ + _exit(EXIT_FAILURE); + default: + break; + } + +} + +/** + * opd_do_read - enter processing loop + * @param buf buffer to read into + * @param size size of buffer + * + * Read some of a buffer from the device and process + * the contents. + */ +static void opd_do_read(char * buf, size_t size) +{ + opd_open_pipe(); + + while (1) { + ssize_t count = -1; + + /* loop to handle EINTR */ + while (count < 0) { + count = op_read_device(devfd, buf, size); + + /* we can lose an alarm or a hup but + * we don't care. + */ + if (signal_alarm) { + signal_alarm = 0; + opd_alarm(); + } + + if (signal_hup) { + signal_hup = 0; + opd_sighup(); + } + + if (signal_term) + opd_sigterm(); + + if (signal_child) + opd_sigchild(); + + if (signal_usr1) { + signal_usr1 = 0; + perfmon_start(); + } + + if (signal_usr2) { + signal_usr2 = 0; + perfmon_stop(); + } + + if (is_jitconv_requested()) { + verbprintf(vmisc, "Start opjitconv was triggered\n"); + opd_do_jitdumps(); + } + } + + opd_do_samples(buf, count); + } + + opd_close_pipe(); +} + + +/** opd_alarm - sync files and report stats */ +static void opd_alarm(void) +{ + sfile_sync_files(); + opd_print_stats(); + alarm(60 * 10); +} + + +/** re-open files for logrotate/opcontrol --reset */ +static void opd_sighup(void) +{ + printf("Received SIGHUP.\n"); + /* We just close them, and re-open them lazily as usual. */ + sfile_close_files(); + close(1); + close(2); + opd_open_logfile(); +} + + +static void clean_exit(void) +{ + perfmon_exit(); + unlink(op_lock_file); +} + + +static void opd_sigterm(void) +{ + opd_do_jitdumps(); + opd_print_stats(); + printf("oprofiled stopped %s", op_get_time()); + exit(EXIT_FAILURE); +} + +/* SIGCHLD received from JIT dump child process. */ +static void opd_sigchild(void) +{ + int child_status; + wait(&child_status); + jit_conversion_running = 0; + if (WIFEXITED(child_status) && (!WEXITSTATUS(child_status))) { + verbprintf(vmisc, "JIT dump processing complete.\n"); + } else { + printf("JIT dump processing exited abnormally: %d\n", + WEXITSTATUS(child_status)); + } + +} + +static void opd_26_init(void) +{ + size_t i; + size_t opd_buf_size; + unsigned long long start_time = 0ULL; + struct timeval tv; + + opd_create_vmlinux(vmlinux, kernel_range); + opd_create_xen(xenimage, xen_range); + + opd_buf_size = opd_read_fs_int("/dev/oprofile/", "buffer_size", 1); + kernel_pointer_size = opd_read_fs_int("/dev/oprofile/", "pointer_size", 1); + + s_buf_bytesize = opd_buf_size * kernel_pointer_size; + + sbuf = xmalloc(s_buf_bytesize); + + opd_reread_module_info(); + + for (i = 0; i < OPD_MAX_STATS; i++) + opd_stats[i] = 0; + + perfmon_init(); + + cookie_init(); + sfile_init(); + anon_init(); + + /* must be /after/ perfmon_init() at least */ + if (atexit(clean_exit)) { + perfmon_exit(); + perror("oprofiled: couldn't set exit cleanup: "); + exit(EXIT_FAILURE); + } + + /* trigger kernel module setup before returning control to opcontrol */ + opd_open_files(); + gettimeofday(&tv, NULL); + start_time = 0ULL; + start_time = tv.tv_sec; + sprintf(start_time_str, "%llu", start_time); + +} + + +static void opd_26_start(void) +{ + /* simple sleep-then-process loop */ + opd_do_read(sbuf, s_buf_bytesize); +} + + +static void opd_26_exit(void) +{ + opd_print_stats(); + printf("oprofiled stopped %s", op_get_time()); + + free(sbuf); + free(vmlinux); + /* FIXME: free kernel images, sfiles etc. */ +} + +struct oprofiled_ops opd_26_ops = { + .init = opd_26_init, + .start = opd_26_start, + .exit = opd_26_exit, +}; diff --git a/daemon/opd_anon.c b/daemon/opd_anon.c new file mode 100644 index 0000000..9caea3d --- /dev/null +++ b/daemon/opd_anon.c @@ -0,0 +1,228 @@ +/** + * @file opd_anon.c + * Anonymous region handling. + * + * Our caching of maps has some problems: if we get tgid reuse, + * and it's the same application, we might end up with wrong + * maps. The same happens in an unmap-remap case. There's not much + * we can do about this, we just hope it's not too common... + * + * What is relatively common is expanding anon maps, which leaves us + * with lots of separate sample files. + * + * @remark Copyright 2005 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @Modifications Gisle Dankel + */ + +#include "opd_anon.h" +#include "opd_trans.h" +#include "opd_sfile.h" +#include "opd_printf.h" +#include "op_libiberty.h" + +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#define HASH_SIZE 1024 +#define HASH_BITS (HASH_SIZE - 1) + +/* + * Note that this value is tempered by the fact that when we miss in the + * anon cache, we'll tear down all the mappings for that tgid. Thus, LRU + * of a mapping can potentially clear out a much larger number of + * mappings. + */ +#define LRU_SIZE 8192 +#define LRU_AMOUNT (LRU_SIZE/8) + +static struct list_head hashes[HASH_SIZE]; +static struct list_head lru; +static size_t nr_lru; + +static void do_lru(struct transient * trans) +{ + size_t nr_to_kill = LRU_AMOUNT; + struct list_head * pos; + struct list_head * pos2; + struct anon_mapping * entry; + + list_for_each_safe(pos, pos2, &lru) { + entry = list_entry(pos, struct anon_mapping, lru_list); + if (trans->anon == entry) + clear_trans_current(trans); + if (trans->last_anon == entry) + clear_trans_last(trans); + sfile_clear_anon(entry); + list_del(&entry->list); + list_del(&entry->lru_list); + --nr_lru; + free(entry); + if (nr_to_kill-- == 0) + break; + } +} + + +static unsigned long hash_anon(pid_t tgid, cookie_t app) +{ + return ((app >> DCOOKIE_SHIFT) ^ (tgid >> 2)) & (HASH_SIZE - 1); +} + + +static void clear_anon_maps(struct transient * trans) +{ + unsigned long hash = hash_anon(trans->tgid, trans->app_cookie); + pid_t tgid = trans->tgid; + cookie_t app = trans->app_cookie; + struct list_head * pos; + struct list_head * pos2; + struct anon_mapping * entry; + + clear_trans_current(trans); + + list_for_each_safe(pos, pos2, &hashes[hash]) { + entry = list_entry(pos, struct anon_mapping, list); + if (entry->tgid == tgid && entry->app_cookie == app) { + if (trans->last_anon == entry) + clear_trans_last(trans); + sfile_clear_anon(entry); + list_del(&entry->list); + list_del(&entry->lru_list); + --nr_lru; + free(entry); + } + } + + if (vmisc) { + char const * name = verbose_cookie(app); + printf("Cleared anon maps for tgid %u (%s).\n", tgid, name); + } +} + + +static void +add_anon_mapping(struct transient * trans, vma_t start, vma_t end, char * name) +{ + unsigned long hash = hash_anon(trans->tgid, trans->app_cookie); + struct anon_mapping * m = xmalloc(sizeof(struct anon_mapping)); + m->tgid = trans->tgid; + m->app_cookie = trans->app_cookie; + m->start = start; + m->end = end; + strncpy(m->name, name, MAX_IMAGE_NAME_SIZE + 1); + list_add_tail(&m->list, &hashes[hash]); + list_add_tail(&m->lru_list, &lru); + if (++nr_lru == LRU_SIZE) + do_lru(trans); + if (vmisc) { + char const * name = verbose_cookie(m->app_cookie); + printf("Added anon map 0x%llx-0x%llx for tgid %u (%s).\n", + start, end, m->tgid, name); + } +} + + +/* 42000000-4212f000 r-xp 00000000 16:03 424334 /lib/tls/libc-2.3.2.so */ +static void get_anon_maps(struct transient * trans) +{ + FILE * fp = NULL; + char buf[PATH_MAX]; + vma_t start, end; + int ret; + + snprintf(buf, PATH_MAX, "/proc/%d/maps", trans->tgid); + fp = fopen(buf, "r"); + if (!fp) + return; + + while (fgets(buf, PATH_MAX, fp) != NULL) { + char tmp[MAX_IMAGE_NAME_SIZE + 1]; + char name[MAX_IMAGE_NAME_SIZE + 1]; + /* Some anon maps have labels like + * [heap], [stack], [vdso], [vsyscall] ... + * Keep track of these labels. If a map has no name, call it "anon". + * Ignore all mappings starting with "/" (file or shared memory object) + */ + strcpy(name, "anon"); + ret = sscanf(buf, "%llx-%llx %20s %20s %20s %20s %20s", + &start, &end, tmp, tmp, tmp, tmp, name); + if (ret < 6 || name[0] == '/') + continue; + + add_anon_mapping(trans, start, end, name); + } + + fclose(fp); +} + + +static int +anon_match(struct transient const * trans, struct anon_mapping const * anon) +{ + if (!anon) + return 0; + if (trans->tgid != anon->tgid) + return 0; + if (trans->app_cookie != anon->app_cookie) + return 0; + if (trans->pc < anon->start) + return 0; + return (trans->pc < anon->end); +} + + +struct anon_mapping * find_anon_mapping(struct transient * trans) +{ + unsigned long hash = hash_anon(trans->tgid, trans->app_cookie); + struct list_head * pos; + struct anon_mapping * entry; + int tried = 0; + + if (anon_match(trans, trans->anon)) + return (trans->anon); + +retry: + list_for_each(pos, &hashes[hash]) { + entry = list_entry(pos, struct anon_mapping, list); + if (anon_match(trans, entry)) + goto success; + } + + if (!tried) { + clear_anon_maps(trans); + get_anon_maps(trans); + tried = 1; + goto retry; + } + + return NULL; + +success: + /* + * Typically, there's one big mapping that matches. Let's go + * faster. + */ + list_del(&entry->list); + list_add(&entry->list, &hashes[hash]); + + verbprintf(vmisc, "Found range 0x%llx-0x%llx for tgid %u, pc %llx.\n", + entry->start, entry->end, (unsigned int)entry->tgid, + trans->pc); + return entry; +} + + +void anon_init(void) +{ + size_t i; + + for (i = 0; i < HASH_SIZE; ++i) + list_init(&hashes[i]); + + list_init(&lru); +} diff --git a/daemon/opd_anon.h b/daemon/opd_anon.h new file mode 100644 index 0000000..3f66b55 --- /dev/null +++ b/daemon/opd_anon.h @@ -0,0 +1,54 @@ +/** + * @file opd_anon.h + * Anonymous region handling. + * + * @remark Copyright 2005 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + */ + +#ifndef OPD_ANON_H +#define OPD_ANON_H + +#include "op_types.h" +#include "op_list.h" + +#include "opd_cookie.h" + +#include <sys/types.h> + +struct transient; + +/** + * Shift useful bits into play for VMA hashing. + */ +#define VMA_SHIFT 13 + +/* Maximum size of the image name considered */ +#define MAX_IMAGE_NAME_SIZE 20 + +struct anon_mapping { + /** start of the mapping */ + vma_t start; + /** end of the mapping */ + vma_t end; + /** tgid of the app */ + pid_t tgid; + /** cookie of the app */ + cookie_t app_cookie; + /** hash list */ + struct list_head list; + /** lru list */ + struct list_head lru_list; + char name[MAX_IMAGE_NAME_SIZE+1]; +}; + +/** + * Try to find an anonymous mapping for the given pc/tgid pair. + */ +struct anon_mapping * find_anon_mapping(struct transient *); + +void anon_init(void); + +#endif /* OPD_ANON_H */ diff --git a/daemon/opd_cookie.c b/daemon/opd_cookie.c new file mode 100644 index 0000000..3578e48 --- /dev/null +++ b/daemon/opd_cookie.c @@ -0,0 +1,209 @@ +/** + * @file opd_cookie.c + * cookie -> name cache + * + * @remark Copyright 2002, 2005 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + */ + +#include "opd_cookie.h" +#include "oprofiled.h" +#include "op_list.h" +#include "op_libiberty.h" + +#include <sys/syscall.h> +#include <unistd.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#ifndef __NR_lookup_dcookie +#if defined(__i386__) +#define __NR_lookup_dcookie 253 +#elif defined(__x86_64__) +#define __NR_lookup_dcookie 212 +#elif defined(__powerpc__) +#define __NR_lookup_dcookie 235 +#elif defined(__alpha__) +#define __NR_lookup_dcookie 406 +#elif defined(__hppa__) +#define __NR_lookup_dcookie 223 +#elif defined(__ia64__) +#define __NR_lookup_dcookie 1237 +#elif defined(__sparc__) +/* untested */ +#define __NR_lookup_dcookie 208 +#elif defined(__s390__) || defined (__s390x__) +#define __NR_lookup_dcookie 110 +#elif defined(__arm__) +#define __NR_lookup_dcookie (__NR_SYSCALL_BASE+249) +#elif defined(__mips__) +#include <sgidefs.h> +/* O32 */ +#if _MIPS_SIM == _MIPS_SIM_ABI32 +#define __NR_lookup_dcookie 4247 +/* N64 */ +#elif _MIPS_SIM == _MIPS_SIM_ABI64 +#define __NR_lookup_dcookie 5206 +/* N32 */ +#elif _MIPS_SIM == _MIPS_SIM_NABI32 +#define __NR_lookup_dcookie 6206 +#else +#error Unknown MIPS ABI: Dunno __NR_lookup_dcookie +#endif +#else +#error Please define __NR_lookup_dcookie for your architecture +#endif +#endif /* __NR_lookup_dcookie */ + +#if (defined(__powerpc__) && !defined(__powerpc64__)) || defined(__hppa__)\ + || (defined(__s390__) && !defined(__s390x__)) \ + || (defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32) \ + && defined(__MIPSEB__)) \ + || (defined(__arm__) && defined(__ARM_EABI__) \ + && defined(__ARMEB__)) +static inline int lookup_dcookie(cookie_t cookie, char * buf, size_t size) +{ + return syscall(__NR_lookup_dcookie, (unsigned long)(cookie >> 32), + (unsigned long)(cookie & 0xffffffff), buf, size); +} +#elif (defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32)) \ + || (defined(__arm__) && defined(__ARM_EABI__)) +static inline int lookup_dcookie(cookie_t cookie, char * buf, size_t size) +{ + return syscall(__NR_lookup_dcookie, + (unsigned long)(cookie & 0xffffffff), + (unsigned long)(cookie >> 32), buf, size); +} +#else +static inline int lookup_dcookie(cookie_t cookie, char * buf, size_t size) +{ + return syscall(__NR_lookup_dcookie, cookie, buf, size); +} +#endif + + +struct cookie_entry { + cookie_t value; + char * name; + int ignored; + struct list_head list; +}; + + +#define HASH_SIZE 512 +#define HASH_BITS (HASH_SIZE - 1) + +static struct list_head hashes[HASH_SIZE]; + +static struct cookie_entry * create_cookie(cookie_t cookie) +{ + int err; + struct cookie_entry * entry = xmalloc(sizeof(struct cookie_entry)); + + entry->value = cookie; + entry->name = xmalloc(PATH_MAX + 1); + + err = lookup_dcookie(cookie, entry->name, PATH_MAX); + + if (err < 0) { + fprintf(stderr, "Lookup of cookie %llx failed, errno=%d\n", + cookie, errno); + free(entry->name); + entry->name = NULL; + entry->ignored = 0; + } else { + entry->ignored = is_image_ignored(entry->name); + } + + return entry; +} + + +/* Cookie monster want cookie! */ +static unsigned long hash_cookie(cookie_t cookie) +{ + return (cookie >> DCOOKIE_SHIFT) & (HASH_SIZE - 1); +} + + +char const * find_cookie(cookie_t cookie) +{ + unsigned long hash = hash_cookie(cookie); + struct list_head * pos; + struct cookie_entry * entry; + + if (cookie == INVALID_COOKIE || cookie == NO_COOKIE) + return NULL; + + list_for_each(pos, &hashes[hash]) { + entry = list_entry(pos, struct cookie_entry, list); + if (entry->value == cookie) + goto out; + } + + /* not sure this can ever happen due to is_cookie_ignored */ + entry = create_cookie(cookie); + list_add(&entry->list, &hashes[hash]); +out: + return entry->name; +} + + +int is_cookie_ignored(cookie_t cookie) +{ + unsigned long hash = hash_cookie(cookie); + struct list_head * pos; + struct cookie_entry * entry; + + if (cookie == INVALID_COOKIE || cookie == NO_COOKIE) + return 1; + + list_for_each(pos, &hashes[hash]) { + entry = list_entry(pos, struct cookie_entry, list); + if (entry->value == cookie) + goto out; + } + + entry = create_cookie(cookie); + list_add(&entry->list, &hashes[hash]); +out: + return entry->ignored; +} + + +char const * verbose_cookie(cookie_t cookie) +{ + unsigned long hash = hash_cookie(cookie); + struct list_head * pos; + struct cookie_entry * entry; + + if (cookie == INVALID_COOKIE) + return "invalid"; + + if (cookie == NO_COOKIE) + return "anonymous"; + + list_for_each(pos, &hashes[hash]) { + entry = list_entry(pos, struct cookie_entry, list); + if (entry->value == cookie) { + if (!entry->name) + return "failed lookup"; + return entry->name; + } + } + + return "not hashed"; +} + + +void cookie_init(void) +{ + size_t i; + + for (i = 0; i < HASH_SIZE; ++i) + list_init(&hashes[i]); +} diff --git a/daemon/opd_cookie.h b/daemon/opd_cookie.h new file mode 100644 index 0000000..a9f13b1 --- /dev/null +++ b/daemon/opd_cookie.h @@ -0,0 +1,39 @@ +/** + * @file opd_cookie.h + * cookie -> name cache + * + * @remark Copyright 2002, 2005 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + */ + +#ifndef OPD_COOKIE_H +#define OPD_COOKIE_H + +typedef unsigned long long cookie_t; + +#define INVALID_COOKIE ~0LLU +#define NO_COOKIE 0LLU + +/** + * Shift value to remove trailing zero on a dcookie value, 7 is sufficient + * for most architecture + */ +#define DCOOKIE_SHIFT 7 + +/** + * Return the name of the given dcookie. May return + * NULL on failure. + */ +char const * find_cookie(cookie_t cookie); + +/** return true if this cookie should be ignored */ +int is_cookie_ignored(cookie_t cookie); + +/** give a textual description of the cookie */ +char const * verbose_cookie(cookie_t cookie); + +void cookie_init(void); + +#endif /* OPD_COOKIE_H */ diff --git a/daemon/opd_events.c b/daemon/opd_events.c new file mode 100644 index 0000000..81a87d2 --- /dev/null +++ b/daemon/opd_events.c @@ -0,0 +1,165 @@ +/** + * @file daemon/opd_events.c + * Event details for each counter + * + * @remark Copyright 2002, 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "config.h" + +#include "opd_events.h" +#include "opd_printf.h" +#include "oprofiled.h" + +#include "op_string.h" +#include "op_config.h" +#include "op_cpufreq.h" +#include "op_cpu_type.h" +#include "op_libiberty.h" +#include "op_hw_config.h" +#include "op_sample_file.h" + +#include <stdlib.h> +#include <stdio.h> + +extern op_cpu cpu_type; + +struct opd_event opd_events[OP_MAX_COUNTERS]; + +static double cpu_speed; + +static void malformed_events(void) +{ + fprintf(stderr, "oprofiled: malformed events passed " + "on the command line\n"); + exit(EXIT_FAILURE); +} + + +static char * copy_token(char ** c, char delim) +{ + char * tmp = *c; + char * tmp2 = *c; + char * str; + + if (!**c) + return NULL; + + while (*tmp2 && *tmp2 != delim) + ++tmp2; + + if (tmp2 == tmp) + return NULL; + + str = op_xstrndup(tmp, tmp2 - tmp); + *c = tmp2; + if (**c) + ++*c; + return str; +} + + +static unsigned long copy_ulong(char ** c, char delim) +{ + unsigned long val = 0; + char * str = copy_token(c, delim); + if (!str) + malformed_events(); + val = strtoul(str, NULL, 0); + free(str); + return val; +} + + +void opd_parse_events(char const * events) +{ + char * ev = xstrdup(events); + char * c; + size_t cur = 0; + + if (cpu_type == CPU_TIMER_INT) { + struct opd_event * event = &opd_events[0]; + event->name = xstrdup("TIMER"); + event->value = event->counter + = event->count = event->um = 0; + event->kernel = 1; + event->user = 1; + return; + } + + if (!ev || !strlen(ev)) { + fprintf(stderr, "oprofiled: no events passed.\n"); + exit(EXIT_FAILURE); + } + + verbprintf(vmisc, "Events: %s\n", ev); + + c = ev; + + while (*c && cur < op_nr_counters) { + struct opd_event * event = &opd_events[cur]; + + if (!(event->name = copy_token(&c, ':'))) + malformed_events(); + event->value = copy_ulong(&c, ':'); + event->counter = copy_ulong(&c, ':'); + event->count = copy_ulong(&c, ':'); + event->um = copy_ulong(&c, ':'); + event->kernel = copy_ulong(&c, ':'); + event->user = copy_ulong(&c, ','); + ++cur; + } + + if (*c) { + fprintf(stderr, "oprofiled: too many events passed.\n"); + exit(EXIT_FAILURE); + } + + free(ev); + + cpu_speed = op_cpu_frequency(); +} + + +struct opd_event * find_counter_event(unsigned long counter) +{ + size_t i; + + for (i = 0; i < op_nr_counters && opd_events[i].name; ++i) { + if (counter == opd_events[i].counter) + return &opd_events[i]; + } + + fprintf(stderr, "Unknown event for counter %lu\n", counter); + abort(); + return NULL; +} + + +void fill_header(struct opd_header * header, unsigned long counter, + vma_t anon_start, vma_t cg_to_anon_start, + int is_kernel, int cg_to_is_kernel, + int spu_samples, uint64_t embed_offset, time_t mtime) +{ + struct opd_event * event = find_counter_event(counter); + + memset(header, '\0', sizeof(struct opd_header)); + header->version = OPD_VERSION; + memcpy(header->magic, OPD_MAGIC, sizeof(header->magic)); + header->cpu_type = cpu_type; + header->ctr_event = event->value; + header->ctr_count = event->count; + header->ctr_um = event->um; + header->is_kernel = is_kernel; + header->cg_to_is_kernel = cg_to_is_kernel; + header->cpu_speed = cpu_speed; + header->mtime = mtime; + header->anon_start = anon_start; + header->spu_profile = spu_samples; + header->embedded_offset = embed_offset; + header->cg_to_anon_start = cg_to_anon_start; +} diff --git a/daemon/opd_events.h b/daemon/opd_events.h new file mode 100644 index 0000000..3bd0106 --- /dev/null +++ b/daemon/opd_events.h @@ -0,0 +1,47 @@ +/** + * @file daemon/opd_events.h + * Event details for each counter + * + * @remark Copyright 2002, 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OPD_EVENTS_H + +#include "op_types.h" + +#include <stdint.h> +#include <time.h> + +/** event description for setup (perfmon) and mangling */ +struct opd_event { + char * name; + unsigned long value; + unsigned long counter; + unsigned long count; + unsigned long um; + unsigned long kernel; + unsigned long user; +}; + +/* needed for opd_perfmon.c */ +extern struct opd_event opd_events[]; + +/** parse the events into the opd_events array */ +void opd_parse_events(char const * events); + +/** Find the event for the given counter */ +struct opd_event * find_counter_event(unsigned long counter); + +struct opd_header; + +/** fill the sample file header with event info etc. */ +void fill_header(struct opd_header * header, unsigned long counter, + vma_t anon_start, vma_t anon_end, + int is_kernel, int cg_to_is_kernel, + int spu_samples, uint64_t embed_offset, time_t mtime); + +#endif /* OPD_EVENTS_H */ diff --git a/daemon/opd_interface.h b/daemon/opd_interface.h new file mode 100644 index 0000000..c876830 --- /dev/null +++ b/daemon/opd_interface.h @@ -0,0 +1,45 @@ +/** + * @file opd_interface.h + * + * Module / user space interface for 2.6 kernels and above + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * Modified by Aravind Menon for Xen + * These modifications are: + * Copyright (C) 2005 Hewlett-Packard Co. + */ + +#ifndef OPD_INTERFACE_H +#define OPD_INTERFACE_H + +#define CTX_SWITCH_CODE 1 +#define CPU_SWITCH_CODE 2 +#define COOKIE_SWITCH_CODE 3 +#define KERNEL_ENTER_SWITCH_CODE 4 +#define USER_ENTER_SWITCH_CODE 5 +#define MODULE_LOADED_CODE 6 +#define CTX_TGID_CODE 7 +#define TRACE_BEGIN_CODE 8 +/* Code 9 used to be TRACE_END_CODE which is not used anymore */ +/* Code 9 is now considered an unknown escape code */ +#define XEN_ENTER_SWITCH_CODE 10 +/* + * Ugly work-around for the unfortunate collision between Xenoprof's + * DOMAIN_SWITCH_CODE (in use on x86) and Cell's SPU_PROFILING_CODE + * (in use with Power): + */ +#if defined(__powerpc__) +#define SPU_PROFILING_CODE 11 +#define SPU_CTX_SWITCH_CODE 12 +#define DOMAIN_SWITCH_CODE 13 +#define LAST_CODE 14 +#else +#define DOMAIN_SWITCH_CODE 11 +#define LAST_CODE 12 +#endif + +#endif /* OPD_INTERFACE_H */ diff --git a/daemon/opd_kernel.c b/daemon/opd_kernel.c new file mode 100644 index 0000000..5ebc210 --- /dev/null +++ b/daemon/opd_kernel.c @@ -0,0 +1,229 @@ +/** + * @file daemon/opd_kernel.c + * Dealing with the kernel and kernel module samples + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * Modified by Aravind Menon for Xen + * These modifications are: + * Copyright (C) 2005 Hewlett-Packard Co. + */ + +#include "opd_kernel.h" +#include "opd_sfile.h" +#include "opd_trans.h" +#include "opd_printf.h" +#include "opd_stats.h" +#include "oprofiled.h" + +#include "op_fileio.h" +#include "op_config.h" +#include "op_libiberty.h" + +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> + +static LIST_HEAD(modules); + +static struct kernel_image vmlinux_image; + +static struct kernel_image xen_image; + +void opd_create_vmlinux(char const * name, char const * arg) +{ + /* vmlinux is *not* on the list of modules */ + list_init(&vmlinux_image.list); + + /* for no vmlinux */ + if (no_vmlinux) { + vmlinux_image.name = "no-vmlinux"; + return; + } + + vmlinux_image.name = xstrdup(name); + + sscanf(arg, "%llx,%llx", &vmlinux_image.start, &vmlinux_image.end); + + verbprintf(vmisc, "kernel_start = %llx, kernel_end = %llx\n", + vmlinux_image.start, vmlinux_image.end); + + if (!vmlinux_image.start && !vmlinux_image.end) { + fprintf(stderr, "error: mis-parsed kernel range: %llx-%llx\n", + vmlinux_image.start, vmlinux_image.end); + exit(EXIT_FAILURE); + } +} + +void opd_create_xen(char const * name, char const * arg) +{ + /* xen is *not* on the list of modules */ + list_init(&xen_image.list); + + /* for no xen */ + if (no_xen) { + xen_image.name = "no-xen"; + return; + } + + xen_image.name = xstrdup(name); + + sscanf(arg, "%llx,%llx", &xen_image.start, &xen_image.end); + + verbprintf(vmisc, "xen_start = %llx, xen_end = %llx\n", + xen_image.start, xen_image.end); + + if (!xen_image.start && !xen_image.end) { + fprintf(stderr, "error: mis-parsed xen range: %llx-%llx\n", + xen_image.start, xen_image.end); + exit(EXIT_FAILURE); + } +} + + +/** + * Allocate and initialise a kernel image description + * @param name image name + * @param start start address + * @param end end address + */ +static struct kernel_image * +opd_create_module(char const * name, vma_t start, vma_t end) +{ + struct kernel_image * image = xmalloc(sizeof(struct kernel_image)); + + image->name = xstrdup(name); + image->start = start; + image->end = end; + list_add(&image->list, &modules); + + return image; +} + + +/** + * Clear and free all kernel image information and reset + * values. + */ +static void opd_clear_modules(void) +{ + struct list_head * pos; + struct list_head * pos2; + struct kernel_image * image; + + list_for_each_safe(pos, pos2, &modules) { + image = list_entry(pos, struct kernel_image, list); + if (image->name) + free(image->name); + free(image); + } + + list_init(&modules); + + /* clear out lingering references */ + sfile_clear_kernel(); +} + + +/* + * each line is in the format: + * + * module_name 16480 1 dependencies Live 0xe091e000 + * + * without any blank space in each field + */ +void opd_reread_module_info(void) +{ + FILE * fp; + char * line; + struct kernel_image * image; + int module_size; + char ref_count[32+1]; + int ret; + char module_name[256+1]; + char live_info[32+1]; + char dependencies[4096+1]; + unsigned long long start_address; + + if (no_vmlinux) + return; + + opd_clear_modules(); + + printf("Reading module info.\n"); + + fp = op_try_open_file("/proc/modules", "r"); + + if (!fp) { + printf("oprofiled: /proc/modules not readable, " + "can't process module samples.\n"); + return; + } + + while (1) { + line = op_get_line(fp); + + if (!line) + break; + + if (line[0] == '\0') { + free(line); + continue; + } + + ret = sscanf(line, "%256s %u %32s %4096s %32s %llx", + module_name, &module_size, ref_count, + dependencies, live_info, &start_address); + if (ret != 6) { + printf("bad /proc/modules entry: %s\n", line); + free(line); + continue; + } + + image = opd_create_module(module_name, start_address, + start_address + module_size); + + verbprintf(vmodule, "module %s start %llx end %llx\n", + image->name, image->start, image->end); + + free(line); + } + + op_close_file(fp); +} + + +/** + * find a kernel image by PC value + * @param trans holds PC value to look up + * + * find the kernel image which contains this PC. + * + * Return %NULL if not found. + */ +struct kernel_image * find_kernel_image(struct transient const * trans) +{ + struct list_head * pos; + struct kernel_image * image = &vmlinux_image; + + if (no_vmlinux) + return image; + + if (image->start <= trans->pc && image->end > trans->pc) + return image; + + list_for_each(pos, &modules) { + image = list_entry(pos, struct kernel_image, list); + if (image->start <= trans->pc && image->end > trans->pc) + return image; + } + + if (xen_image.start <= trans->pc && xen_image.end > trans->pc) + return &xen_image; + + return NULL; +} diff --git a/daemon/opd_kernel.h b/daemon/opd_kernel.h new file mode 100644 index 0000000..cb71a30 --- /dev/null +++ b/daemon/opd_kernel.h @@ -0,0 +1,43 @@ +/** + * @file daemon/opd_kernel.h + * Dealing with the kernel and kernel module images + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * Modified by Aravind Menon for Xen + * These modifications are: + * Copyright (C) 2005 Hewlett-Packard Co. + */ + +#ifndef OPD_KERNEL_H +#define OPD_KERNEL_H + +#include "op_types.h" +#include "op_list.h" + +struct transient; + +/** create the kernel image */ +void opd_create_vmlinux(char const * name, char const * arg); + +void opd_create_xen(char const * name, char const * arg); + +/** opd_reread_module_info - parse /proc/modules for kernel modules */ +void opd_reread_module_info(void); + +/** Describes a kernel module or vmlinux itself */ +struct kernel_image { + char * name; + vma_t start; + vma_t end; + struct list_head list; +}; + +/** Find a kernel_image based upon the given parameters in trans. */ +struct kernel_image * +find_kernel_image(struct transient const * trans); + +#endif /* OPD_KERNEL_H */ diff --git a/daemon/opd_mangling.c b/daemon/opd_mangling.c new file mode 100644 index 0000000..08a6079 --- /dev/null +++ b/daemon/opd_mangling.c @@ -0,0 +1,205 @@ +/** + * @file daemon/opd_mangling.c + * Mangling and opening of sample files + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <sys/types.h> + +#include "opd_mangling.h" +#include "opd_kernel.h" +#include "opd_cookie.h" +#include "opd_sfile.h" +#include "opd_anon.h" +#include "opd_printf.h" +#include "opd_events.h" +#include "oprofiled.h" + +#include "op_file.h" +#include "op_sample_file.h" +#include "op_config.h" +#include "op_mangle.h" +#include "op_events.h" +#include "op_libiberty.h" + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + + +static char const * get_dep_name(struct sfile const * sf) +{ + if (sf->anon) + return find_cookie(sf->app_cookie); + + /* avoid to call find_cookie(), caller can recover using image_name */ + if (sf->cookie == sf->app_cookie) + return NULL; + + if (!separate_kernel && !(separate_lib && !sf->kernel)) + return NULL; + + /* this will fail if e.g. kernel thread */ + if (sf->app_cookie == 0) + return NULL; + + return find_cookie(sf->app_cookie); +} + + +static char * mangle_anon(struct anon_mapping const * anon) +{ + char * name = xmalloc(PATH_MAX); + + snprintf(name, 1024, "%u.0x%llx.0x%llx", (unsigned int)anon->tgid, + anon->start, anon->end); + + return name; +} + + +static char * +mangle_filename(struct sfile * last, struct sfile const * sf, int counter, int cg) +{ + char * mangled; + struct mangle_values values; + struct opd_event * event = find_counter_event(counter); + + values.flags = 0; + + if (sf->kernel) { + values.image_name = sf->kernel->name; + values.flags |= MANGLE_KERNEL; + } else if (sf->anon) { + values.flags |= MANGLE_ANON; + values.image_name = mangle_anon(sf->anon); + values.anon_name = sf->anon->name; + } else { + values.image_name = find_cookie(sf->cookie); + } + + values.dep_name = get_dep_name(sf); + if (!values.dep_name) + values.dep_name = values.image_name; + + /* FIXME: log */ + if (!values.image_name || !values.dep_name) + return NULL; + + if (separate_thread) { + values.flags |= MANGLE_TGID | MANGLE_TID; + values.tid = sf->tid; + values.tgid = sf->tgid; + } + + if (separate_cpu) { + values.flags |= MANGLE_CPU; + values.cpu = sf->cpu; + } + + if (cg) { + values.flags |= MANGLE_CALLGRAPH; + if (last->kernel) { + values.cg_image_name = last->kernel->name; + } else if (last->anon) { + values.flags |= MANGLE_CG_ANON; + values.cg_image_name = mangle_anon(last->anon); + values.anon_name = last->anon->name; + } else { + values.cg_image_name = find_cookie(last->cookie); + } + + /* FIXME: log */ + if (!values.cg_image_name) { + if (values.flags & MANGLE_ANON) + free((char *)values.image_name); + return NULL; + } + } + + values.event_name = event->name; + values.count = event->count; + values.unit_mask = event->um; + + mangled = op_mangle_filename(&values); + + if (values.flags & MANGLE_ANON) + free((char *)values.image_name); + if (values.flags & MANGLE_CG_ANON) + free((char *)values.cg_image_name); + return mangled; +} + + +int opd_open_sample_file(odb_t * file, struct sfile * last, + struct sfile * sf, int counter, int cg) +{ + char * mangled; + char const * binary; + int spu_profile = 0; + vma_t last_start = 0; + int err; + + mangled = mangle_filename(last, sf, counter, cg); + + if (!mangled) + return EINVAL; + + verbprintf(vsfile, "Opening \"%s\"\n", mangled); + + create_path(mangled); + + /* locking sf will lock associated cg files too */ + sfile_get(sf); + if (sf != last) + sfile_get(last); + +retry: + err = odb_open(file, mangled, ODB_RDWR, sizeof(struct opd_header)); + + /* This can naturally happen when racing against opcontrol --reset. */ + if (err) { + if (err == EMFILE) { + if (sfile_lru_clear()) { + printf("LRU cleared but odb_open() fails for %s.\n", mangled); + abort(); + } + goto retry; + } + + fprintf(stderr, "oprofiled: open of %s failed: %s\n", + mangled, strerror(err)); + goto out; + } + + if (!sf->kernel) + binary = find_cookie(sf->cookie); + else + binary = sf->kernel->name; + + if (last && last->anon) + last_start = last->anon->start; + + if (sf->embedded_offset != UNUSED_EMBEDDED_OFFSET) + spu_profile = 1; + + fill_header(odb_get_data(file), counter, + sf->anon ? sf->anon->start : 0, last_start, + !!sf->kernel, last ? !!last->kernel : 0, + spu_profile, sf->embedded_offset, + binary ? op_get_mtime(binary) : 0); + +out: + sfile_put(sf); + if (sf != last) + sfile_put(last); + free(mangled); + return err; +} diff --git a/daemon/opd_mangling.h b/daemon/opd_mangling.h new file mode 100644 index 0000000..0e46ec4 --- /dev/null +++ b/daemon/opd_mangling.h @@ -0,0 +1,33 @@ +/** + * @file daemon/opd_mangling.h + * Mangling and opening of sample files + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OPD_MANGLING_H +#define OPD_MANGLING_H + +#include "odb.h" + +struct sfile; + +/* + * opd_open_sample_file - open a sample file + * @param sf sfile to open sample file for + * @param counter counter number + * @param cg if this is a callgraph file + * + * Open image sample file for the sfile, counter + * counter and set up memory mappings for it. + * + * Returns 0 on success. + */ +int opd_open_sample_file(odb_t * file, struct sfile * last, + struct sfile * sf, int counter, int cg); + +#endif /* OPD_MANGLING_H */ diff --git a/daemon/opd_perfmon.c b/daemon/opd_perfmon.c new file mode 100644 index 0000000..a1b158a --- /dev/null +++ b/daemon/opd_perfmon.c @@ -0,0 +1,496 @@ +/** + * @file opd_perfmon.c + * perfmonctl() handling + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + */ + +#ifdef __ia64__ + +/* need this for sched_setaffinity() in <sched.h> */ +#define _GNU_SOURCE + +#include "oprofiled.h" +#include "opd_perfmon.h" +#include "opd_events.h" + +#include "op_cpu_type.h" +#include "op_libiberty.h" +#include "op_hw_config.h" + +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#ifdef HAVE_SCHED_SETAFFINITY +#include <sched.h> +#endif + +extern op_cpu cpu_type; + +#ifndef HAVE_SCHED_SETAFFINITY + +/* many glibc's are not yet up to date */ +#ifndef __NR_sched_setaffinity +#define __NR_sched_setaffinity 1231 +#endif + +/* Copied from glibc's <sched.h> and <bits/sched.h> and munged */ +#define CPU_SETSIZE 1024 +#define __NCPUBITS (8 * sizeof (unsigned long)) +typedef struct +{ + unsigned long __bits[CPU_SETSIZE / __NCPUBITS]; +} cpu_set_t; + +#define CPU_SET(cpu, cpusetp) \ + ((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS))) +#define CPU_ZERO(cpusetp) \ + memset((cpusetp), 0, sizeof(cpu_set_t)) + +static int +sched_setaffinity(pid_t pid, size_t len, cpu_set_t const * cpusetp) +{ + return syscall(__NR_sched_setaffinity, pid, len, cpusetp); +} +#endif + + +#ifndef HAVE_PERFMONCTL +#ifndef __NR_perfmonctl +#define __NR_perfmonctl 1175 +#endif + +static int perfmonctl(int fd, int cmd, void * arg, int narg) +{ + return syscall(__NR_perfmonctl, fd, cmd, arg, narg); +} +#endif + + +static unsigned char uuid[16] = { + 0x77, 0x7a, 0x6e, 0x61, 0x20, 0x65, 0x73, 0x69, + 0x74, 0x6e, 0x72, 0x20, 0x61, 0x65, 0x0a, 0x6c +}; + + +static size_t nr_cpus; + +struct child { + pid_t pid; + int up_pipe[2]; + int ctx_fd; + sig_atomic_t sigusr1; + sig_atomic_t sigusr2; + sig_atomic_t sigterm; +}; + +static struct child * children; + +static void perfmon_start_child(int ctx_fd) +{ + if (perfmonctl(ctx_fd, PFM_START, 0, 0) == -1) { + perror("Couldn't start perfmon: "); + exit(EXIT_FAILURE); + } +} + + +static void perfmon_stop_child(int ctx_fd) +{ + if (perfmonctl(ctx_fd, PFM_STOP, 0, 0) == -1) { + perror("Couldn't stop perfmon: "); + exit(EXIT_FAILURE); + } +} + + +static void child_sigusr1(int val __attribute__((unused))) +{ + size_t i; + + for (i = 0; i < nr_cpus; ++i) { + if (children[i].pid == getpid()) { + children[i].sigusr1 = 1; + return; + } + } +} + + +static void child_sigusr2(int val __attribute__((unused))) +{ + size_t i; + + for (i = 0; i < nr_cpus; ++i) { + if (children[i].pid == getpid()) { + children[i].sigusr2 = 1; + return; + } + } +} + + +static void child_sigterm(int val __attribute__((unused))) +{ + printf("Child received SIGTERM, killing parent.\n"); + kill(getppid(), SIGTERM); +} + + +static void set_affinity(size_t cpu) +{ + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + + int err = sched_setaffinity(getpid(), sizeof(set), &set); + + if (err == -1) { + fprintf(stderr, "Failed to set affinity: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } +} + + +static void setup_signals(void) +{ + struct sigaction act; + sigset_t mask; + + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigprocmask(SIG_BLOCK, &mask, NULL); + + act.sa_handler = child_sigusr1; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + if (sigaction(SIGUSR1, &act, NULL)) { + perror("oprofiled: install of SIGUSR1 handler failed: "); + exit(EXIT_FAILURE); + } + + act.sa_handler = child_sigusr2; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + if (sigaction(SIGUSR2, &act, NULL)) { + perror("oprofiled: install of SIGUSR2 handler failed: "); + exit(EXIT_FAILURE); + } + + act.sa_handler = child_sigterm; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + if (sigaction(SIGTERM, &act, NULL)) { + perror("oprofiled: install of SIGTERM handler failed: "); + exit(EXIT_FAILURE); + } +} + + +/** create the per-cpu context */ +static void create_context(struct child * self) +{ + pfarg_context_t ctx; + int err; + + memset(&ctx, 0, sizeof(pfarg_context_t)); + memcpy(&ctx.ctx_smpl_buf_id, &uuid, 16); + ctx.ctx_flags = PFM_FL_SYSTEM_WIDE; + + err = perfmonctl(0, PFM_CREATE_CONTEXT, &ctx, 1); + if (err == -1) { + fprintf(stderr, "CREATE_CONTEXT failed: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + self->ctx_fd = ctx.ctx_fd; +} + + +/** program the perfmon counters */ +static void write_pmu(struct child * self) +{ + pfarg_reg_t pc[OP_MAX_COUNTERS]; + pfarg_reg_t pd[OP_MAX_COUNTERS]; + int err; + size_t i; + + memset(pc, 0, sizeof(pc)); + memset(pd, 0, sizeof(pd)); + +#define PMC_GEN_INTERRUPT (1UL << 5) +#define PMC_PRIV_MONITOR (1UL << 6) +/* McKinley requires pmc4 to have bit 23 set (enable PMU). + * It is supposedly ignored in other pmc registers. + */ +#define PMC_MANDATORY (1UL << 23) +#define PMC_USER (1UL << 3) +#define PMC_KERNEL (1UL << 0) + for (i = 0; i < op_nr_counters && opd_events[i].name; ++i) { + struct opd_event * event = &opd_events[i]; + pc[i].reg_num = event->counter + 4; + pc[i].reg_value = PMC_GEN_INTERRUPT; + pc[i].reg_value |= PMC_PRIV_MONITOR; + pc[i].reg_value |= PMC_MANDATORY; + (event->user) ? (pc[i].reg_value |= PMC_USER) + : (pc[i].reg_value &= ~PMC_USER); + (event->kernel) ? (pc[i].reg_value |= PMC_KERNEL) + : (pc[i].reg_value &= ~PMC_KERNEL); + pc[i].reg_value &= ~(0xff << 8); + pc[i].reg_value |= ((event->value & 0xff) << 8); + pc[i].reg_value &= ~(0xf << 16); + pc[i].reg_value |= ((event->um & 0xf) << 16); + pc[i].reg_smpl_eventid = event->counter; + } + + for (i = 0; i < op_nr_counters && opd_events[i].name; ++i) { + struct opd_event * event = &opd_events[i]; + pd[i].reg_value = ~0UL - event->count + 1; + pd[i].reg_short_reset = ~0UL - event->count + 1; + pd[i].reg_num = event->counter + 4; + } + + err = perfmonctl(self->ctx_fd, PFM_WRITE_PMCS, pc, i); + if (err == -1) { + perror("Couldn't write PMCs: "); + exit(EXIT_FAILURE); + } + + err = perfmonctl(self->ctx_fd, PFM_WRITE_PMDS, pd, i); + if (err == -1) { + perror("Couldn't write PMDs: "); + exit(EXIT_FAILURE); + } +} + + +static void load_context(struct child * self) +{ + pfarg_load_t load_args; + int err; + + memset(&load_args, 0, sizeof(load_args)); + load_args.load_pid = self->pid; + + err = perfmonctl(self->ctx_fd, PFM_LOAD_CONTEXT, &load_args, 1); + if (err == -1) { + perror("Couldn't load context: "); + exit(EXIT_FAILURE); + } +} + + +static void notify_parent(struct child * self, size_t cpu) +{ + for (;;) { + ssize_t ret; + ret = write(self->up_pipe[1], &cpu, sizeof(size_t)); + if (ret == sizeof(size_t)) + break; + if (ret < 0 && errno != EINTR) { + fprintf(stderr, "Failed to write child pipe with %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + } +} + + +static void run_child(size_t cpu) +{ + struct child * self = &children[cpu]; + + self->pid = getpid(); + self->sigusr1 = 0; + self->sigusr2 = 0; + self->sigterm = 0; + + setup_signals(); + + set_affinity(cpu); + + create_context(self); + + write_pmu(self); + + load_context(self); + + notify_parent(self, cpu); + + for (;;) { + sigset_t sigmask; + sigfillset(&sigmask); + sigdelset(&sigmask, SIGUSR1); + sigdelset(&sigmask, SIGUSR2); + sigdelset(&sigmask, SIGTERM); + + if (self->sigusr1) { + printf("PFM_START on CPU%d\n", (int)cpu); + fflush(stdout); + perfmon_start_child(self->ctx_fd); + self->sigusr1 = 0; + } + + if (self->sigusr2) { + printf("PFM_STOP on CPU%d\n", (int)cpu); + fflush(stdout); + perfmon_stop_child(self->ctx_fd); + self->sigusr2 = 0; + } + + sigsuspend(&sigmask); + } +} + + +static void wait_for_child(struct child * child) +{ + size_t tmp; + for (;;) { + ssize_t ret; + ret = read(child->up_pipe[0], &tmp, sizeof(size_t)); + if (ret == sizeof(size_t)) + break; + if (ret < 0 && errno != EINTR) { + fprintf(stderr, "Failed to read child pipe with %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + } + printf("Perfmon child up on CPU%d\n", (int)tmp); + fflush(stdout); + + close(child->up_pipe[0]); + close(child->up_pipe[1]); +} + +static struct child* xen_ctx; + +void perfmon_init(void) +{ + size_t i; + long nr; + + if (cpu_type == CPU_TIMER_INT) + return; + + if (!no_xen) { + xen_ctx = xmalloc(sizeof(struct child)); + xen_ctx->pid = getpid(); + xen_ctx->up_pipe[0] = -1; + xen_ctx->up_pipe[1] = -1; + xen_ctx->sigusr1 = 0; + xen_ctx->sigusr2 = 0; + xen_ctx->sigterm = 0; + + create_context(xen_ctx); + + write_pmu(xen_ctx); + + load_context(xen_ctx); + return; + } + + + nr = sysconf(_SC_NPROCESSORS_ONLN); + if (nr == -1) { + fprintf(stderr, "Couldn't determine number of CPUs.\n"); + exit(EXIT_FAILURE); + } + + nr_cpus = nr; + + children = xmalloc(sizeof(struct child) * nr_cpus); + + for (i = 0; i < nr_cpus; ++i) { + int ret; + + if (pipe(children[i].up_pipe)) { + perror("Couldn't create child pipe.\n"); + exit(EXIT_FAILURE); + } + + ret = fork(); + if (ret == -1) { + fprintf(stderr, "Couldn't fork perfmon child.\n"); + exit(EXIT_FAILURE); + } else if (ret == 0) { + printf("Running perfmon child on CPU%d.\n", (int)i); + fflush(stdout); + run_child(i); + } else { + children[i].pid = ret; + printf("Waiting on CPU%d\n", (int)i); + wait_for_child(&children[i]); + } + } +} + + +void perfmon_exit(void) +{ + size_t i; + + if (cpu_type == CPU_TIMER_INT) + return; + + if (!no_xen) + return; + + for (i = 0; i < nr_cpus; ++i) { + kill(children[i].pid, SIGKILL); + waitpid(children[i].pid, NULL, 0); + } +} + + +void perfmon_start(void) +{ + size_t i; + + if (cpu_type == CPU_TIMER_INT) + return; + + if (!no_xen) { + perfmon_start_child(xen_ctx->ctx_fd); + return; + } + + for (i = 0; i < nr_cpus; ++i) + kill(children[i].pid, SIGUSR1); +} + + +void perfmon_stop(void) +{ + size_t i; + + if (cpu_type == CPU_TIMER_INT) + return; + + if (!no_xen) { + perfmon_stop_child(xen_ctx->ctx_fd); + return; + } + + for (i = 0; i < nr_cpus; ++i) + kill(children[i].pid, SIGUSR2); +} + +#endif /* __ia64__ */ diff --git a/daemon/opd_perfmon.h b/daemon/opd_perfmon.h new file mode 100644 index 0000000..9b4267f --- /dev/null +++ b/daemon/opd_perfmon.h @@ -0,0 +1,106 @@ +/** + * @file opd_perfmon.h + * perfmonctl() handling + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + */ + +#ifndef OPD_PERFMON_H +#define OPD_PERFMON_H + +#ifdef __ia64__ + +#include <stdlib.h> + +void perfmon_init(void); +void perfmon_exit(void); +void perfmon_start(void); +void perfmon_stop(void); + +/* The following is from asm/perfmon.h. When it's installed on + * enough boxes, we can remove this and include the platform + * perfmon.h + */ + +typedef unsigned char pfm_uuid_t[16]; /* custom sampling buffer identifier type */ + +/* + * Request structure used to define a context + */ +typedef struct { + pfm_uuid_t ctx_smpl_buf_id; /* which buffer format to use (if needed) */ + unsigned long ctx_flags; /* noblock/block */ + unsigned short ctx_nextra_sets; /* number of extra event sets (you always get 1) */ + unsigned short ctx_reserved1; /* for future use */ + int ctx_fd; /* return arg: unique identification for context */ + void *ctx_smpl_vaddr; /* return arg: virtual address of sampling buffer, is used */ + unsigned long ctx_reserved2[11];/* for future use */ +} pfarg_context_t; + +/* + * Request structure used to write/read a PMC or PMD + */ +typedef struct { + unsigned int reg_num; /* which register */ + unsigned short reg_set; /* event set for this register */ + unsigned short reg_reserved1; /* for future use */ + + unsigned long reg_value; /* initial pmc/pmd value */ + unsigned long reg_flags; /* input: pmc/pmd flags, return: reg error */ + + unsigned long reg_long_reset; /* reset after buffer overflow notification */ + unsigned long reg_short_reset; /* reset after counter overflow */ + + unsigned long reg_reset_pmds[4]; /* which other counters to reset on overflow */ + unsigned long reg_random_seed; /* seed value when randomization is used */ + unsigned long reg_random_mask; /* bitmask used to limit random value */ + unsigned long reg_last_reset_val;/* return: PMD last reset value */ + + unsigned long reg_smpl_pmds[4]; /* which pmds are accessed when PMC overflows */ + unsigned long reg_smpl_eventid; /* opaque sampling event identifier */ + + unsigned long reg_reserved2[3]; /* for future use */ +} pfarg_reg_t; + +typedef struct { + pid_t load_pid; /* process to load the context into */ + unsigned short load_set; /* first event set to load */ + unsigned short load_reserved1; /* for future use */ + unsigned long load_reserved2[3]; /* for future use */ +} pfarg_load_t; + +#define PFM_WRITE_PMCS 0x01 +#define PFM_WRITE_PMDS 0x02 +#define PFM_STOP 0x04 +#define PFM_START 0x05 +#define PFM_CREATE_CONTEXT 0x08 +#define PFM_LOAD_CONTEXT 0x10 +#define PFM_FL_SYSTEM_WIDE 0x02 + +#else + +void perfmon_init(void) +{ +} + + +void perfmon_exit(void) +{ +} + + +void perfmon_start(void) +{ +} + + +void perfmon_stop(void) +{ +} + +#endif /* __ia64__ */ + +#endif /* OPD_PERFMON_H */ diff --git a/daemon/opd_pipe.c b/daemon/opd_pipe.c new file mode 100644 index 0000000..a5c334a --- /dev/null +++ b/daemon/opd_pipe.c @@ -0,0 +1,96 @@ +/** + * @file daemon/opd_pipe.c + * Functions handling the $SESSIONDIR/opd_pipe FIFO special file. + * NOTE: This code is dealing with potentially insecure input. + * + * @remark Copyright 2008 OProfile authors + * @remark Read the file COPYING + * + * @author Daniel Hansel + */ + +#include "opd_pipe.h" +#include "opd_printf.h" +#include "op_config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> + +static int fifo; + +void opd_create_pipe(void) +{ + mode_t orig_umask = umask(0111); + if (mkfifo(op_pipe_file, 0666) == -1) { + if (errno != EEXIST) { + perror("oprofiled: couldn't create pipe: "); + exit(EXIT_FAILURE); + } + } + umask(orig_umask); +} + + +void opd_open_pipe(void) +{ + fifo = open(op_pipe_file, O_RDONLY | O_NONBLOCK); + if (fifo == -1) { + perror("oprofiled: couldn't open pipe: "); + exit(EXIT_FAILURE); + } +} + + +void opd_close_pipe(void) +{ + close(fifo); +} + + +int is_jitconv_requested(void) +{ + /* number of dropped (unknown) requests */ + static long nr_drops = 0; + /* modulus to output only a few warnings to avoid flooding oprofiled.log */ + static int mod_cnt_drops = 1; + FILE * fd; + char line[256]; + int i, ret = 0; + + /* get a file descriptor to the pipe */ + fd = fdopen(fifo, "r"); + + if (fd == NULL) { + perror("oprofiled: couldn't create file descriptor: "); + exit(EXIT_FAILURE); + } + + /* read up to 99 lines to check for 'do_jitconv' */ + for (i = 0; i < 99; i++) { + /* just break if no new line is found */ + if (fgets(line, 256, fd) == NULL) + break; + line[strlen(line) - 1] = '\0'; + + if (strstr(line, "do_jitconv") != NULL) { + ret = 1; + } else { + nr_drops++; + + if (nr_drops % mod_cnt_drops == 0) { + printf( + "Warning: invalid pipe request received (dropped request(s): %ld)\n", + nr_drops); + /* increase modulus to avoid flooding log file */ + mod_cnt_drops *= 5; + } + } + } + + return ret; +} diff --git a/daemon/opd_pipe.h b/daemon/opd_pipe.h new file mode 100644 index 0000000..7f96b07 --- /dev/null +++ b/daemon/opd_pipe.h @@ -0,0 +1,45 @@ +/** + * @file daemon/opd_pipe.h + * Functions handling the $SESSIONDIR/opd_pipe FIFO special file. + * NOTE: This code is dealing with potencially insecure input. + * + * @remark Copyright 2008 OProfile authors + * @remark Read the file COPYING + * + * @author Daniel Hansel + */ + +#ifndef OPD_PIPE_H_ +#define OPD_PIPE_H_ + +/** + * opd_create_pipe - creates the oprofiled fifo file + * + * Creates the Oprofile daemon fifo pipe to enable communication between + * the daemon and the 'opcontrol --dump' command. Failure to create the pipe + * is a fatal error. + */ +void opd_create_pipe(void); + +/** + * opd_open_pipe - opens the oprofiled fifo file + */ +void opd_open_pipe(void); + +/** + * opd_close_pipe - closes the oprofiled fifo file + * + * Closes the Oprofile daemon fifo pipe. + */ +void opd_close_pipe(void); + +/** + * is_jitconv_requested - check for request to jit conversion + * + * Checks the Oprofile daemon fifo pipe for do_jitconv request. + * If jit conversion is requested ('do_jitconv' is sent) the check returns 1. + * Otherwise it returns 0. + */ +int is_jitconv_requested(void); + +#endif /*OPD_PIPE_H_*/ diff --git a/daemon/opd_printf.h b/daemon/opd_printf.h new file mode 100644 index 0000000..e1f8476 --- /dev/null +++ b/daemon/opd_printf.h @@ -0,0 +1,35 @@ +/** + * @file daemon/opd_printf.h + * Output routines + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OPD_PRINTF_H +#define OPD_PRINTF_H + +/// log all sample file name manipulation; sample files open, close, +/// sfile LRU etc. voluminous. FIXME need to be splitted (filename manip, files +/// handling) ? +extern int vsfile; +/// log samples, voluminous. +extern int vsamples; +/// log arc, very voluminous. +extern int varcs; +/// kernel module handling +extern int vmodule; +/// all others not fitting in above category, not voluminous. +extern int vmisc; + +#define verbprintf(x, args...) \ + do { \ + /* look like fragile but we must catch verbrintf("%s", "") */ \ + if (x == 1) \ + printf(args); \ + } while (0) + +#endif /* OPD_PRINTF_H */ diff --git a/daemon/opd_sfile.c b/daemon/opd_sfile.c new file mode 100644 index 0000000..03ebf55 --- /dev/null +++ b/daemon/opd_sfile.c @@ -0,0 +1,619 @@ +/** + * @file daemon/opd_sfile.c + * Management of sample files + * + * @remark Copyright 2002, 2005 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "opd_sfile.h" + +#include "opd_trans.h" +#include "opd_kernel.h" +#include "opd_mangling.h" +#include "opd_anon.h" +#include "opd_printf.h" +#include "opd_stats.h" +#include "oprofiled.h" + +#include "op_libiberty.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define HASH_SIZE 2048 +#define HASH_BITS (HASH_SIZE - 1) + +/** All sfiles are hashed into these lists */ +static struct list_head hashes[HASH_SIZE]; + +/** All sfiles are on this list. */ +static LIST_HEAD(lru_list); + + +/* FIXME: can undoubtedly improve this hashing */ +/** Hash the transient parameters for lookup. */ +static unsigned long +sfile_hash(struct transient const * trans, struct kernel_image * ki) +{ + unsigned long val = 0; + + if (separate_thread) { + val ^= trans->tid << 2; + val ^= trans->tgid << 2; + } + + if (separate_kernel || ((trans->anon || separate_lib) && !ki)) + val ^= trans->app_cookie >> (DCOOKIE_SHIFT + 3); + + if (separate_cpu) + val ^= trans->cpu; + + /* cookie meaningless for kernel, shouldn't hash */ + if (trans->in_kernel) { + val ^= ki->start >> 14; + val ^= ki->end >> 7; + return val & HASH_BITS; + } + + if (trans->cookie != NO_COOKIE) { + val ^= trans->cookie >> DCOOKIE_SHIFT; + return val & HASH_BITS; + } + + if (!separate_thread) + val ^= trans->tgid << 2; + + if (trans->anon) { + val ^= trans->anon->start >> VMA_SHIFT; + val ^= trans->anon->end >> (VMA_SHIFT + 1); + } + + return val & HASH_BITS; +} + + +static int +do_match(struct sfile const * sf, cookie_t cookie, cookie_t app_cookie, + struct kernel_image const * ki, struct anon_mapping const * anon, + pid_t tgid, pid_t tid, unsigned int cpu) +{ + /* this is a simplified check for "is a kernel image" AND + * "is the right kernel image". Also handles no-vmlinux + * correctly. + */ + if (sf->kernel != ki) + return 0; + + if (separate_thread) { + if (sf->tid != tid || sf->tgid != tgid) + return 0; + } + + if (separate_cpu) { + if (sf->cpu != cpu) + return 0; + } + + if (separate_kernel || ((anon || separate_lib) && !ki)) { + if (sf->app_cookie != app_cookie) + return 0; + } + + /* ignore the cached trans->cookie for kernel images, + * it's meaningless and we checked all others already + */ + if (ki) + return 1; + + if (sf->anon != anon) + return 0; + + return sf->cookie == cookie; +} + + +static int +trans_match(struct transient const * trans, struct sfile const * sfile, + struct kernel_image const * ki) +{ + return do_match(sfile, trans->cookie, trans->app_cookie, ki, + trans->anon, trans->tgid, trans->tid, trans->cpu); +} + + +static int +sfile_equal(struct sfile const * sf, struct sfile const * sf2) +{ + return do_match(sf, sf2->cookie, sf2->app_cookie, sf2->kernel, + sf2->anon, sf2->tgid, sf2->tid, sf2->cpu); +} + + +static int +is_sf_ignored(struct sfile const * sf) +{ + if (sf->kernel) { + if (!is_image_ignored(sf->kernel->name)) + return 0; + + /* Let a dependent kernel image redeem the sf if we're + * executing on behalf of an application. + */ + return is_cookie_ignored(sf->app_cookie); + } + + /* Anon regions are always dependent on the application. + * Otherwise, let a dependent image redeem the sf. + */ + if (sf->anon || is_cookie_ignored(sf->cookie)) + return is_cookie_ignored(sf->app_cookie); + + return 0; +} + + +/** create a new sfile matching the current transient parameters */ +static struct sfile * +create_sfile(unsigned long hash, struct transient const * trans, + struct kernel_image * ki) +{ + size_t i; + struct sfile * sf; + + sf = xmalloc(sizeof(struct sfile)); + + sf->hashval = hash; + + /* The logic here: if we're in the kernel, the cached cookie is + * meaningless (though not the app_cookie if separate_kernel) + */ + sf->cookie = trans->in_kernel ? INVALID_COOKIE : trans->cookie; + sf->app_cookie = INVALID_COOKIE; + sf->tid = (pid_t)-1; + sf->tgid = (pid_t)-1; + sf->cpu = 0; + sf->kernel = ki; + sf->anon = trans->anon; + + for (i = 0 ; i < op_nr_counters ; ++i) + odb_init(&sf->files[i]); + + for (i = 0; i < CG_HASH_SIZE; ++i) + list_init(&sf->cg_hash[i]); + + if (separate_thread) + sf->tid = trans->tid; + if (separate_thread || trans->cookie == NO_COOKIE) + sf->tgid = trans->tgid; + + if (separate_cpu) + sf->cpu = trans->cpu; + + if (separate_kernel || ((trans->anon || separate_lib) && !ki)) + sf->app_cookie = trans->app_cookie; + + sf->ignored = is_sf_ignored(sf); + + sf->embedded_offset = trans->embedded_offset; + + /* If embedded_offset is a valid value, it means we're + * processing a Cell BE SPU profile; in which case, we + * want sf->app_cookie to hold trans->app_cookie. + */ + if (trans->embedded_offset != UNUSED_EMBEDDED_OFFSET) + sf->app_cookie = trans->app_cookie; + return sf; +} + + +struct sfile * sfile_find(struct transient const * trans) +{ + struct sfile * sf; + struct list_head * pos; + struct kernel_image * ki = NULL; + unsigned long hash; + + if (trans->tracing != TRACING_ON) { + opd_stats[OPD_SAMPLES]++; + opd_stats[trans->in_kernel == 1 ? OPD_KERNEL : OPD_PROCESS]++; + } + + /* There is a small race where this *can* happen, see + * caller of cpu_buffer_reset() in the kernel + */ + if (trans->in_kernel == -1) { + verbprintf(vsamples, "Losing sample at 0x%llx of unknown provenance.\n", + trans->pc); + opd_stats[OPD_NO_CTX]++; + return NULL; + } + + /* we might need a kernel image start/end to hash on */ + if (trans->in_kernel) { + ki = find_kernel_image(trans); + if (!ki) { + verbprintf(vsamples, "Lost kernel sample %llx\n", trans->pc); + opd_stats[OPD_LOST_KERNEL]++; + return NULL; + } + } else if (trans->cookie == NO_COOKIE && !trans->anon) { + if (vsamples) { + char const * app = verbose_cookie(trans->app_cookie); + printf("No anon map for pc %llx, app %s.\n", + trans->pc, app); + } + opd_stats[OPD_LOST_NO_MAPPING]++; + return NULL; + } + + hash = sfile_hash(trans, ki); + list_for_each(pos, &hashes[hash]) { + sf = list_entry(pos, struct sfile, hash); + if (trans_match(trans, sf, ki)) { + sfile_get(sf); + goto lru; + } + } + + sf = create_sfile(hash, trans, ki); + list_add(&sf->hash, &hashes[hash]); + +lru: + sfile_put(sf); + return sf; +} + + +static void sfile_dup(struct sfile * to, struct sfile * from) +{ + size_t i; + + memcpy(to, from, sizeof (struct sfile)); + + for (i = 0 ; i < op_nr_counters ; ++i) + odb_init(&to->files[i]); + + for (i = 0; i < CG_HASH_SIZE; ++i) + list_init(&to->cg_hash[i]); + + list_init(&to->hash); + list_init(&to->lru); +} + + +static odb_t * get_file(struct transient const * trans, int is_cg) +{ + struct sfile * sf = trans->current; + struct sfile * last = trans->last; + struct cg_entry * cg; + struct list_head * pos; + unsigned long hash; + odb_t * file; + + if (trans->event >= op_nr_counters) { + fprintf(stderr, "%s: Invalid counter %lu\n", __FUNCTION__, + trans->event); + abort(); + } + + file = &sf->files[trans->event]; + + if (!is_cg) + goto open; + + hash = last->hashval & (CG_HASH_SIZE - 1); + + /* Need to look for the right 'to'. Since we're looking for + * 'last', we use its hash. + */ + list_for_each(pos, &sf->cg_hash[hash]) { + cg = list_entry(pos, struct cg_entry, hash); + if (sfile_equal(last, &cg->to)) { + file = &cg->to.files[trans->event]; + goto open; + } + } + + cg = xmalloc(sizeof(struct cg_entry)); + sfile_dup(&cg->to, last); + list_add(&cg->hash, &sf->cg_hash[hash]); + file = &cg->to.files[trans->event]; + +open: + if (!odb_open_count(file)) + opd_open_sample_file(file, last, sf, trans->event, is_cg); + + /* Error is logged by opd_open_sample_file */ + if (!odb_open_count(file)) + return NULL; + + return file; +} + + +static void verbose_print_sample(struct sfile * sf, vma_t pc, uint counter) +{ + char const * app = verbose_cookie(sf->app_cookie); + printf("0x%llx(%u): ", pc, counter); + if (sf->anon) { + printf("anon (tgid %u, 0x%llx-0x%llx), ", + (unsigned int)sf->anon->tgid, + sf->anon->start, sf->anon->end); + } else if (sf->kernel) { + printf("kern (name %s, 0x%llx-0x%llx), ", sf->kernel->name, + sf->kernel->start, sf->kernel->end); + } else { + printf("%s(%llx), ", verbose_cookie(sf->cookie), sf->cookie); + } + printf("app %s(%llx)", app, sf->app_cookie); +} + + +static void verbose_sample(struct transient const * trans, vma_t pc) +{ + printf("Sample "); + verbose_print_sample(trans->current, pc, trans->event); + printf("\n"); +} + + +static void +verbose_arc(struct transient const * trans, vma_t from, vma_t to) +{ + printf("Arc "); + verbose_print_sample(trans->current, from, trans->event); + printf(" -> 0x%llx", to); + printf("\n"); +} + + +static void sfile_log_arc(struct transient const * trans) +{ + int err; + vma_t from = trans->pc; + vma_t to = trans->last_pc; + uint64_t key; + odb_t * file; + + file = get_file(trans, 1); + + /* absolute value -> offset */ + if (trans->current->kernel) + from -= trans->current->kernel->start; + + if (trans->last->kernel) + to -= trans->last->kernel->start; + + if (trans->current->anon) + from -= trans->current->anon->start; + + if (trans->last->anon) + to -= trans->last->anon->start; + + if (varcs) + verbose_arc(trans, from, to); + + if (!file) { + opd_stats[OPD_LOST_SAMPLEFILE]++; + return; + } + + /* Possible narrowings to 32-bit value only. */ + key = to & (0xffffffff); + key |= ((uint64_t)from) << 32; + + err = odb_update_node(file, key); + if (err) { + fprintf(stderr, "%s: %s\n", __FUNCTION__, strerror(err)); + abort(); + } +} + + +void sfile_log_sample(struct transient const * trans) +{ + int err; + vma_t pc = trans->pc; + odb_t * file; + + if (trans->tracing == TRACING_ON) { + /* can happen if kernel sample falls through the cracks, + * see opd_put_sample() */ + if (trans->last) + sfile_log_arc(trans); + return; + } + + file = get_file(trans, 0); + + /* absolute value -> offset */ + if (trans->current->kernel) + pc -= trans->current->kernel->start; + + if (trans->current->anon) + pc -= trans->current->anon->start; + + if (vsamples) + verbose_sample(trans, pc); + + if (!file) { + opd_stats[OPD_LOST_SAMPLEFILE]++; + return; + } + + err = odb_update_node(file, (uint64_t)pc); + if (err) { + fprintf(stderr, "%s: %s\n", __FUNCTION__, strerror(err)); + abort(); + } +} + + +static int close_sfile(struct sfile * sf, void * data __attribute__((unused))) +{ + size_t i; + + /* it's OK to close a non-open odb file */ + for (i = 0; i < op_nr_counters; ++i) + odb_close(&sf->files[i]); + + return 0; +} + + +static void kill_sfile(struct sfile * sf) +{ + close_sfile(sf, NULL); + list_del(&sf->hash); + list_del(&sf->lru); +} + + +static int sync_sfile(struct sfile * sf, void * data __attribute__((unused))) +{ + size_t i; + + for (i = 0; i < op_nr_counters; ++i) + odb_sync(&sf->files[i]); + + return 0; +} + + +static int is_sfile_kernel(struct sfile * sf, void * data __attribute__((unused))) +{ + return !!sf->kernel; +} + + +static int is_sfile_anon(struct sfile * sf, void * data) +{ + return sf->anon == data; +} + + +typedef int (*sfile_func)(struct sfile *, void *); + +static void +for_one_sfile(struct sfile * sf, sfile_func func, void * data) +{ + size_t i; + int free_sf = func(sf, data); + + for (i = 0; i < CG_HASH_SIZE; ++i) { + struct list_head * pos; + struct list_head * pos2; + list_for_each_safe(pos, pos2, &sf->cg_hash[i]) { + struct cg_entry * cg = + list_entry(pos, struct cg_entry, hash); + if (free_sf || func(&cg->to, data)) { + kill_sfile(&cg->to); + list_del(&cg->hash); + free(cg); + } + } + } + + if (free_sf) { + kill_sfile(sf); + free(sf); + } +} + + +static void for_each_sfile(sfile_func func, void * data) +{ + struct list_head * pos; + struct list_head * pos2; + + list_for_each_safe(pos, pos2, &lru_list) { + struct sfile * sf = list_entry(pos, struct sfile, lru); + for_one_sfile(sf, func, data); + } +} + + +void sfile_clear_kernel(void) +{ + for_each_sfile(is_sfile_kernel, NULL); +} + + +void sfile_clear_anon(struct anon_mapping * anon) +{ + for_each_sfile(is_sfile_anon, anon); +} + + +void sfile_sync_files(void) +{ + for_each_sfile(sync_sfile, NULL); +} + + +void sfile_close_files(void) +{ + for_each_sfile(close_sfile, NULL); +} + + +static int always_true(void) +{ + return 1; +} + + +#define LRU_AMOUNT 256 + +/* + * Clear out older sfiles. Note the current sfiles we're using + * will not be present in this list, due to sfile_get/put() pairs + * around the caller of this. + */ +int sfile_lru_clear(void) +{ + struct list_head * pos; + struct list_head * pos2; + int amount = LRU_AMOUNT; + + if (list_empty(&lru_list)) + return 1; + + list_for_each_safe(pos, pos2, &lru_list) { + struct sfile * sf; + if (!--amount) + break; + sf = list_entry(pos, struct sfile, lru); + for_one_sfile(sf, (sfile_func)always_true, NULL); + } + + return 0; +} + + +void sfile_get(struct sfile * sf) +{ + if (sf) + list_del(&sf->lru); +} + + +void sfile_put(struct sfile * sf) +{ + if (sf) + list_add_tail(&sf->lru, &lru_list); +} + + +void sfile_init(void) +{ + size_t i = 0; + + for (; i < HASH_SIZE; ++i) + list_init(&hashes[i]); +} diff --git a/daemon/opd_sfile.h b/daemon/opd_sfile.h new file mode 100644 index 0000000..86d5025 --- /dev/null +++ b/daemon/opd_sfile.h @@ -0,0 +1,113 @@ +/** + * @file daemon/opd_sfile.h + * Management of sample files + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OPD_SFILE_H +#define OPD_SFILE_H + +#include "opd_cookie.h" + +#include "odb.h" +#include "op_hw_config.h" +#include "op_types.h" +#include "op_list.h" + +#include <sys/types.h> + +struct kernel_image; +struct transient; + +#define CG_HASH_SIZE 16 +#define UNUSED_EMBEDDED_OFFSET ~0LLU + +/** + * Each set of sample files (where a set is over the physical counter + * types) will have one of these for it. We match against the + * descriptions here to find which sample DB file we need to modify. + * + * cg files are stored in the hash. + */ +struct sfile { + /** hash value for this sfile */ + unsigned long hashval; + /** cookie value for the binary profiled */ + cookie_t cookie; + /** cookie value for the application owner, INVALID_COOKIE if not set */ + cookie_t app_cookie; + /** thread ID, -1 if not set */ + pid_t tid; + /** thread group ID, -1 if not set */ + pid_t tgid; + /** CPU number */ + unsigned int cpu; + /** kernel image if applicable */ + struct kernel_image * kernel; + /** anonymous mapping */ + struct anon_mapping * anon; + /** embedded offset for Cell BE SPU */ + uint64_t embedded_offset; + + /** hash table link */ + struct list_head hash; + /** lru list */ + struct list_head lru; + /** true if this file should be ignored in profiles */ + int ignored; + /** opened sample files */ + odb_t files[OP_MAX_COUNTERS]; + /** hash table of opened cg sample files */ + struct list_head cg_hash[CG_HASH_SIZE]; +}; + +/** a call-graph entry */ +struct cg_entry { + /** where arc is to */ + struct sfile to; + /** next in the hash slot */ + struct list_head hash; +}; + +/** clear any sfiles that are for the kernel */ +void sfile_clear_kernel(void); + +struct anon_mapping; + +/** clear any sfiles for the given anon mapping */ +void sfile_clear_anon(struct anon_mapping *); + +/** sync sample files */ +void sfile_sync_files(void); + +/** close sample files */ +void sfile_close_files(void); + +/** clear out a certain amount of LRU entries + * return non-zero if the lru is already empty */ +int sfile_lru_clear(void); + +/** remove a sfile from the lru list, protecting it from sfile_lru_clear() */ +void sfile_get(struct sfile * sf); + +/** add this sfile to lru list */ +void sfile_put(struct sfile * sf); + +/** + * Find the sfile for the current parameters. Note that is required + * that the PC value be set appropriately (needed for kernel images) + */ +struct sfile * sfile_find(struct transient const * trans); + +/** Log the sample in a previously located sfile. */ +void sfile_log_sample(struct transient const * trans); + +/** initialise hashes */ +void sfile_init(void); + +#endif /* OPD_SFILE_H */ diff --git a/daemon/opd_spu.c b/daemon/opd_spu.c new file mode 100644 index 0000000..62a2c2b --- /dev/null +++ b/daemon/opd_spu.c @@ -0,0 +1,176 @@ +/** + * @file daemon/opd_spu.c + * Processing the sample buffer for Cell BE SPU profile + * + * @remark Copyright 2007 OProfile authors + * @remark Read the file COPYING + * + * @author Maynard Johnson + * (C) Copyright IBM Corporation 2007 + */ + +#include "opd_interface.h" +#include "opd_printf.h" +#include "opd_sfile.h" +#include "opd_stats.h" +#include "opd_trans.h" +#include "op_libiberty.h" + +#include <stdlib.h> +#include <stdio.h> + +struct spu_context_info { + pid_t tid; + pid_t tgid; + cookie_t app_cookie; + uint64_t embedded_offset; + cookie_t spu_cookie; +}; + +static struct spu_context_info * spu_context_cache; + +/* Forward declaration */ +static void process_spu_samples(struct transient * trans); + +void (*special_processor)(struct transient *); + +/* + * This function is called when the first value found in the + * buffer (after the beginning ESCAPE_CODE) is SPU_PROFILING_CODE. + * Once we get here, the rest of the processing of the buffer is + * Cell-SPU-specific, so we do not need to return until the + * trans.buffer is empty. + */ +void code_spu_profiling(struct transient * trans) +{ + /* Next value in buffer is the number of SPUs. */ + unsigned long long num_spus = pop_buffer_value(trans); + /* Free the cache from previous run */ + free(spu_context_cache); + spu_context_cache = xmalloc(sizeof(struct spu_context_info) * num_spus); + special_processor = process_spu_samples; + process_spu_samples(trans); +} + +void code_spu_ctx_switch(struct transient * trans) +{ + clear_trans_current(trans); + + if (!enough_remaining(trans, 6)) { + trans->remaining = 0; + return; + } + + /* First value in the buffer for an SPU context switch is + * the SPU number. For SPU profiling, 'cpu' = 'spu'. + */ + trans->cpu = pop_buffer_value(trans); + trans->tid = pop_buffer_value(trans); + trans->tgid = pop_buffer_value(trans); + trans->app_cookie = pop_buffer_value(trans); + + if (vmisc) { + char const * app = find_cookie(trans->app_cookie); + printf("SPU_CTX_SWITCH to tid %lu, tgid %lu, cookie %llx(%s)\n", + (unsigned long)trans->tid, (unsigned long)trans->tgid, + trans->app_cookie, app ? app : "none"); + } + + /* The trans->cookie will point to the binary file where the SPU ELF + * can be found. If the SPU ELF is embedded, it may be embedded in + * either the executable application binary or a shared lib. If shared + * library, then trans->cookie will differ from the previously obtained + * trans->app_cookie. For the non-embedded case, trans->cookie always + * points to a separate binary file. + */ + trans->cookie = pop_buffer_value(trans); + trans->embedded_offset = pop_buffer_value(trans); +} + + +static void cache_spu_context_info(struct transient * trans) +{ + int i = trans->cpu; + spu_context_cache[i].tid = trans->tid; + spu_context_cache[i].tgid = trans->tgid; + spu_context_cache[i].app_cookie = trans->app_cookie; + spu_context_cache[i].embedded_offset = trans->embedded_offset; + spu_context_cache[i].spu_cookie = trans->cookie; +} + +static void update_trans_for_spu(struct transient * trans) +{ + int i = trans->cpu; + trans->tid = spu_context_cache[i].tid; + trans->tgid = spu_context_cache[i].tgid; + trans->app_cookie = spu_context_cache[i].app_cookie; + trans->embedded_offset = spu_context_cache[i].embedded_offset; + trans->cookie = spu_context_cache[i].spu_cookie; +} +#define SPU_NUM_MASK 0xFFFFFFFF00000000ULL +#define SPU_CYCLES_COUNTER 0 + +static void opd_put_spu_sample +(struct transient * trans, unsigned long long pc) +{ + unsigned long spu_number = (pc & SPU_NUM_MASK) >> 32; + if (trans->cpu != spu_number) { + trans->cpu = spu_number; + clear_trans_current(trans); + update_trans_for_spu(trans); + } + /* get the current sfile if needed */ + if (!trans->current) + trans->current = sfile_find(trans); + + if (trans->tracing != TRACING_ON) + trans->event = SPU_CYCLES_COUNTER; + + trans->pc = (pc & ~SPU_NUM_MASK); + /* log the sample or arc */ + sfile_log_sample(trans); + + /* switch to trace mode */ + if (trans->tracing == TRACING_START) + trans->tracing = TRACING_ON; + + update_trans_last(trans); +} + +/* + * This function processes SPU context switches and + * SPU program counter samples. After processing a + * context switch (via handlers[code)), we cache the + * SPU context information that has been temporarily + * stored in trans. + */ +static void process_spu_samples(struct transient * trans) +{ + unsigned long long code; + trans->in_kernel = 0; + while (trans->remaining) { + code = pop_buffer_value(trans); + + if (!is_escape_code(code)) { + opd_put_spu_sample(trans, code); + continue; + } + + if (!trans->remaining) { + verbprintf(vmisc, "Dangling ESCAPE_CODE.\n"); + opd_stats[OPD_DANGLING_CODE]++; + break; + } + + /* started with ESCAPE_CODE, next is type */ + code = pop_buffer_value(trans); + + if (code >= LAST_CODE) { + fprintf(stderr, "Unknown code %llu\n", code); + abort(); + } + + handlers[code](trans); + cache_spu_context_info(trans); + } +} diff --git a/daemon/opd_stats.c b/daemon/opd_stats.c new file mode 100644 index 0000000..ddb1940 --- /dev/null +++ b/daemon/opd_stats.c @@ -0,0 +1,85 @@ +/** + * @file daemon/opd_stats.c + * Management of daemon statistics + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "opd_stats.h" +#include "oprofiled.h" + +#include "op_get_time.h" + +#include <dirent.h> +#include <stdlib.h> +#include <stdio.h> + +unsigned long opd_stats[OPD_MAX_STATS]; + +/** + * print_if - print an integer value read from file filename, + * do nothing if the value read == -1 except if force is non-zero + */ +static void print_if(char const * fmt, char const * path, char const * filename, int force) +{ + int value = opd_read_fs_int(path, filename, 0); + if (value != -1 || force) + printf(fmt, value); +} + +/** + * opd_print_stats - print out latest statistics + */ +void opd_print_stats(void) +{ + DIR * dir; + struct dirent * dirent; + + printf("\n%s\n", op_get_time()); + printf("Nr. sample dumps: %lu\n", opd_stats[OPD_DUMP_COUNT]); + printf("Nr. non-backtrace samples: %lu\n", opd_stats[OPD_SAMPLES]); + printf("Nr. kernel samples: %lu\n", opd_stats[OPD_KERNEL]); + printf("Nr. lost samples (no kernel/user): %lu\n", opd_stats[OPD_NO_CTX]); + printf("Nr. lost kernel samples: %lu\n", opd_stats[OPD_LOST_KERNEL]); + printf("Nr. incomplete code structs: %lu\n", opd_stats[OPD_DANGLING_CODE]); + printf("Nr. samples lost due to sample file open failure: %lu\n", + opd_stats[OPD_LOST_SAMPLEFILE]); + printf("Nr. samples lost due to no permanent mapping: %lu\n", + opd_stats[OPD_LOST_NO_MAPPING]); + print_if("Nr. event lost due to buffer overflow: %u\n", + "/dev/oprofile/stats", "event_lost_overflow", 1); + print_if("Nr. samples lost due to no mapping: %u\n", + "/dev/oprofile/stats", "sample_lost_no_mapping", 1); + print_if("Nr. backtraces skipped due to no file mapping: %u\n", + "/dev/oprofile/stats", "bt_lost_no_mapping", 0); + print_if("Nr. samples lost due to no mm: %u\n", + "/dev/oprofile/stats", "sample_lost_no_mm", 1); + + if (!(dir = opendir("/dev/oprofile/stats/"))) + goto out; + while ((dirent = readdir(dir))) { + int cpu_nr; + char path[256]; + if (sscanf(dirent->d_name, "cpu%d", &cpu_nr) != 1) + continue; + snprintf(path, 256, "/dev/oprofile/stats/%s", dirent->d_name); + + print_if("Nr. samples lost cpu buffer overflow: %u\n", + path, "sample_lost_overflow", 1); + print_if("Nr. samples lost task exit: %u\n", + path, "sample_lost_task_exit", 0); + print_if("Nr. samples received: %u\n", + path, "sample_received", 1); + print_if("Nr. backtrace aborted: %u\n", + path, "backtrace_aborted", 0); + print_if("Nr. samples lost invalid pc: %u\n", + path, "sample_invalid_eip", 0); + } + closedir(dir); +out: + fflush(stdout); +} diff --git a/daemon/opd_stats.h b/daemon/opd_stats.h new file mode 100644 index 0000000..177b86a --- /dev/null +++ b/daemon/opd_stats.h @@ -0,0 +1,31 @@ +/** + * @file daemon/opd_stats.h + * Management of daemon statistics + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OPD_STATS_H +#define OPD_STATS_H + +extern unsigned long opd_stats[]; + +enum { OPD_SAMPLES, /**< nr. samples */ + OPD_KERNEL, /**< nr. kernel samples */ + OPD_PROCESS, /**< nr. userspace samples */ + OPD_NO_CTX, /**< nr. samples lost due to not knowing if in the kernel or not */ + OPD_LOST_KERNEL, /**< nr. kernel samples lost */ + OPD_LOST_SAMPLEFILE, /**< nr samples for which sample file can't be opened */ + OPD_LOST_NO_MAPPING, /**< nr samples lost due to no mapping */ + OPD_DUMP_COUNT, /**< nr. of times buffer is read */ + OPD_DANGLING_CODE, /**< nr. partial code notifications (buffer overflow */ + OPD_MAX_STATS /**< end of stats */ +}; + +void opd_print_stats(void); + +#endif /* OPD_STATS_H */ diff --git a/daemon/opd_trans.c b/daemon/opd_trans.c new file mode 100644 index 0000000..871e6e6 --- /dev/null +++ b/daemon/opd_trans.c @@ -0,0 +1,349 @@ +/** + * @file daemon/opd_trans.c + * Processing the sample buffer + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * Modified by Aravind Menon for Xen + * These modifications are: + * Copyright (C) 2005 Hewlett-Packard Co. + * + * Modified by Maynard Johnson <maynardj@us.ibm.com> + * These modifications are: + * (C) Copyright IBM Corporation 2007 + */ + +#include "opd_trans.h" +#include "opd_kernel.h" +#include "opd_sfile.h" +#include "opd_anon.h" +#include "opd_stats.h" +#include "opd_printf.h" +#include "opd_interface.h" + +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <errno.h> + +extern size_t kernel_pointer_size; + + +void clear_trans_last(struct transient * trans) +{ + trans->last = NULL; + trans->last_anon = NULL; +} + + +void clear_trans_current(struct transient * trans) +{ + trans->current = NULL; + trans->anon = NULL; +} + + +uint64_t pop_buffer_value(struct transient * trans) +{ + uint64_t val; + + if (!trans->remaining) { + fprintf(stderr, "BUG: popping empty buffer !\n"); + abort(); + } + + if (kernel_pointer_size == 4) { + uint32_t const * lbuf = (void const *)trans->buffer; + val = *lbuf; + } else { + uint64_t const * lbuf = (void const *)trans->buffer; + val = *lbuf; + } + + trans->remaining--; + trans->buffer += kernel_pointer_size; + return val; +} + + +int enough_remaining(struct transient * trans, size_t size) +{ + if (trans->remaining >= size) + return 1; + + verbprintf(vmisc, "Dangling ESCAPE_CODE.\n"); + opd_stats[OPD_DANGLING_CODE]++; + return 0; +} + + +static void opd_put_sample(struct transient * trans, unsigned long long pc) +{ + unsigned long long event; + + if (!enough_remaining(trans, 1)) { + trans->remaining = 0; + return; + } + + event = pop_buffer_value(trans); + + if (trans->tracing != TRACING_ON) + trans->event = event; + + trans->pc = pc; + + /* sfile can change at each sample for kernel */ + if (trans->in_kernel != 0) + clear_trans_current(trans); + + if (!trans->in_kernel && trans->cookie == NO_COOKIE) + trans->anon = find_anon_mapping(trans); + + /* get the current sfile if needed */ + if (!trans->current) + trans->current = sfile_find(trans); + + /* + * can happen if kernel sample falls through the cracks, or if + * it's a sample from an anon region we couldn't find + */ + if (!trans->current) + goto out; + + /* FIXME: this logic is perhaps too harsh? */ + if (trans->current->ignored || (trans->last && trans->last->ignored)) + goto out; + + /* log the sample or arc */ + sfile_log_sample(trans); + +out: + /* switch to trace mode */ + if (trans->tracing == TRACING_START) + trans->tracing = TRACING_ON; + + update_trans_last(trans); +} + + +static void code_unknown(struct transient * trans __attribute__((unused))) +{ + fprintf(stderr, "Unknown code !\n"); + abort(); +} + + +static void code_ctx_switch(struct transient * trans) +{ + clear_trans_current(trans); + + if (!enough_remaining(trans, 5)) { + trans->remaining = 0; + return; + } + + trans->tid = pop_buffer_value(trans); + trans->app_cookie = pop_buffer_value(trans); + /* must be ESCAPE_CODE, CTX_TGID_CODE, tgid. Like this + * because tgid was added later in a compatible manner. + */ + pop_buffer_value(trans); + pop_buffer_value(trans); + trans->tgid = pop_buffer_value(trans); + + if (vmisc) { + char const * app = find_cookie(trans->app_cookie); + printf("CTX_SWITCH to tid %lu, tgid %lu, cookie %llx(%s)\n", + (unsigned long)trans->tid, (unsigned long)trans->tgid, + trans->app_cookie, app ? app : "none"); + } +} + + +static void code_cpu_switch(struct transient * trans) +{ + clear_trans_current(trans); + + if (!enough_remaining(trans, 1)) { + trans->remaining = 0; + return; + } + + trans->cpu = pop_buffer_value(trans); + verbprintf(vmisc, "CPU_SWITCH to %lu\n", trans->cpu); +} + + +static void code_cookie_switch(struct transient * trans) +{ + clear_trans_current(trans); + + if (!enough_remaining(trans, 1)) { + trans->remaining = 0; + return; + } + + trans->cookie = pop_buffer_value(trans); + + if (vmisc) { + char const * name = verbose_cookie(trans->cookie); + verbprintf(vmisc, "COOKIE_SWITCH to cookie %s(%llx)\n", + name, trans->cookie); + } +} + + +static void code_kernel_enter(struct transient * trans) +{ + verbprintf(vmisc, "KERNEL_ENTER_SWITCH to kernel\n"); + trans->in_kernel = 1; + clear_trans_current(trans); + /* subtlety: we must keep trans->cookie cached, + * even though it's meaningless for the kernel - + * we won't necessarily get a cookie switch on + * kernel exit. See comments in opd_sfile.c + */ +} + + +static void code_user_enter(struct transient * trans) +{ + verbprintf(vmisc, "USER_ENTER_SWITCH to user-space\n"); + trans->in_kernel = 0; + clear_trans_current(trans); + clear_trans_last(trans); +} + + +static void code_module_loaded(struct transient * trans __attribute__((unused))) +{ + verbprintf(vmodule, "MODULE_LOADED_CODE\n"); + opd_reread_module_info(); + clear_trans_current(trans); + clear_trans_last(trans); +} + + +/* + * This also implicitly signals the end of the previous + * trace, so we never explicitly set TRACING_OFF when + * processing a buffer. + */ +static void code_trace_begin(struct transient * trans) +{ + verbprintf(varcs, "TRACE_BEGIN\n"); + trans->tracing = TRACING_START; +} + +static void code_xen_enter(struct transient * trans) +{ + verbprintf(vmisc, "XEN_ENTER_SWITCH to xen\n"); + trans->in_kernel = 1; + trans->current = NULL; + /* subtlety: we must keep trans->cookie cached, even though it's + * meaningless for Xen - we won't necessarily get a cookie switch + * on Xen exit. See comments in opd_sfile.c. It seems that we can + * get away with in_kernel = 1 as long as we supply the correct + * Xen image, and its address range in startup find_kernel_image + * is modified to look in the Xen image also + */ +} + +extern void code_spu_profiling(struct transient * trans); +extern void code_spu_ctx_switch(struct transient * trans); + +handler_t handlers[LAST_CODE + 1] = { + &code_unknown, + &code_ctx_switch, + &code_cpu_switch, + &code_cookie_switch, + &code_kernel_enter, + &code_user_enter, + &code_module_loaded, + /* tgid handled differently */ + &code_unknown, + &code_trace_begin, + &code_unknown, + &code_xen_enter, +#if defined(__powerpc__) + &code_spu_profiling, + &code_spu_ctx_switch, +#endif + &code_unknown, +}; + +extern void (*special_processor)(struct transient *); + +void opd_process_samples(char const * buffer, size_t count) +{ + struct transient trans = { + .buffer = buffer, + .remaining = count, + .tracing = TRACING_OFF, + .current = NULL, + .last = NULL, + .cookie = INVALID_COOKIE, + .app_cookie = INVALID_COOKIE, + .anon = NULL, + .last_anon = NULL, + .pc = 0, + .last_pc = 0, + .event = 0, + .in_kernel = -1, + .cpu = -1, + .tid = -1, + .embedded_offset = UNUSED_EMBEDDED_OFFSET, + .tgid = -1 + }; + + /* FIXME: was uint64_t but it can't compile on alpha where uint64_t + * is an unsigned long and below the printf("..." %llu\n", code) + * generate a warning, this look like a stopper to use c98 types :/ + */ + unsigned long long code; + + if (special_processor) { + special_processor(&trans); + return; + } + + int i; + + for (i = 0; i < count && i < 200; i++) { + verbprintf(vmisc, "buffer[%d] is %x\n", i, buffer[i]); + } + + while (trans.remaining) { + code = pop_buffer_value(&trans); + + verbprintf(vmisc, "In opd_process_samples (code is %lld)\n", code); + + if (!is_escape_code(code)) { + opd_put_sample(&trans, code); + continue; + } + + if (!trans.remaining) { + verbprintf(vmisc, "Dangling ESCAPE_CODE.\n"); + opd_stats[OPD_DANGLING_CODE]++; + break; + } + + // started with ESCAPE_CODE, next is type + code = pop_buffer_value(&trans); + + verbprintf(vmisc, "next code is %lld\n", code); + if (code >= LAST_CODE) { + fprintf(stderr, "Unknown code %llu\n", code); + abort(); + } + + handlers[code](&trans); + } +} diff --git a/daemon/opd_trans.h b/daemon/opd_trans.h new file mode 100644 index 0000000..ab4e816 --- /dev/null +++ b/daemon/opd_trans.h @@ -0,0 +1,85 @@ +/** + * @file daemon/opd_trans.h + * Processing the sample buffer + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * + * Modified by Maynard Johnson <maynardj@us.ibm.com> + * These modifications are: + * (C) Copyright IBM Corporation 2007 + */ + +#ifndef OPD_TRANS_H +#define OPD_TRANS_H + +#include "opd_cookie.h" +#include "op_types.h" + +#include <stdint.h> + +struct sfile; +struct anon_mapping; + +enum tracing_type { + TRACING_OFF, + TRACING_START, + TRACING_ON +}; + +/** + * Transient values used for parsing the event buffer. + * Note that these are reset for each buffer read, but + * that should be ok as in the kernel, cpu_buffer_reset() + * ensures that a correct context starts off the buffer. + */ +struct transient { + char const * buffer; + size_t remaining; + enum tracing_type tracing; + struct sfile * current; + struct sfile * last; + struct anon_mapping * anon; + struct anon_mapping * last_anon; + cookie_t cookie; + cookie_t app_cookie; + vma_t pc; + vma_t last_pc; + unsigned long event; + int in_kernel; + unsigned long cpu; + pid_t tid; + pid_t tgid; + uint64_t embedded_offset; +}; + +typedef void (*handler_t)(struct transient *); +extern handler_t handlers[]; + +uint64_t pop_buffer_value(struct transient * trans); +int enough_remaining(struct transient * trans, size_t size); +static inline void update_trans_last(struct transient * trans) +{ + trans->last = trans->current; + trans->last_anon = trans->anon; + trans->last_pc = trans->pc; +} + +extern size_t kernel_pointer_size; +static inline int is_escape_code(uint64_t code) +{ + return kernel_pointer_size == 4 ? code == ~0LU : code == ~0LLU; +} + +void opd_process_samples(char const * buffer, size_t count); + +/** used when we need to clear data that's been freed */ +void clear_trans_last(struct transient * trans); + +/** used when we need to clear data that's been freed */ +void clear_trans_current(struct transient * trans); + +#endif /* OPD_TRANS_H */ diff --git a/daemon/oprofiled.c b/daemon/oprofiled.c new file mode 100644 index 0000000..ec2ea1b --- /dev/null +++ b/daemon/oprofiled.c @@ -0,0 +1,522 @@ +/** + * @file daemon/oprofiled.c + * Initialisation and setup + * + * @remark Copyright 2002, 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * Modified by Aravind Menon for Xen + * These modifications are: + * Copyright (C) 2005 Hewlett-Packard Co. + */ + +#include "config.h" + +#include "oprofiled.h" +#include "opd_printf.h" +#include "opd_events.h" + +#include "op_config.h" +#include "op_version.h" +#include "op_hw_config.h" +#include "op_libiberty.h" +#include "op_file.h" +#include "op_abi.h" +#include "op_string.h" +#include "op_cpu_type.h" +#include "op_popt.h" +#include "op_lockfile.h" +#include "op_list.h" +#include "op_fileio.h" + +#include <sys/types.h> +#include <sys/resource.h> +#include <stdlib.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> +#include <dirent.h> +#include <limits.h> + +sig_atomic_t signal_alarm; +sig_atomic_t signal_hup; +sig_atomic_t signal_term; +sig_atomic_t signal_child; +sig_atomic_t signal_usr1; +sig_atomic_t signal_usr2; + +uint op_nr_counters; +op_cpu cpu_type; +int vsfile; +int vsamples; +int varcs; +int vmodule; +int vmisc; +int separate_lib; +int separate_kernel; +int separate_thread; +int separate_cpu; +int no_vmlinux; +char * vmlinux; +char * kernel_range; +char * session_dir; +int no_xen; +char * xenimage; +char * xen_range; +static char * verbose; +static char * binary_name_filter; +static char * events; +static int showvers; +static struct oprofiled_ops * opd_ops; +extern struct oprofiled_ops opd_24_ops; +extern struct oprofiled_ops opd_26_ops; + +#define OPD_IMAGE_FILTER_HASH_SIZE 32 +static struct list_head images_filter[OPD_IMAGE_FILTER_HASH_SIZE]; + +static struct poptOption options[] = { + { "session-dir", 0, POPT_ARG_STRING, &session_dir, 0, "place sample database in dir instead of default location", "/var/lib/oprofile", }, + { "kernel-range", 'r', POPT_ARG_STRING, &kernel_range, 0, "Kernel VMA range", "start-end", }, + { "vmlinux", 'k', POPT_ARG_STRING, &vmlinux, 0, "vmlinux kernel image", "file", }, + { "no-vmlinux", 0, POPT_ARG_NONE, &no_vmlinux, 0, "vmlinux kernel image file not available", NULL, }, + { "xen-range", 0, POPT_ARG_STRING, &xen_range, 0, "Xen VMA range", "start-end", }, + { "xen-image", 0, POPT_ARG_STRING, &xenimage, 0, "Xen image", "file", }, + { "image", 0, POPT_ARG_STRING, &binary_name_filter, 0, "image name filter", "profile these comma separated image" }, + { "separate-lib", 0, POPT_ARG_INT, &separate_lib, 0, "separate library samples for each distinct application", "[0|1]", }, + { "separate-kernel", 0, POPT_ARG_INT, &separate_kernel, 0, "separate kernel samples for each distinct application", "[0|1]", }, + { "separate-thread", 0, POPT_ARG_INT, &separate_thread, 0, "thread-profiling mode", "[0|1]" }, + { "separate-cpu", 0, POPT_ARG_INT, &separate_cpu, 0, "separate samples for each CPU", "[0|1]" }, + { "events", 'e', POPT_ARG_STRING, &events, 0, "events list", "[events]" }, + { "version", 'v', POPT_ARG_NONE, &showvers, 0, "show version", NULL, }, + { "verbose", 'V', POPT_ARG_STRING, &verbose, 0, "be verbose in log file", "all,sfile,arcs,samples,module,misc", }, + POPT_AUTOHELP + { NULL, 0, 0, NULL, 0, NULL, NULL, }, +}; + + +void opd_open_logfile(void) +{ + if (open(op_log_file, O_WRONLY|O_CREAT|O_NOCTTY|O_APPEND, 0644) == -1) { + perror("oprofiled: couldn't re-open stdout: "); + exit(EXIT_FAILURE); + } + + if (dup2(1, 2) == -1) { + perror("oprofiled: couldn't dup stdout to stderr: "); + exit(EXIT_FAILURE); + } +} + + +/** + * opd_fork - fork and return as child + * + * fork() and exit the parent with _exit(). + * Failure is fatal. + */ +static void opd_fork(void) +{ + switch (fork()) { + case -1: + perror("oprofiled: fork() failed: "); + exit(EXIT_FAILURE); + break; + case 0: + break; + default: + /* parent */ + _exit(EXIT_SUCCESS); + break; + } +} + + +static void opd_go_daemon(void) +{ + opd_fork(); + + if (chdir(op_session_dir)) { + fprintf(stderr, "oprofiled: opd_go_daemon: couldn't chdir to %s: %s", + op_session_dir, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (setsid() < 0) { + perror("oprofiled: opd_go_daemon: couldn't setsid: "); + exit(EXIT_FAILURE); + } + + opd_fork(); +} + + +static void opd_write_abi(void) +{ + char * cbuf; + + cbuf = xmalloc(strlen(op_session_dir) + 5); + strcpy(cbuf, op_session_dir); + strcat(cbuf, "/abi"); + op_write_abi_to_file(cbuf); + free(cbuf); +} + + +/** + * opd_alarm - sync files and report stats + */ +static void opd_alarm(int val __attribute__((unused))) +{ + signal_alarm = 1; +} + + +/* re-open logfile for logrotate */ +static void opd_sighup(int val __attribute__((unused))) +{ + signal_hup = 1; +} + + +static void opd_sigterm(int val __attribute__((unused))) +{ + signal_term = 1; +} + +static void opd_sigchild(int val __attribute__((unused))) +{ + signal_child = 1; +} + + +static void opd_sigusr1(int val __attribute__((unused))) +{ + signal_usr1 = 1; +} + + +static void opd_sigusr2(int val __attribute__((unused))) +{ + signal_usr2 = 1; +} + + +static void opd_setup_signals(void) +{ + struct sigaction act; + + act.sa_handler = opd_alarm; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + if (sigaction(SIGALRM, &act, NULL)) { + perror("oprofiled: install of SIGALRM handler failed: "); + exit(EXIT_FAILURE); + } + + act.sa_handler = opd_sighup; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGALRM); + + if (sigaction(SIGHUP, &act, NULL)) { + perror("oprofiled: install of SIGHUP handler failed: "); + exit(EXIT_FAILURE); + } + + act.sa_handler = opd_sigterm; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGTERM); + + if (sigaction(SIGTERM, &act, NULL)) { + perror("oprofiled: install of SIGTERM handler failed: "); + exit(EXIT_FAILURE); + } + + act.sa_handler = opd_sigchild; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGCHLD); + + if (sigaction(SIGCHLD, &act, NULL)) { + perror("oprofiled: install of SIGCHLD handler failed: "); + exit(EXIT_FAILURE); + } + + act.sa_handler = opd_sigusr1; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGTERM); + + if (sigaction(SIGUSR1, &act, NULL)) { + perror("oprofiled: install of SIGUSR1 handler failed: "); + exit(EXIT_FAILURE); + } + + act.sa_handler = opd_sigusr2; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGTERM); + + if (sigaction(SIGUSR2, &act, NULL)) { + perror("oprofiled: install of SIGUSR2 handler failed: "); + exit(EXIT_FAILURE); + } +} + + +struct opd_hashed_name { + char * name; + struct list_head next; +}; + + +static void add_image_filter(char const * name) +{ + size_t hash; + struct opd_hashed_name * elt = xmalloc(sizeof(struct opd_hashed_name)); + elt->name = xmalloc(PATH_MAX); + if (!realpath(name, elt->name)) { + free(elt->name); + free(elt); + return; + } + hash = op_hash_string(elt->name); + verbprintf(vmisc, "Adding to image filter: \"%s\"\n", elt->name); + list_add(&elt->next, &images_filter[hash % OPD_IMAGE_FILTER_HASH_SIZE]); +} + + +static void opd_parse_image_filter(void) +{ + size_t i; + char const * last = binary_name_filter; + char const * cur = binary_name_filter; + + if (!binary_name_filter) + return; + + for (i = 0; i < OPD_IMAGE_FILTER_HASH_SIZE; ++i) + list_init(&images_filter[i]); + + while ((cur = strchr(last, ',')) != NULL) { + char * tmp = op_xstrndup(last, cur - last); + add_image_filter(tmp); + free(tmp); + last = cur + 1; + } + add_image_filter(last); +} + + +int is_image_ignored(char const * name) +{ + size_t hash; + struct list_head * pos; + + if (!binary_name_filter) + return 0; + + hash = op_hash_string(name); + + list_for_each(pos, &images_filter[hash % OPD_IMAGE_FILTER_HASH_SIZE]) { + struct opd_hashed_name * hashed_name = + list_entry(pos, struct opd_hashed_name, next); + if (!strcmp(hashed_name->name, name)) + return 0; + } + + return 1; +} + + +/** return the int in the given oprofilefs file */ +int opd_read_fs_int(char const * path, char const * name, int fatal) +{ + char filename[PATH_MAX + 1]; + snprintf(filename, PATH_MAX, "%s/%s", path, name); + return op_read_int_from_file(filename, fatal); +} + + +static void opd_handle_verbose_option(char const * name) +{ + if (!strcmp(name, "all")) { + vsfile = 1; + vsamples = 1; + varcs = 1; + vmodule = 1; + vmisc = 1; + } else if (!strcmp(name, "sfile")) { + vsfile = 1; + } else if (!strcmp(name, "arcs")) { + varcs = 1; + } else if (!strcmp(name, "samples")) { + vsamples = 1; + } else if (!strcmp(name, "module")) { + vmodule = 1; + } else if (!strcmp(name, "misc")) { + vmisc = 1; + } else { + fprintf(stderr, "unknown verbose options\n"); + exit(EXIT_FAILURE); + } +} + +static void opd_parse_verbose(void) +{ + char const * last = verbose; + char const * cur = verbose; + + if (!verbose) + return; + + while ((cur = strchr(last, ',')) != NULL) { + char * tmp = op_xstrndup(last, cur - last); + opd_handle_verbose_option(tmp); + free(tmp); + last = cur + 1; + } + opd_handle_verbose_option(last); +} + + +static void opd_options(int argc, char const * argv[]) +{ + poptContext optcon; + char * tmp; + + optcon = op_poptGetContext(NULL, argc, argv, options, 0); + + if (showvers) + show_version(argv[0]); + + opd_parse_verbose(); + + if (separate_kernel) + separate_lib = 1; + + cpu_type = op_get_cpu_type(); + op_nr_counters = op_get_nr_counters(cpu_type); + + if (!no_vmlinux) { + if (!vmlinux || !strcmp("", vmlinux)) { + fprintf(stderr, "oprofiled: no vmlinux specified.\n"); + poptPrintHelp(optcon, stderr, 0); + exit(EXIT_FAILURE); + } + + /* canonicalise vmlinux filename. fix #637805 */ + tmp = xmalloc(PATH_MAX); + if (realpath(vmlinux, tmp)) + vmlinux = tmp; + else + free(tmp); + + if (!kernel_range || !strcmp("", kernel_range)) { + fprintf(stderr, "oprofiled: no kernel VMA range specified.\n"); + poptPrintHelp(optcon, stderr, 0); + exit(EXIT_FAILURE); + } + } + + if (events == NULL) { + fprintf(stderr, "oprofiled: no events specified.\n"); + poptPrintHelp(optcon, stderr, 0); + exit(EXIT_FAILURE); + } + + if (!xenimage || !strcmp("", xenimage)) { + no_xen = 1; + } else { + no_xen = 0; + + /* canonicalise xen image filename. */ + tmp = xmalloc(PATH_MAX); + if (realpath(xenimage, tmp)) + xenimage = tmp; + else + free(tmp); + + if (!xen_range || !strcmp("", xen_range)) { + fprintf(stderr, "oprofiled: no Xen VMA range specified.\n"); + poptPrintHelp(optcon, stderr, 0); + exit(EXIT_FAILURE); + } + } + + opd_parse_events(events); + + opd_parse_image_filter(); + + poptFreeContext(optcon); +} + + +/* determine what kernel we're running and which daemon + * to use + */ +static struct oprofiled_ops * get_ops(void) +{ + switch (op_get_interface()) { +#ifndef ANDROID + case OP_INTERFACE_24: + printf("Using 2.4 OProfile kernel interface.\n"); + return &opd_24_ops; +#endif + case OP_INTERFACE_26: + printf("Using 2.6+ OProfile kernel interface.\n"); + return &opd_26_ops; + default: + break; + } + + fprintf(stderr, "Couldn't determine kernel version.\n"); + exit(EXIT_FAILURE); + return NULL; +} + + +int main(int argc, char const * argv[]) +{ + int err; + struct rlimit rlim = { 2048, 2048 }; + + opd_options(argc, argv); + init_op_config_dirs(session_dir); + + opd_setup_signals(); + + err = setrlimit(RLIMIT_NOFILE, &rlim); + if (err) + perror("warning: could not set RLIMIT_NOFILE to 2048: "); + + opd_write_abi(); + + opd_ops = get_ops(); + + opd_ops->init(); + + opd_go_daemon(); + + /* clean up every 10 minutes */ + alarm(60 * 10); + + if (op_write_lock_file(op_lock_file)) { + fprintf(stderr, "oprofiled: could not create lock file %s\n", + op_lock_file); + exit(EXIT_FAILURE); + } + + opd_ops->start(); + + opd_ops->exit(); + + return 0; +} diff --git a/daemon/oprofiled.h b/daemon/oprofiled.h new file mode 100644 index 0000000..b319df1 --- /dev/null +++ b/daemon/oprofiled.h @@ -0,0 +1,69 @@ +/** + * @file daemon/oprofiled.h + * Initialisation and setup + * + * @remark Copyright 2002, 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * Modified by Aravind Menon for Xen + * These modifications are: + * Copyright (C) 2005 Hewlett-Packard Co. + */ + +#ifndef OPROFILED_H + +#include <signal.h> + +struct oprofiled_ops { + void (*init)(void); + void (*start)(void); + void (*exit)(void); +}; + + +/** + * opd_open_logfile - open the log file + * + * Open the logfile on stdout and stderr. This function + * assumes that 1 and 2 are the lowest close()d file + * descriptors. Failure to open on either descriptor is + * a fatal error. + */ +void opd_open_logfile(void); + + +/** + * is_image_ignored - check if we must ignore this image + * @param name the name to check + * + * Return true if the image should not be profiled + */ +int is_image_ignored(char const * name); + +/** return the int in the given oprofilefs file, error is fatal if !is_fatal */ +int opd_read_fs_int(char const * path, char const * name, int is_fatal); + + +/** global variable positioned by signal handler */ +extern sig_atomic_t signal_alarm; +extern sig_atomic_t signal_hup; +extern sig_atomic_t signal_term; +extern sig_atomic_t signal_child; +extern sig_atomic_t signal_usr1; +extern sig_atomic_t signal_usr2; + +extern unsigned int op_nr_counters; +extern int separate_lib; +extern int separate_kernel; +extern int separate_thread; +extern int separate_cpu; +extern int no_vmlinux; +extern char * vmlinux; +extern char * kernel_range; +extern int no_xen; +extern char * xenimage; +extern char * xen_range; + +#endif /* OPROFILED_H */ diff --git a/libabi/Android.mk b/libabi/Android.mk new file mode 100644 index 0000000..3213c1e --- /dev/null +++ b/libabi/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= op_abi.c + +LOCAL_MODULE := libabi + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + $(LOCAL_PATH)/../libdb \ + $(LOCAL_PATH)/../libutil \ + $(LOCAL_PATH)/../libop + +include $(BUILD_STATIC_LIBRARY) diff --git a/libabi/abi.cpp b/libabi/abi.cpp new file mode 100644 index 0000000..04a6b2d --- /dev/null +++ b/libabi/abi.cpp @@ -0,0 +1,75 @@ +/** + * @file abi.cpp + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Graydon Hoare + * @author John Levon + * @author Philippe Elie + */ + +#include "abi.h" +#include "op_abi.h" +#include "odb.h" +#include "op_sample_file.h" + +#include <iostream> +#include <cassert> + +using namespace std; + +typedef map<string, int> abi_map; +typedef abi_map::const_iterator abi_iter; + +abi_exception::abi_exception(string const d) : desc(d) {} + + +abi::abi() +{ + op_abi_entry const * entry = get_abi(); + for ( ; entry->name != 0; ++entry) + slots[entry->name] = entry->offset; + + slots["little_endian"] = op_little_endian(); +} + + +int abi::need(string const key) const throw (abi_exception) +{ + if (slots.find(key) != slots.end()) + return slots.find(key)->second; + else + throw abi_exception(string("missing ABI key: ") + key); +} + + +bool abi::operator==(abi const & other) const +{ + return slots == other.slots; +} + + +ostream & operator<<(ostream & o, abi const & abi) +{ + abi_iter i = abi.slots.begin(); + abi_iter e = abi.slots.end(); + + for (; i != e; ++i) + o << i->first << " " << i->second << endl; + + return o; +} + + +istream & operator>>(istream & i, abi & abi) +{ + string key; + int val; + abi.slots.clear(); + + while(i >> key >> val) + abi.slots[key] = val; + + return i; +} diff --git a/libabi/abi.h b/libabi/abi.h new file mode 100644 index 0000000..8b63e7f --- /dev/null +++ b/libabi/abi.h @@ -0,0 +1,42 @@ +/** + * @file abi.h + * + * Contains internal ABI management class + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Graydon Hoare + */ + +#ifndef OPROF_ABI_H +#define OPROF_ABI_H + +#include <string> +#include <map> +#include <iosfwd> + +struct abi_exception : std::exception { + std::string const desc; + + explicit abi_exception(std::string const d); + + ~abi_exception() throw() {} +}; + + +class abi { +public: + abi(); + + int need(std::string const key) const throw (abi_exception); + + bool operator==(abi const & other) const; + friend std::ostream & operator<<(std::ostream & o, abi const & abi); + friend std::istream & operator>>(std::istream & i, abi & abi); + +private: + std::map<std::string, int> slots; +}; + +#endif // OPROF_ABI_H diff --git a/libabi/op_abi.c b/libabi/op_abi.c new file mode 100644 index 0000000..283e3ff --- /dev/null +++ b/libabi/op_abi.c @@ -0,0 +1,94 @@ +/** + * @file op_abi.c + * This file contains a simple C interface to the ABI-describing functionality, + * the majority of which is implemented in C++. This is the file which is + * intended for use in files outside the /libabi directory. + * + * @remark Copyright 2002, 2005 OProfile authors + * @remark Read the file COPYING + * + * @author Graydon Hoare + * @author Philippe Elie + */ + +#include "op_abi.h" +#include "odb.h" +#include "op_sample_file.h" + +#include <stdio.h> +#include <stddef.h> +#include <assert.h> + +static struct op_abi_entry const abi_entries[] = { + { "sizeof_double", sizeof(double) }, + { "sizeof_time_t", sizeof(time_t) }, + { "sizeof_u8", sizeof(u8) }, + { "sizeof_u32", sizeof(u32) }, + { "sizeof_int", sizeof(int) }, + { "sizeof_unsigned_int", sizeof(unsigned int) }, + { "sizeof_odb_key_t", sizeof(odb_key_t) }, + { "sizeof_odb_index_t", sizeof(odb_index_t) }, + { "sizeof_odb_value_t", sizeof(odb_value_t) }, + { "sizeof_odb_node_nr_t", sizeof(odb_node_nr_t) }, + { "sizeof_odb_descr_t", sizeof(odb_descr_t) }, + { "sizeof_odb_node_t", sizeof(odb_node_t) }, + { "sizeof_struct_opd_header", sizeof(struct opd_header) }, + + { "offsetof_node_key", offsetof(odb_node_t, key) }, + { "offsetof_node_value", offsetof(odb_node_t, value) }, + { "offsetof_node_next", offsetof(odb_node_t, next) }, + + { "offsetof_descr_size", offsetof(odb_descr_t, size) }, + { "offsetof_descr_current_size", offsetof(odb_descr_t, current_size) }, + + { "offsetof_header_magic", offsetof(struct opd_header, magic) }, + { "offsetof_header_version", offsetof(struct opd_header, version) }, + { "offsetof_header_cpu_type", offsetof(struct opd_header, cpu_type) }, + { "offsetof_header_ctr_event", offsetof(struct opd_header, ctr_event) }, + { "offsetof_header_ctr_um", offsetof(struct opd_header, ctr_um) }, + { "offsetof_header_ctr_count", offsetof(struct opd_header, ctr_count) }, + { "offsetof_header_is_kernel", offsetof(struct opd_header, is_kernel) }, + { "offsetof_header_cpu_speed", offsetof(struct opd_header, cpu_speed) }, + { "offsetof_header_mtime", offsetof(struct opd_header, mtime) }, + { "offsetof_header_cg_to_is_kernel", offsetof(struct opd_header, cg_to_is_kernel), }, + { "offsetof_header_anon_start", offsetof(struct opd_header, anon_start) }, + { "offsetof_header_cg_to_anon_start", offsetof(struct opd_header, cg_to_anon_start) }, + + { NULL, 0 }, +}; + + +struct op_abi_entry const * get_abi(void) +{ + return abi_entries; +} + + +int op_little_endian(void) +{ + unsigned int probe = 0xff; + size_t sz = sizeof(unsigned int); + unsigned char * probe_byte = (unsigned char *)&probe; + + assert(probe_byte[0] == 0xff || probe_byte[sz - 1] == 0xff); + + return probe_byte[0] == 0xff; +} + + +int op_write_abi_to_file(char const * abi_file) +{ + FILE * fp; + struct op_abi_entry const * abi_entry; + + if ((fp = fopen(abi_file, "w")) == NULL) + return 0; + + for (abi_entry = get_abi() ; abi_entry->name != NULL; ++abi_entry) + fprintf(fp, "%s %u\n", abi_entry->name, abi_entry->offset); + fprintf(fp, "little_endian %d\n", op_little_endian()); + + fclose(fp); + + return 1; +} diff --git a/libabi/op_abi.h b/libabi/op_abi.h new file mode 100644 index 0000000..fbd337b --- /dev/null +++ b/libabi/op_abi.h @@ -0,0 +1,43 @@ +/** + * @file op_abi.h + * This file contains a simple C interface to the ABI-describing functionality, + * the majority of which is implemented in C++. this is the file which is + * intended for use in files outside the /libabi directory. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Graydon Hoare + * @author Philippe Elie + */ + +#ifndef OP_ABI_H +#define OP_ABI_H + +struct op_abi_entry { + char const * name; + /// offset or size of the named entry + int offset; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/// return array is terminated by a NULL entry in name field +struct op_abi_entry const * get_abi(void); + +/// return non zero if the abi is little endian +int op_little_endian(void); + +/** + * Write current abi to file. + * return 1 on success, 0 on failure + */ +int op_write_abi_to_file(char const * abi_file); + +#ifdef __cplusplus +} +#endif + +#endif // OP_ABI_H diff --git a/libabi/opimport.cpp b/libabi/opimport.cpp new file mode 100644 index 0000000..57f74a7 --- /dev/null +++ b/libabi/opimport.cpp @@ -0,0 +1,225 @@ +/** + * @file opimport.cpp + * Import sample files from other ABI + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Graydon Hoare + */ + +#include "abi.h" +#include "odb.h" +#include "popt_options.h" +#include "op_sample_file.h" + +#include <fstream> +#include <iostream> +#include <vector> +#include <cassert> +#include <cstring> +#include <cstdlib> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/mman.h> +#include <cstdlib> +#include <cstring> + +using namespace std; + +namespace { + string output_filename; + string abi_filename; + bool verbose; + bool force; +}; + + +popt::option options_array[] = { + popt::option(verbose, "verbose", 'V', "verbose output"), + popt::option(output_filename, "output", 'o', "output to file", "filename"), + popt::option(abi_filename, "abi", 'a', "abi description", "filename"), + popt::option(force, "force", 'f', "force conversion, even if identical") +}; + + +struct extractor { + + abi const & theabi; + + unsigned char const * begin; + unsigned char const * end; + bool little_endian; + + explicit + extractor(abi const & a, unsigned char const * src, size_t len) + : theabi(a), begin(src), end(src + len) { + little_endian = theabi.need(string("little_endian")) == 1; + if (verbose) { + cerr << "source byte order is: " + << string(little_endian ? "little" : "big") + << " endian" << endl; + } + } + + template <typename T> + void extract(T & targ, void const * src_, + char const * sz, char const * off); +}; + + +template <typename T> +void extractor::extract(T & targ, void const * src_, + char const * sz, char const * off) +{ + unsigned char const * src = static_cast<unsigned char const *>(src_) + + theabi.need(off); + size_t nbytes = theabi.need(sz); + + if (nbytes == 0) + return; + + assert(nbytes <= sizeof(T)); + assert(src >= begin); + assert(src + nbytes <= end); + + if (verbose) + cerr << hex << "get " << sz << " = " << nbytes + << " bytes @ " << off << " = " << (src - begin) + << " : "; + + targ = 0; + if (little_endian) + while(nbytes--) + targ = (targ << 8) | src[nbytes]; + else + for(size_t i = 0; i < nbytes; ++i) + targ = (targ << 8) | src[i]; + + if (verbose) + cerr << " = " << targ << endl; +} + + +void import_from_abi(abi const & abi, void const * srcv, + size_t len, odb_t * dest) throw (abi_exception) +{ + struct opd_header * head = + static_cast<opd_header *>(odb_get_data(dest)); + unsigned char const * src = static_cast<unsigned char const *>(srcv); + unsigned char const * const begin = src; + extractor ext(abi, src, len); + + memcpy(head->magic, src + abi.need("offsetof_header_magic"), 4); + + // begin extracting opd header + ext.extract(head->version, src, "sizeof_u32", "offsetof_header_version"); + ext.extract(head->cpu_type, src, "sizeof_u32", "offsetof_header_cpu_type"); + ext.extract(head->ctr_event, src, "sizeof_u32", "offsetof_header_ctr_event"); + ext.extract(head->ctr_um, src, "sizeof_u32", "offsetof_header_ctr_um"); + ext.extract(head->ctr_count, src, "sizeof_u32", "offsetof_header_ctr_count"); + ext.extract(head->is_kernel, src, "sizeof_u32", "offsetof_header_is_kernel"); + // "double" extraction is unlikely to work + head->cpu_speed = 0.0; + ext.extract(head->mtime, src, "sizeof_time_t", "offsetof_header_mtime"); + ext.extract(head->cg_to_is_kernel, src, "sizeof_u32", + "offsetof_header_cg_to_is_kernel"); + ext.extract(head->anon_start, src, "sizeof_u32", + "offsetof_header_anon_start"); + ext.extract(head->cg_to_anon_start, src, "sizeof_u32", + "offsetof_header_cg_to_anon_start"); + src += abi.need("sizeof_struct_opd_header"); + // done extracting opd header + + // begin extracting necessary parts of descr + odb_node_nr_t node_nr; + ext.extract(node_nr, src, "sizeof_odb_node_nr_t", "offsetof_descr_current_size"); + src += abi.need("sizeof_odb_descr_t"); + // done extracting descr + + // skip node zero, it is reserved and contains nothing usefull + src += abi.need("sizeof_odb_node_t"); + + // begin extracting nodes + unsigned int step = abi.need("sizeof_odb_node_t"); + if (verbose) + cerr << "extracting " << node_nr << " nodes of " << step << " bytes each " << endl; + + assert(src + (node_nr * step) <= begin + len); + + for (odb_node_nr_t i = 1 ; i < node_nr ; ++i, src += step) { + odb_key_t key; + odb_value_t val; + ext.extract(key, src, "sizeof_odb_key_t", "offsetof_node_key"); + ext.extract(val, src, "sizeof_odb_value_t", "offsetof_node_value"); + int rc = odb_add_node(dest, key, val); + if (rc != EXIT_SUCCESS) { + cerr << strerror(rc) << endl; + exit(EXIT_FAILURE); + } + } + // done extracting nodes +} + + +int main(int argc, char const ** argv) +{ + + vector<string> inputs; + popt::parse_options(argc, argv, inputs); + + if (inputs.size() != 1) { + cerr << "error: must specify exactly 1 input file" << endl; + exit(1); + } + + abi current_abi, input_abi; + + { + ifstream abi_file(abi_filename.c_str()); + if (!abi_file) { + cerr << "error: cannot open abi file " + << abi_filename << endl; + exit(1); + } + abi_file >> input_abi; + } + + if (!force && current_abi == input_abi) { + cerr << "input abi is identical to native. " + << "no conversion necessary." << endl; + exit(1); + } + + int in_fd; + struct stat statb; + void * in; + odb_t dest; + int rc; + + assert((in_fd = open(inputs[0].c_str(), O_RDONLY)) > 0); + assert(fstat(in_fd, &statb) == 0); + assert((in = mmap(0, statb.st_size, PROT_READ, + MAP_PRIVATE, in_fd, 0)) != (void *)-1); + + rc = odb_open(&dest, output_filename.c_str(), ODB_RDWR, + sizeof(struct opd_header)); + if (rc) { + cerr << "odb_open() fail:\n" + << strerror(rc) << endl; + exit(EXIT_FAILURE); + } + + try { + import_from_abi(input_abi, in, statb.st_size, &dest); + } catch (abi_exception & e) { + cerr << "caught abi exception: " << e.desc << endl; + } + + odb_close(&dest); + + assert(munmap(in, statb.st_size) == 0); +} diff --git a/libdb/Android.mk b/libdb/Android.mk new file mode 100644 index 0000000..1594fe8 --- /dev/null +++ b/libdb/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + db_debug.c \ + db_insert.c \ + db_manage.c \ + db_stat.c \ + db_travel.c + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + $(LOCAL_PATH)/../libutil + +LOCAL_MODULE := libdb + +include $(BUILD_STATIC_LIBRARY) diff --git a/libdb/db_debug.c b/libdb/db_debug.c new file mode 100644 index 0000000..0575570 --- /dev/null +++ b/libdb/db_debug.c @@ -0,0 +1,132 @@ +/** + * @file db_debug.c + * Debug routines for libdb + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "odb.h" + +static int check_circular_list(odb_data_t const * data) +{ + odb_node_nr_t pos; + int do_abort = 0; + unsigned char * bitmap = malloc(data->descr->current_size); + memset(bitmap, '\0', data->descr->current_size); + + for (pos = 0 ; pos < data->descr->size * BUCKET_FACTOR ; ++pos) { + + odb_index_t index = data->hash_base[pos]; + if (index && !do_abort) { + while (index) { + if (bitmap[index]) + do_abort = 1; + + bitmap[index] = 1; + index = data->node_base[index].next; + } + } + + if (do_abort) { + printf("circular list detected size: %d\n", + data->descr->current_size); + + memset(bitmap, '\0', data->descr->current_size); + + index = data->hash_base[pos]; + while (index) { + printf("%d ", index); + if (bitmap[index]) + exit(1); + + bitmap[index] = 1; + index = data->node_base[index].next; + } + } + + /* purely an optimization: intead of memset the map reset only + * the needed part: not my use to optimize test but here the + * test was so slow it was useless */ + index = data->hash_base[pos]; + while (index) { + bitmap[index] = 1; + index = data->node_base[index].next; + } + } + + free(bitmap); + + return do_abort; +} + +static int check_redundant_key(odb_data_t const * data, odb_key_t max) +{ + odb_node_nr_t pos; + + unsigned char * bitmap = malloc(max + 1); + memset(bitmap, '\0', max + 1); + + for (pos = 1 ; pos < data->descr->current_size ; ++pos) { + if (bitmap[data->node_base[pos].key]) { + printf("redundant key found %lld\n", + (unsigned long long)data->node_base[pos].key); + return 1; + } + bitmap[data->node_base[pos].key] = 1; + } + free(bitmap); + + return 0; +} + +int odb_check_hash(odb_t const * odb) +{ + odb_node_nr_t pos; + odb_node_nr_t nr_node = 0; + odb_node_nr_t nr_node_out_of_bound = 0; + int ret = 0; + odb_key_t max = 0; + odb_data_t * data = odb->data; + + for (pos = 0 ; pos < data->descr->size * BUCKET_FACTOR ; ++pos) { + odb_index_t index = data->hash_base[pos]; + while (index) { + if (index >= data->descr->current_size) { + nr_node_out_of_bound++; + break; + } + ++nr_node; + + if (data->node_base[index].key > max) + max = data->node_base[index].key; + + index = data->node_base[index].next; + } + } + + if (nr_node != data->descr->current_size - 1) { + printf("hash table walk found %d node expect %d node\n", + nr_node, data->descr->current_size - 1); + ret = 1; + } + + if (nr_node_out_of_bound) { + printf("out of bound node index: %d\n", nr_node_out_of_bound); + ret = 1; + } + + if (ret == 0) + ret = check_circular_list(data); + + if (ret == 0) + ret = check_redundant_key(data, max); + + return ret; +} diff --git a/libdb/db_insert.c b/libdb/db_insert.c new file mode 100644 index 0000000..018c294 --- /dev/null +++ b/libdb/db_insert.c @@ -0,0 +1,102 @@ +/** + * @file db_insert.c + * Inserting a key-value pair into a DB + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "odb.h" + + +static inline int add_node(odb_data_t * data, odb_key_t key, odb_value_t value) +{ + odb_index_t new_node; + odb_node_t * node; + odb_index_t index; + + /* no locking is necessary: iteration interface retrieve data through + * the node_base array, we doesn't increase current_size now but it's + * done by odb_commit_reservation() so the new slot is visible only + * after the increment + */ + if (data->descr->current_size >= data->descr->size) { + if (odb_grow_hashtable(data)) + return EINVAL; + } + new_node = data->descr->current_size; + + node = &data->node_base[new_node]; + node->value = value; + node->key = key; + + index = odb_do_hash(data, key); + node->next = data->hash_base[index]; + data->hash_base[index] = new_node; + + /* FIXME: we need wrmb() here */ + odb_commit_reservation(data); + + return 0; +} + +int odb_update_node(odb_t * odb, odb_key_t key) +{ + odb_index_t index; + odb_node_t * node; + odb_data_t * data; + + data = odb->data; + index = data->hash_base[odb_do_hash(data, key)]; + while (index) { + node = &data->node_base[index]; + if (node->key == key) { + if (node->value + 1 != 0) { + node->value += 1; + } else { + /* post profile tools must handle overflow */ + /* FIXME: the tricky way will be just to add + * a goto to jump right before the return + * add_node(), in this way we no longer can + * overflow. It'll work because new node are + * linked at the start of the node list for + * this bucket so this loop will see first a + * non overflowed node if one exist. When we + * grow the hashtable the most recently + * allocated node for this key will be setup + * last, so again it'll be linked at start of + * the list. pp tools looke like ok with this + * change. + * + * This change doesn't involve any file format + * change but perhaps it's a bit hacky to do + * this w/o bumping the sample file format + * version. The drawback of this is the added + * node are additive not multiplicative. + * (multiplicative as if we add more bits to + * store a value) + */ + } + return 0; + } + + index = node->next; + } + + return add_node(data, key, 1); +} + + +int odb_add_node(odb_t * odb, odb_key_t key, odb_value_t value) +{ + return add_node(odb->data, key, value); +} diff --git a/libdb/db_manage.c b/libdb/db_manage.c new file mode 100644 index 0000000..d8a6fcb --- /dev/null +++ b/libdb/db_manage.c @@ -0,0 +1,311 @@ +/** + * @file db_manage.c + * Management of a DB file + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#ifndef ANDROID +#include <sys/fcntl.h> +#else +#include <fcntl.h> +#endif +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> + +#include "odb.h" +#include "op_string.h" +#include "op_libiberty.h" + + +static __inline odb_descr_t * odb_to_descr(odb_data_t * data) +{ + return (odb_descr_t *)(((char*)data->base_memory) + data->sizeof_header); +} + + +static __inline odb_node_t * odb_to_node_base(odb_data_t * data) +{ + return (odb_node_t *)(((char *)data->base_memory) + data->offset_node); +} + + +static __inline odb_index_t * odb_to_hash_base(odb_data_t * data) +{ + return (odb_index_t *)(((char *)data->base_memory) + + data->offset_node + + (data->descr->size * sizeof(odb_node_t))); +} + + +/** + * return the number of bytes used by hash table, node table and header. + */ +static unsigned int tables_size(odb_data_t const * data, odb_node_nr_t node_nr) +{ + size_t size; + + size = node_nr * (sizeof(odb_index_t) * BUCKET_FACTOR); + size += node_nr * sizeof(odb_node_t); + size += data->offset_node; + + return size; +} + + +int odb_grow_hashtable(odb_data_t * data) +{ + unsigned int old_file_size; + unsigned int new_file_size; + unsigned int pos; + void * new_map; + + old_file_size = tables_size(data, data->descr->size); + new_file_size = tables_size(data, data->descr->size * 2); + + if (ftruncate(data->fd, new_file_size)) + return 1; + + new_map = mremap(data->base_memory, + old_file_size, new_file_size, MREMAP_MAYMOVE); + + if (new_map == MAP_FAILED) + return 1; + + data->base_memory = new_map; + data->descr = odb_to_descr(data); + data->descr->size *= 2; + data->node_base = odb_to_node_base(data); + data->hash_base = odb_to_hash_base(data); + data->hash_mask = (data->descr->size * BUCKET_FACTOR) - 1; + + /* rebuild the hash table, node zero is never used. This works + * because layout of file is node table then hash table, + * sizeof(node) > sizeof(bucket) and when we grow table we + * double size ==> old hash table and new hash table can't + * overlap so on the new hash table is entirely in the new + * memory area (the grown part) and we know the new hash + * hash table is zeroed. That's why we don't need to zero init + * the new table */ + /* OK: the above is not exact + * if BUCKET_FACTOR < sizeof(bd_node_t) / sizeof(bd_node_nr_t) + * all things are fine and we don't need to init the hash + * table because in this case the new hash table is completely + * inside the new growed part. Avoiding to touch this memory is + * useful. + */ +#if 0 + for (pos = 0 ; pos < data->descr->size*BUCKET_FACTOR ; ++pos) + data->hash_base[pos] = 0; +#endif + + for (pos = 1; pos < data->descr->current_size; ++pos) { + odb_node_t * node = &data->node_base[pos]; + size_t index = odb_do_hash(data, node->key); + node->next = data->hash_base[index]; + data->hash_base[index] = pos; + } + + return 0; +} + + +void odb_init(odb_t * odb) +{ + odb->data = NULL; +} + + +/* the default number of page, calculated to fit in 4096 bytes */ +#define DEFAULT_NODE_NR(offset_node) 128 +#define FILES_HASH_SIZE 512 + +static struct list_head files_hash[FILES_HASH_SIZE]; + + +static void init_hash() +{ + size_t i; + for (i = 0; i < FILES_HASH_SIZE; ++i) + list_init(&files_hash[i]); +} + + +static odb_data_t * +find_samples_data(size_t hash, char const * filename) +{ + struct list_head * pos; + + /* FIXME: maybe an initial init routine ? */ + if (files_hash[0].next == NULL) { + init_hash(); + return NULL; + } + + list_for_each(pos, &files_hash[hash]) { + odb_data_t * entry = list_entry(pos, odb_data_t, list); + if (strcmp(entry->filename, filename) == 0) + return entry; + } + + return NULL; +} + + +int odb_open(odb_t * odb, char const * filename, enum odb_rw rw, + size_t sizeof_header) +{ + struct stat stat_buf; + odb_node_nr_t nr_node; + odb_data_t * data; + size_t hash; + int err = 0; + + int flags = (rw == ODB_RDWR) ? (O_CREAT | O_RDWR) : O_RDONLY; + int mmflags = (rw == ODB_RDWR) ? (PROT_READ | PROT_WRITE) : PROT_READ; + + hash = op_hash_string(filename) % FILES_HASH_SIZE; + data = find_samples_data(hash, filename); + if (data) { + odb->data = data; + data->ref_count++; + return 0; + } + + data = xmalloc(sizeof(odb_data_t)); + memset(data, '\0', sizeof(odb_data_t)); + list_init(&data->list); + data->offset_node = sizeof_header + sizeof(odb_descr_t); + data->sizeof_header = sizeof_header; + data->ref_count = 1; + data->filename = xstrdup(filename); + + data->fd = open(filename, flags, 0644); + if (data->fd < 0) { + err = errno; + goto out; + } + + if (fstat(data->fd, &stat_buf)) { + err = errno; + goto fail; + } + + if (stat_buf.st_size == 0) { + size_t file_size; + + if (rw == ODB_RDONLY) { + err = EIO; + goto fail; + } + + nr_node = DEFAULT_NODE_NR(data->offset_node); + + file_size = tables_size(data, nr_node); + if (ftruncate(data->fd, file_size)) { + err = errno; + goto fail; + } + } else { + /* Calculate nr node allowing a sanity check later */ + nr_node = (stat_buf.st_size - data->offset_node) / + ((sizeof(odb_index_t) * BUCKET_FACTOR) + sizeof(odb_node_t)); + } + + data->base_memory = mmap(0, tables_size(data, nr_node), mmflags, + MAP_SHARED, data->fd, 0); + + if (data->base_memory == MAP_FAILED) { + err = errno; + goto fail; + } + + data->descr = odb_to_descr(data); + + if (stat_buf.st_size == 0) { + data->descr->size = nr_node; + /* page zero is not used */ + data->descr->current_size = 1; + } else { + /* file already exist, sanity check nr node */ + if (nr_node != data->descr->size) { + err = EINVAL; + goto fail_unmap; + } + } + + data->hash_base = odb_to_hash_base(data); + data->node_base = odb_to_node_base(data); + data->hash_mask = (data->descr->size * BUCKET_FACTOR) - 1; + + list_add(&data->list, &files_hash[hash]); + odb->data = data; +out: + return err; +fail_unmap: + munmap(data->base_memory, tables_size(data, nr_node)); +fail: + close(data->fd); + free(data->filename); + free(data); + odb->data = NULL; + goto out; +} + + +void odb_close(odb_t * odb) +{ + odb_data_t * data = odb->data; + + if (data) { + data->ref_count--; + if (data->ref_count == 0) { + size_t size = tables_size(data, data->descr->size); + list_del(&data->list); + munmap(data->base_memory, size); + if (data->fd >= 0) + close(data->fd); + free(data->filename); + free(data); + odb->data = NULL; + } + } +} + + +int odb_open_count(odb_t const * odb) +{ + if (!odb->data) + return 0; + return odb->data->ref_count; +} + + +void * odb_get_data(odb_t * odb) +{ + return odb->data->base_memory; +} + + +void odb_sync(odb_t const * odb) +{ + odb_data_t * data = odb->data; + size_t size; + + if (!data) + return; + + size = tables_size(data, data->descr->size); + msync(data->base_memory, size, MS_ASYNC); +} diff --git a/libdb/db_stat.c b/libdb/db_stat.c new file mode 100644 index 0000000..6d29e9a --- /dev/null +++ b/libdb/db_stat.c @@ -0,0 +1,88 @@ +/** + * @file db_stat.c + * Statistics routines for libdb + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#include <stdlib.h> +#include <stdio.h> + +#include "odb.h" +#include "op_types.h" + +/// hold various statistics data for a db file +struct odb_hash_stat_t { + odb_node_nr_t node_nr; /**< allocated node number */ + odb_node_nr_t used_node_nr; /**< in use node number */ + count_type total_count; /**< cumulated samples count */ + odb_index_t hash_table_size; /**< hash table entry number */ + odb_node_nr_t max_list_length; /**< worst case */ + double average_list_length; /**< average case */ + /* do we need variance ? */ +}; + +odb_hash_stat_t * odb_hash_stat(odb_t const * odb) +{ + size_t max_length = 0; + double total_length = 0.0; + size_t nr_non_empty_list = 0; + size_t pos; + odb_data_t * data = odb->data; + + odb_hash_stat_t * result = calloc(1, sizeof(odb_hash_stat_t)); + if (!result) { + fprintf(stderr, "not enough memory\n"); + exit(EXIT_FAILURE); + } + + result->node_nr = data->descr->size; + result->used_node_nr = data->descr->current_size; + result->hash_table_size = data->descr->size * BUCKET_FACTOR; + + /* FIXME: I'm dubious if this do right statistics for hash table + * efficiency check */ + + for (pos = 0 ; pos < result->hash_table_size ; ++pos) { + size_t cur_length = 0; + size_t index = data->hash_base[pos]; + while (index) { + result->total_count += data->node_base[index].value; + index = data->node_base[index].next; + ++cur_length; + } + + if (cur_length > max_length) + max_length = cur_length; + + if (cur_length) { + total_length += cur_length; + ++nr_non_empty_list; + } + } + + result->max_list_length = max_length; + result->average_list_length = total_length / nr_non_empty_list; + + return result; +} + + +void odb_hash_display_stat(odb_hash_stat_t const * stat) +{ + printf("total node number: %d\n", stat->node_nr); + printf("total used node: %d\n", stat->used_node_nr); + printf("total count: %llu\n", stat->total_count); + printf("hash table size: %d\n", stat->hash_table_size); + printf("greater list length: %d\n", stat->max_list_length); + printf("average non empty list length: %2.4f\n", stat->average_list_length); +} + + +void odb_hash_free_stat(odb_hash_stat_t * stat) +{ + free(stat); +} diff --git a/libdb/db_travel.c b/libdb/db_travel.c new file mode 100644 index 0000000..3ed467b --- /dev/null +++ b/libdb/db_travel.c @@ -0,0 +1,18 @@ +/** + * @file db_travel.c + * Inspection of a DB + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#include "odb.h" + +odb_node_t * odb_get_iterator(odb_t const * odb, odb_node_nr_t * nr) +{ + /* node zero is unused */ + *nr = odb->data->descr->current_size - 1; + return odb->data->node_base + 1; +} diff --git a/libdb/odb.h b/libdb/odb.h new file mode 100644 index 0000000..c190b57 --- /dev/null +++ b/libdb/odb.h @@ -0,0 +1,223 @@ +/** + * @file odb.h + * This file contains various definitions and interface for management + * of in-memory, through mmaped file, growable hash table, that stores + * sample files. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#ifndef ODB_HASH_H +#define ODB_HASH_H + +#include <stddef.h> +#include <stdint.h> + +#include "op_list.h" + +/** the type of key. 64-bit because CG needs 32-bit pair {from,to} */ +typedef uint64_t odb_key_t; +/** the type of an information in the database */ +typedef unsigned int odb_value_t; +/** the type of index (node number), list are implemented through index */ +typedef unsigned int odb_index_t; +/** the type store node number */ +typedef odb_index_t odb_node_nr_t; +/** store the hash mask, hash table size are always power of two */ +typedef odb_index_t odb_hash_mask_t; + +/* there is (bucket factor * nr node) entry in hash table, this can seem + * excessive but hash coding eip don't give a good distributions and our + * goal is to get a O(1) amortized insert time. bucket factor must be a + * power of two. FIXME: see big comment in odb_hash_add_node, you must + * re-enable zeroing hash table if BUCKET_FACTOR > 2 (roughly exact, you + * want to read the comment in odb_hash_add_node() if you tune this define) + */ +#define BUCKET_FACTOR 1 + +/** a db hash node */ +typedef struct { + odb_key_t key; /**< eip */ + odb_value_t value; /**< samples count */ + odb_index_t next; /**< next entry for this bucket */ +} odb_node_t; + +/** the minimal information which must be stored in the file to reload + * properly the data base, following this header is the node array then + * the hash table (when growing we avoid to copy node array) + */ +typedef struct { + odb_node_nr_t size; /**< in node nr (power of two) */ + odb_node_nr_t current_size; /**< nr used node + 1, node 0 unused */ + int padding[6]; /**< for padding and future use */ +} odb_descr_t; + +/** a "database". this is an in memory only description. + * + * We allow to manage a database inside a mapped file with an "header" of + * unknown size so odb_open get a parameter to specify the size of this header. + * A typical use is: + * + * struct header { int etc; ... }; + * odb_open(&hash, filename, ODB_RW, sizeof(header)); + * so on this library have no dependency on the header type. + * + * the internal memory layout from base_memory is: + * the unknown header (sizeof_header) + * odb_descr_t + * the node array: (descr->size * sizeof(odb_node_t) entries + * the hash table: array of odb_index_t indexing the node array + * (descr->size * BUCKET_FACTOR) entries + */ +typedef struct odb_data { + odb_node_t * node_base; /**< base memory area of the page */ + odb_index_t * hash_base; /**< base memory of hash table */ + odb_descr_t * descr; /**< the current state of database */ + odb_hash_mask_t hash_mask; /**< == descr->size - 1 */ + unsigned int sizeof_header; /**< from base_memory to odb header */ + unsigned int offset_node; /**< from base_memory to node array */ + void * base_memory; /**< base memory of the maped memory */ + int fd; /**< mmaped memory file descriptor */ + char * filename; /**< full path name of sample file */ + int ref_count; /**< reference count */ + struct list_head list; /**< hash bucket list */ +} odb_data_t; + +typedef struct { + odb_data_t * data; +} odb_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/* db_manage.c */ + +/** how to open the DB file */ +enum odb_rw { + ODB_RDONLY = 0, /**< open for read only */ + ODB_RDWR = 1 /**< open for read and/or write */ +}; + +/** + * odb_init - initialize a DB file + * @param odb the DB file to init + */ +void odb_init(odb_t * odb); + +/** + * odb_open - open a DB file + * @param odb the data base object to setup + * @param filename the filename where go the maped memory + * @param rw \enum ODB_RW if opening for writing, else \enum ODB_RDONLY + * @param sizeof_header size of the file header if any + * + * The sizeof_header parameter allows the data file to have a header + * at the start of the file which is skipped. + * odb_open() always preallocate a few number of pages. + * returns 0 on success, errno on failure + */ +int odb_open(odb_t * odb, char const * filename, + enum odb_rw rw, size_t sizeof_header); + +/** Close the given ODB file */ +void odb_close(odb_t * odb); + +/** return the number of times this sample file is open */ +int odb_open_count(odb_t const * odb); + +/** return the start of the mapped data */ +void * odb_get_data(odb_t * odb); + +/** issue a msync on the used size of the mmaped file */ +void odb_sync(odb_t const * odb); + +/** + * grow the hashtable in such way current_size is the index of the first free + * node. Take care all node pointer can be invalidated by this call. + * + * Node allocation is done in a two step way 1st) ensure a free node exist + * eventually, caller can setup it, 2nd) commit the node allocation with + * odb_commit_reservation(). + * This is done in this way to ensure node setup is visible from another + * process like pp tools in an atomic way. + * + * returns 0 on success, non zero on failure in this case this function do + * nothing and errno is set by the first libc call failure allowing to retry + * after cleanup some program resource. + */ +int odb_grow_hashtable(odb_data_t * data); +/** + * commit a previously successfull node reservation. This can't fail. + */ +static __inline void odb_commit_reservation(odb_data_t * data) +{ + ++data->descr->current_size; +} + +/** "immpossible" node number to indicate an error from odb_hash_add_node() */ +#define ODB_NODE_NR_INVALID ((odb_node_nr_t)-1) + +/* db_debug.c */ +/** check that the hash is well built */ +int odb_check_hash(odb_t const * odb); + +/* db_stat.c */ +typedef struct odb_hash_stat_t odb_hash_stat_t; +odb_hash_stat_t * odb_hash_stat(odb_t const * odb); +void odb_hash_display_stat(odb_hash_stat_t const * stats); +void odb_hash_free_stat(odb_hash_stat_t * stats); + +/* db_insert.c */ +/** update info at key by incrementing its associated value by one, + * if the key does not exist a new node is created and the value associated + * is set to one. + * + * returns EXIT_SUCCESS on success, EXIT_FAILURE on failure + */ +int odb_update_node(odb_t * odb, odb_key_t key); + +/** Add a new node w/o regarding if a node with the same key already exists + * + * returns EXIT_SUCCESS on success, EXIT_FAILURE on failure + */ +int odb_add_node(odb_t * odb, odb_key_t key, odb_value_t value); + +/* db_travel.c */ +/** + * return a base pointer to the node array and number of node in this array + * caller then will iterate through: + * + * odb_node_nr_t node_nr, pos; + * odb_node_t * node = odb_get_iterator(odb, &node_nr); + * for ( pos = 0 ; pos < node_nr ; ++pos) + * // do something + * + * note than caller does not need to filter nil key as it's a valid key, + * The returned range is all valid (i.e. should never contain zero value). + */ +odb_node_t * odb_get_iterator(odb_t const * odb, odb_node_nr_t * nr); + +static __inline unsigned int +odb_do_hash(odb_data_t const * data, odb_key_t value) +{ + /* FIXME: better hash for eip value, needs to instrument code + * and do a lot of tests ... */ + /* trying to combine high order bits his a no-op: inside a binary image + * high order bits don't vary a lot, hash table start with 7 bits mask + * so this hash coding use bits 0-7, 8-15. Hash table is stored in + * files avoiding to rebuilding them at profiling re-start so + * on changing do_hash() change the file format! + */ + uint32_t temp = (value >> 32) ^ value; + return ((temp << 0) ^ (temp >> 8)) & data->hash_mask; +} + +#ifdef __cplusplus +} +#endif + +#endif /* !ODB_H */ diff --git a/libop/Android.mk b/libop/Android.mk new file mode 100644 index 0000000..8fbd1e6 --- /dev/null +++ b/libop/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + op_alloc_counter.c \ + op_config.c \ + op_cpu_type.c \ + op_events.c \ + op_get_interface.c \ + op_mangle.c \ + op_parse_event.c + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + $(LOCAL_PATH)/../libutil + +LOCAL_MODULE := libop + +include $(BUILD_STATIC_LIBRARY) diff --git a/libop/op_alloc_counter.c b/libop/op_alloc_counter.c new file mode 100644 index 0000000..353100a --- /dev/null +++ b/libop/op_alloc_counter.c @@ -0,0 +1,213 @@ +/** + * @file op_alloc_counter.c + * hardware counter allocation + * + * You can have silliness here. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <stdlib.h> +#include <ctype.h> +#include <dirent.h> + +#include "op_events.h" +#include "op_libiberty.h" + + +typedef struct counter_arc_head { + /** the head of allowed counter for this event */ + struct list_head next; +} counter_arc_head; + + +typedef struct counter_arc { + /** counter nr */ + int counter; + /** the next counter allowed for this event */ + struct list_head next; +} counter_arc; + + +/** + * @param pev an array of event + * @param nr_events number of entry in pev + * + * build an array of counter list allowed for each events + * counter_arc_head[i] is the list of allowed counter for pev[i] events + * The returned pointer is an array of nr_events entry + */ +static counter_arc_head * +build_counter_arc(struct op_event const * pev[], int nr_events) +{ + counter_arc_head * ctr_arc; + int i; + + ctr_arc = xmalloc(nr_events * sizeof(*ctr_arc)); + + for (i = 0; i < nr_events; ++i) { + int j; + u32 mask = pev[i]->counter_mask; + + list_init(&ctr_arc[i].next); + for (j = 0; mask; ++j) { + if (mask & (1 << j)) { + counter_arc * arc = + xmalloc(sizeof(counter_arc)); + arc->counter = j; + /* we are looping by increasing counter number, + * allocation use a left to right tree walking + * so we add at end to ensure counter will + * be allocated by increasing number: it's not + * required but a bit less surprising when + * debugging code + */ + list_add_tail(&arc->next, &ctr_arc[i].next); + mask &= ~(1 << j); + } + } + } + + return ctr_arc; +} + + +/** + * @param ctr_arc the array to deallocate + * @param nr_events number of entry in array + * + * deallocate all previously allocated resource by build_counter_arc() + */ +static void delete_counter_arc(counter_arc_head * ctr_arc, int nr_events) +{ + int i; + for (i = 0; i < nr_events; ++i) { + struct list_head * pos, * pos2; + list_for_each_safe(pos, pos2, &ctr_arc[i].next) { + counter_arc * arc = list_entry(pos, counter_arc, next); + list_del(&arc->next); + free(arc); + } + } + free(ctr_arc); +} + + +/** + * @param ctr_arc tree description, ctr_arc[i] is the i-th level of tree. + * @param max_depth number of entry in array ctr_arc == depth of tree + * @param depth current level we are exploring + * @param allocated_mask current counter already allocated mask + * @param counter_map array of counter number mapping, returned results go + * here + * + * return non zero on succees, in this case counter_map is set to the counter + * mapping number. + * + * Solution is searched through a simple backtracking exploring recursively all + * possible solution until one is found, prunning is done in O(1) by tracking + * a bitmask of already allocated counter. Walking through node is done in + * preorder left to right. + * + * Possible improvment if neccessary: partition counters in class of counter, + * two counter belong to the same class if they allow exactly the same set of + * event. Now using a variant of the backtrack algo can works on class of + * counter rather on counter (this is not an improvment if each counter goes + * in it's own class) + */ +static int +allocate_counter(counter_arc_head const * ctr_arc, int max_depth, int depth, + u32 allocated_mask, size_t * counter_map) +{ + struct list_head * pos; + + if (depth == max_depth) + return 1; + + list_for_each(pos, &ctr_arc[depth].next) { + counter_arc const * arc = list_entry(pos, counter_arc, next); + + if (allocated_mask & (1 << arc->counter)) + continue; + + counter_map[depth] = arc->counter; + + if (allocate_counter(ctr_arc, max_depth, depth + 1, + allocated_mask | (1 << arc->counter), + counter_map)) + return 1; + } + + return 0; +} + +/* determine which directories are counter directories + */ +static int perfcounterdir(const struct dirent * entry) +{ + return (isdigit(entry->d_name[0])); +} + + +/** + * @param mask pointer where to place bit mask of unavailable counters + * + * return >= 0 number of counters that are available + * < 0 could not determine number of counters + * + */ +static int op_get_counter_mask(u32 * mask) +{ + struct dirent **counterlist; + int count, i; + /* assume nothing is available */ + u32 available=0; + + count = scandir("/dev/oprofile", &counterlist, perfcounterdir, alphasort); + if (count < 0) + /* unable to determine bit mask */ + return -1; + /* convert to bit map (0 where counter exists) */ + for (i=0; i<count; ++i) { + available |= 1 << atoi(counterlist[i]->d_name); + free(counterlist[i]); + } + *mask=~available; + free(counterlist); + return count; +} + +size_t * map_event_to_counter(struct op_event const * pev[], int nr_events, + op_cpu cpu_type) +{ + counter_arc_head * ctr_arc; + size_t * counter_map; + int nr_counters; + u32 unavailable_counters = 0; + + nr_counters = op_get_counter_mask(&unavailable_counters); + /* no counters then probably perfmon managing perfmon hw */ + if (nr_counters <= 0) { + nr_counters = op_get_nr_counters(cpu_type); + unavailable_counters = (~0) << nr_counters; + } + if (nr_counters < nr_events) + return 0; + + ctr_arc = build_counter_arc(pev, nr_events); + + counter_map = xmalloc(nr_counters * sizeof(size_t)); + + if (!allocate_counter(ctr_arc, nr_events, 0, unavailable_counters, + counter_map)) { + free(counter_map); + counter_map = 0; + } + + delete_counter_arc(ctr_arc, nr_events); + return counter_map; +} diff --git a/libop/op_alloc_counter.h b/libop/op_alloc_counter.h new file mode 100644 index 0000000..40b4ebf --- /dev/null +++ b/libop/op_alloc_counter.h @@ -0,0 +1,43 @@ +/** + * @file op_alloc_counter.h + * hardware counter allocation + * + * You can have silliness here. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_ALLOC_COUNTER_H +#define OP_ALLOC_COUNTER_H + +#include <stddef.h> + +#include "op_cpu_type.h" + +struct op_event; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @param pev array of selected event we want to bind to counter + * @param nr_events size of pev array + * @param cpu_type cpu type + * + * Try to calculate a binding between passed event in pev and counter number. + * The binding is returned in a size_t * where returned ptr[i] is the counter + * number bound to pev[i] + */ +size_t * map_event_to_counter(struct op_event const * pev[], int nr_events, + op_cpu cpu_type); + +#ifdef __cplusplus +} +#endif + +#endif /* !OP_ALLOC_COUNTER_H */ diff --git a/libop/op_config.c b/libop/op_config.c new file mode 100644 index 0000000..837242b --- /dev/null +++ b/libop/op_config.c @@ -0,0 +1,77 @@ +/** + * @file op_config.c + * Oprofile configuration parameters. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Nathan Tallent + * @Modifications Daniel Hansel + */ + +#include "op_config.h" +#include "op_config_24.h" + +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +/* paths in op_config.h */ +char op_session_dir[PATH_MAX]; +char op_samples_dir[PATH_MAX]; +char op_samples_current_dir[PATH_MAX]; +char op_lock_file[PATH_MAX]; +char op_log_file[PATH_MAX]; +char op_pipe_file[PATH_MAX]; +char op_dump_status[PATH_MAX]; + +/* paths in op_config_24.h */ +char op_device[PATH_MAX]; +char op_note_device[PATH_MAX]; +char op_hash_device[PATH_MAX]; + +void +init_op_config_dirs(char const * session_dir) +{ + int session_dir_len; + + assert(session_dir); + session_dir_len = strlen(session_dir); + + if (session_dir_len + strlen("/samples/oprofiled.log") > PATH_MAX) { + fprintf(stderr, "Session_dir string \"%s\" is too large.\n", + session_dir); + exit(EXIT_FAILURE); + } + + strcpy(op_session_dir, session_dir); + + strcpy(op_samples_dir, op_session_dir); + strcat(op_samples_dir, "/samples/"); + + strcpy(op_samples_current_dir, op_samples_dir); + strcat(op_samples_current_dir, "/current/"); + + strcpy(op_lock_file, op_session_dir); + strcat(op_lock_file, "/lock"); + + strcpy(op_pipe_file, op_session_dir); + strcat(op_pipe_file, "/opd_pipe"); + + strcpy(op_log_file, op_samples_dir); + strcat(op_log_file, "oprofiled.log"); + + strcpy(op_dump_status, op_session_dir); + strcat(op_dump_status, "/complete_dump"); + + strcpy(op_device, op_session_dir); + strcat(op_device, "/opdev"); + + strcpy(op_note_device, op_session_dir); + strcat(op_note_device, "/opnotedev"); + + strcpy(op_hash_device, op_session_dir); + strcat(op_hash_device, "/ophashmapdev"); +} diff --git a/libop/op_config.h b/libop/op_config.h new file mode 100644 index 0000000..b384497 --- /dev/null +++ b/libop/op_config.h @@ -0,0 +1,58 @@ +/** + * @file op_config.h + * + * Parameters a user may want to change. See + * also op_config_24.h + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + * @Modifications Daniel Hansel + */ + +#ifndef OP_CONFIG_H +#define OP_CONFIG_H + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * must be called to initialize the paths below. + * @param session_dir the non-NULL value of the base session directory + */ +void init_op_config_dirs(char const * session_dir); + +/* + * various paths, corresponding to opcontrol, that should be + * initialized by init_op_config_dirs() above. + */ +extern char op_session_dir[]; +extern char op_samples_dir[]; +extern char op_samples_current_dir[]; +extern char op_lock_file[]; +extern char op_log_file[]; +extern char op_pipe_file[]; +extern char op_dump_status[]; + +#define OP_DRIVER_BASE "/dev/oprofile" +#define OP_DATA_DIR "/data/oprofile" + +/* Global directory that stores debug files */ +#ifndef DEBUGDIR +#define DEBUGDIR "/usr/lib/debug" +#endif + +#define OPD_MAGIC "DAE\n" +#define OPD_VERSION 0x11 + +#define OP_MIN_CPU_BUF_SIZE 2048 +#define OP_MAX_CPU_BUF_SIZE 131072 + +#if defined(__cplusplus) +} +#endif + +#endif /* OP_CONFIG_H */ diff --git a/libop/op_config_24.h b/libop/op_config_24.h new file mode 100644 index 0000000..1786fae --- /dev/null +++ b/libop/op_config_24.h @@ -0,0 +1,79 @@ +/** + * @file op_config_24.h + * + * Parameters a user may want to change + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_CONFIG_24_H +#define OP_CONFIG_24_H + +#define OP_MOUNT "/proc/sys/dev/oprofile/" + +extern char op_device[]; +extern char op_note_device[]; +extern char op_hash_device[]; + +/*@{\name module default/min/max settings */ + +/** 65536 * sizeof(op_sample) */ +#define OP_DEFAULT_BUF_SIZE 65536 +/** + * we don't try to wake-up daemon until it remains more than this free entry + * in eviction buffer + */ +#define OP_PRE_WATERMARK(buffer_size) \ + (((buffer_size) / 8) < OP_MIN_PRE_WATERMARK \ + ? OP_MIN_PRE_WATERMARK \ + : (buffer_size) / 8) +/** minimal buffer water mark before we try to wakeup daemon */ +#define OP_MIN_PRE_WATERMARK 8192 +/** maximum number of entry in samples eviction buffer */ +#define OP_MAX_BUF_SIZE 1048576 +/** minimum number of entry in samples eviction buffer */ +#define OP_MIN_BUF_SIZE (32768 + OP_PRE_WATERMARK(32768)) + +/** 16384 * sizeof(op_note) = 273680 bytes default */ +#define OP_DEFAULT_NOTE_SIZE 16384 +/** + * we don't try to wake-up daemon until it remains more than this free entry + * in note buffer + */ +#define OP_PRE_NOTE_WATERMARK(note_size) \ + (((note_size) / 32) < OP_MIN_NOTE_PRE_WATERMARK \ + ? OP_MIN_NOTE_PRE_WATERMARK \ + : (note_size) / 32) +/** minimal note buffer water mark before we try to wakeup daemon */ +#define OP_MIN_NOTE_PRE_WATERMARK 512 +/** maximum number of entry in note buffer */ +#define OP_MAX_NOTE_TABLE_SIZE 1048576 +/** minimum number of entry in note buffer */ +#define OP_MIN_NOTE_TABLE_SIZE (1024 + OP_PRE_NOTE_WATERMARK(1024)) + +/** maximum sampling rate when using RTC */ +#define OP_MAX_RTC_COUNT 4096 +/** minimum sampling rate when using RTC */ +#define OP_MIN_RTC_COUNT 2 + +/*@}*/ + +/** + * nr entries in hash map. This is the maximum number of name components + * allowed. Must be a prime number + */ +#define OP_HASH_MAP_NR 4093 + +/** size of string pool in bytes */ +#define POOL_SIZE 65536 + +#ifndef NR_CPUS +/** maximum number of cpus present in the box */ +#define NR_CPUS 32 +#endif + +#endif /* OP_CONFIG_24_H */ diff --git a/libop/op_cpu_type.c b/libop/op_cpu_type.c new file mode 100644 index 0000000..b9d13de --- /dev/null +++ b/libop/op_cpu_type.c @@ -0,0 +1,158 @@ +/** + * @file op_cpu_type.c + * CPU type determination + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "op_cpu_type.h" + +struct cpu_descr { + char const * pretty; + char const * name; + op_cpu cpu; + unsigned int nr_counters; +}; + +static struct cpu_descr const cpu_descrs[MAX_CPU_TYPE] = { + { "Pentium Pro", "i386/ppro", CPU_PPRO, 2 }, + { "PII", "i386/pii", CPU_PII, 2 }, + { "PIII", "i386/piii", CPU_PIII, 2 }, + { "Athlon", "i386/athlon", CPU_ATHLON, 4 }, + { "CPU with timer interrupt", "timer", CPU_TIMER_INT, 1 }, + { "CPU with RTC device", "rtc", CPU_RTC, 1 }, + { "P4 / Xeon", "i386/p4", CPU_P4, 8 }, + { "IA64", "ia64/ia64", CPU_IA64, 4 }, + { "Itanium", "ia64/itanium", CPU_IA64_1, 4 }, + { "Itanium 2", "ia64/itanium2", CPU_IA64_2, 4 }, + { "AMD64 processors", "x86-64/hammer", CPU_HAMMER, 4 }, + { "P4 / Xeon with 2 hyper-threads", "i386/p4-ht", CPU_P4_HT2, 4 }, + { "Alpha EV4", "alpha/ev4", CPU_AXP_EV4, 2 }, + { "Alpha EV5", "alpha/ev5", CPU_AXP_EV5, 3 }, + { "Alpha PCA56", "alpha/pca56", CPU_AXP_PCA56, 3 }, + { "Alpha EV6", "alpha/ev6", CPU_AXP_EV6, 2 }, + { "Alpha EV67", "alpha/ev67", CPU_AXP_EV67, 20 }, + { "Pentium M (P6 core)", "i386/p6_mobile", CPU_P6_MOBILE, 2 }, + { "ARM/XScale PMU1", "arm/xscale1", CPU_ARM_XSCALE1, 3 }, + { "ARM/XScale PMU2", "arm/xscale2", CPU_ARM_XSCALE2, 5 }, + { "ppc64 POWER4", "ppc64/power4", CPU_PPC64_POWER4, 8 }, + { "ppc64 POWER5", "ppc64/power5", CPU_PPC64_POWER5, 6 }, + { "ppc64 POWER5+", "ppc64/power5+", CPU_PPC64_POWER5p, 6 }, + { "ppc64 970", "ppc64/970", CPU_PPC64_970, 8 }, + { "MIPS 20K", "mips/20K", CPU_MIPS_20K, 1}, + { "MIPS 24K", "mips/24K", CPU_MIPS_24K, 2}, + { "MIPS 25K", "mips/25K", CPU_MIPS_25K, 2}, + { "MIPS 34K", "mips/34K", CPU_MIPS_34K, 4}, + { "MIPS 5K", "mips/5K", CPU_MIPS_5K, 2}, + { "MIPS R10000", "mips/r10000", CPU_MIPS_R10000, 2 }, + { "MIPS R12000", "mips/r12000", CPU_MIPS_R12000, 4 }, + { "QED RM7000", "mips/rm7000", CPU_MIPS_RM7000, 1 }, + { "PMC-Sierra RM9000", "mips/rm9000", CPU_MIPS_RM9000, 2 }, + { "Sibyte SB1", "mips/sb1", CPU_MIPS_SB1, 4 }, + { "NEC VR5432", "mips/vr5432", CPU_MIPS_VR5432, 2 }, + { "NEC VR5500", "mips/vr5500", CPU_MIPS_VR5500, 2 }, + { "e500", "ppc/e500", CPU_PPC_E500, 4 }, + { "e500v2", "ppc/e500v2", CPU_PPC_E500_2, 4 }, + { "Core Solo / Duo", "i386/core", CPU_CORE, 2 }, + { "PowerPC G4", "ppc/7450", CPU_PPC_7450, 6 }, + { "Core 2", "i386/core_2", CPU_CORE_2, 2 }, + { "ppc64 POWER6", "ppc64/power6", CPU_PPC64_POWER6, 4 }, + { "ppc64 970MP", "ppc64/970MP", CPU_PPC64_970MP, 8 }, + { "ppc64 Cell Broadband Engine", "ppc64/cell-be", CPU_PPC64_CELL, 8 }, + { "AMD64 family10", "x86-64/family10", CPU_FAMILY10, 4 }, + { "ppc64 PA6T", "ppc64/pa6t", CPU_PPC64_PA6T, 6 }, + { "ARM MPCore", "arm/mpcore", CPU_ARM_MPCORE, 2 }, + { "ARM V6 PMU", "arm/armv6", CPU_ARM_V6, 3 }, + { "ppc64 POWER5++", "ppc64/power5++", CPU_PPC64_POWER5pp, 6 }, + { "e300", "ppc/e300", CPU_PPC_E300, 4 }, + { "AVR32", "avr32", CPU_AVR32, 3 }, +}; + +static size_t const nr_cpu_descrs = sizeof(cpu_descrs) / sizeof(struct cpu_descr); + +op_cpu op_get_cpu_type(void) +{ + int cpu_type = CPU_NO_GOOD; + char str[100]; + FILE * fp; + + fp = fopen("/proc/sys/dev/oprofile/cpu_type", "r"); + if (!fp) { + /* Try 2.6's oprofilefs one instead. */ + fp = fopen("/dev/oprofile/cpu_type", "r"); + if (!fp) { + fprintf(stderr, "Unable to open cpu_type file for reading\n"); + fprintf(stderr, "Make sure you have done opcontrol --init\n"); + return cpu_type; + } + } + + if (!fgets(str, 99, fp)) { + fprintf(stderr, "Could not read cpu type.\n"); + return CPU_NO_GOOD; + } + + cpu_type = op_get_cpu_number(str); + + fclose(fp); + + return cpu_type; +} + + +op_cpu op_get_cpu_number(char const * cpu_string) +{ + int cpu_type = CPU_NO_GOOD; + size_t i; + + for (i = 0; i < nr_cpu_descrs; ++i) { + if (!strcmp(cpu_descrs[i].name, cpu_string)) { + cpu_type = cpu_descrs[i].cpu; + break; + } + } + + /* Attempt to convert into a number */ + if (cpu_type == CPU_NO_GOOD) + sscanf(cpu_string, "%d\n", &cpu_type); + + if (cpu_type <= CPU_NO_GOOD || cpu_type >= MAX_CPU_TYPE) + cpu_type = CPU_NO_GOOD; + + return cpu_type; +} + + +char const * op_get_cpu_type_str(op_cpu cpu_type) +{ + if (cpu_type <= CPU_NO_GOOD || cpu_type >= MAX_CPU_TYPE) + return "invalid cpu type"; + + return cpu_descrs[cpu_type].pretty; +} + + +char const * op_get_cpu_name(op_cpu cpu_type) +{ + if (cpu_type <= CPU_NO_GOOD || cpu_type >= MAX_CPU_TYPE) + return "invalid cpu type"; + + return cpu_descrs[cpu_type].name; +} + + +int op_get_nr_counters(op_cpu cpu_type) +{ + if (cpu_type <= CPU_NO_GOOD || cpu_type >= MAX_CPU_TYPE) + return 0; + + return cpu_descrs[cpu_type].nr_counters; +} diff --git a/libop/op_cpu_type.h b/libop/op_cpu_type.h new file mode 100644 index 0000000..be95ae2 --- /dev/null +++ b/libop/op_cpu_type.h @@ -0,0 +1,139 @@ +/** + * @file op_cpu_type.h + * CPU type determination + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_CPU_TYPE_H +#define OP_CPU_TYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Supported cpu type. Always add new CPU types at the very end. + */ +typedef enum { + CPU_NO_GOOD = -1, /**< unsupported CPU type */ + CPU_PPRO, /**< Pentium Pro */ + CPU_PII, /**< Pentium II series */ + CPU_PIII, /**< Pentium III series */ + CPU_ATHLON, /**< AMD P6 series */ + CPU_TIMER_INT, /**< CPU using the timer interrupt */ + CPU_RTC, /**< other CPU to use the RTC */ + CPU_P4, /**< Pentium 4 / Xeon series */ + CPU_IA64, /**< Generic IA64 */ + CPU_IA64_1, /**< IA64 Merced */ + CPU_IA64_2, /**< IA64 McKinley */ + CPU_HAMMER, /**< AMD Hammer family */ + CPU_P4_HT2, /**< Pentium 4 / Xeon series with 2 hyper-threads */ + CPU_AXP_EV4, /**< Alpha EV4 family */ + CPU_AXP_EV5, /**< Alpha EV5 family */ + CPU_AXP_PCA56, /**< Alpha PCA56 family */ + CPU_AXP_EV6, /**< Alpha EV6 family */ + CPU_AXP_EV67, /**< Alpha EV67 family */ + CPU_P6_MOBILE, /**< Pentium M series */ + CPU_ARM_XSCALE1, /**< ARM XScale 1 */ + CPU_ARM_XSCALE2, /**< ARM XScale 2 */ + CPU_PPC64_POWER4, /**< ppc64 POWER4 family */ + CPU_PPC64_POWER5, /**< ppc64 POWER5 family */ + CPU_PPC64_POWER5p, /**< ppc64 Power5+ family */ + CPU_PPC64_970, /**< ppc64 970 family */ + CPU_MIPS_20K, /**< MIPS 20K */ + CPU_MIPS_24K, /**< MIPS 24K */ + CPU_MIPS_25K, /**< MIPS 25K */ + CPU_MIPS_34K, /**< MIPS 34K */ + CPU_MIPS_5K, /**< MIPS 5K */ + CPU_MIPS_R10000, /**< MIPS R10000 */ + CPU_MIPS_R12000, /**< MIPS R12000 */ + CPU_MIPS_RM7000, /**< QED RM7000 */ + CPU_MIPS_RM9000, /**< PMC-Sierra RM9000 */ + CPU_MIPS_SB1, /**< Broadcom SB1 */ + CPU_MIPS_VR5432, /**< NEC VR5432 */ + CPU_MIPS_VR5500, /**< MIPS VR5500, VR5532 and VR7701 */ + CPU_PPC_E500, /**< e500 */ + CPU_PPC_E500_2, /**< e500v2 */ + CPU_CORE, /**< Core Solo / Duo series */ + CPU_PPC_7450, /**< PowerPC G4 */ + CPU_CORE_2, /**< Intel Core 2 */ + CPU_PPC64_POWER6, /**< ppc64 POWER6 family */ + CPU_PPC64_970MP, /**< ppc64 970MP */ + CPU_PPC64_CELL, /**< ppc64 Cell Broadband Engine*/ + CPU_FAMILY10, /**< AMD family 10 */ + CPU_PPC64_PA6T, /**< ppc64 PA6T */ + CPU_ARM_MPCORE, /**< ARM MPCore */ + CPU_ARM_V6, /**< ARM V6 */ + CPU_PPC64_POWER5pp, /**< ppc64 Power5++ family */ + CPU_PPC_E300, /**< e300 */ + CPU_AVR32, /**< AVR32 */ + MAX_CPU_TYPE +} op_cpu; + +/** + * get the CPU type from the kernel + * + * returns CPU_NO_GOOD if the CPU could not be identified. + * This function can not work if the module is not loaded + */ +op_cpu op_get_cpu_type(void); + +/** + * get the cpu number based on string + * @param cpu_string with either the cpu type identifier or cpu type number + * + * The function returns CPU_NO_GOOD if no matching string was found. + */ +op_cpu op_get_cpu_number(char const * cpu_string); + +/** + * get the cpu string. + * @param cpu_type the cpu type identifier + * + * The function always return a valid char const * the core cpu denomination + * or "invalid cpu type" if cpu_type is not valid. + */ +char const * op_get_cpu_type_str(op_cpu cpu_type); + +/** + * op_get_cpu_name - get the cpu name + * @param cpu_type the cpu identifier name + * + * The function always return a valid char const * + * Return the OProfile CPU name, e.g. "i386/pii" + */ +char const * op_get_cpu_name(op_cpu cpu_type); + +/** + * compute the number of counters available + * @param cpu_type numeric processor type + * + * returns 0 if the CPU could not be identified + */ +int op_get_nr_counters(op_cpu cpu_type); + +typedef enum { + OP_INTERFACE_NO_GOOD = -1, + OP_INTERFACE_24, + OP_INTERFACE_26 +} op_interface; + +/** + * get the INTERFACE used to communicate between daemon and the kernel + * + * returns OP_INTERFACE_NO_GOOD if the INTERFACE could not be identified. + * This function will identify the interface as OP_INTERFACE_NO_GOOD if + * the module is not loaded. + */ +op_interface op_get_interface(void); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_CPU_TYPE_H */ diff --git a/libop/op_events.c b/libop/op_events.c new file mode 100644 index 0000000..b4a10e7 --- /dev/null +++ b/libop/op_events.c @@ -0,0 +1,862 @@ +/** + * @file op_events.c + * Details of PMC profiling events + * + * You can have silliness here. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "op_events.h" +#include "op_libiberty.h" +#include "op_fileio.h" +#include "op_string.h" +#include "op_cpufreq.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +static LIST_HEAD(events_list); +static LIST_HEAD(um_list); + +static char const * filename; +static unsigned int line_nr; + +static void parse_error(char const * context) +{ + fprintf(stderr, "oprofile: parse error in %s, line %u\n", + filename, line_nr); + fprintf(stderr, "%s\n", context); + exit(EXIT_FAILURE); +} + + +static int parse_int(char const * str) +{ + int value; + if (sscanf(str, "%d", &value) != 1) + parse_error("expected decimal value"); + + return value; +} + + +static int parse_hex(char const * str) +{ + int value; + /* 0x/0X to force the use of hexa notation for field intended to + be in hexadecimal */ + if (sscanf(str, "0x%x", &value) != 1 && + sscanf(str, "0X%x", &value) != 1) + parse_error("expected hexadecimal value"); + + return value; +} + + +static u64 parse_long_hex(char const * str) +{ + u64 value; + if (sscanf(str, "%Lx", &value) != 1) + parse_error("expected long hexadecimal value"); + + fflush(stderr); + return value; +} + + +/* name:MESI type:bitmask default:0x0f */ +static void parse_um(struct op_unit_mask * um, char const * line) +{ + int seen_name = 0; + int seen_type = 0; + int seen_default = 0; + char const * valueend = line + 1; + char const * tagend = line + 1; + char const * start = line; + + while (*valueend) { + valueend = skip_nonws(valueend); + + while (*tagend != ':' && *tagend) + ++tagend; + + if (valueend == tagend) + break; + + if (!*tagend) + parse_error("parse_um() expected :value"); + + ++tagend; + + if (strisprefix(start, "name")) { + if (seen_name) + parse_error("duplicate name: tag"); + seen_name = 1; + um->name = op_xstrndup(tagend, valueend - tagend); + } else if (strisprefix(start, "type")) { + if (seen_type) + parse_error("duplicate type: tag"); + seen_type = 1; + if (strisprefix(tagend, "mandatory")) { + um->unit_type_mask = utm_mandatory; + } else if (strisprefix(tagend, "bitmask")) { + um->unit_type_mask = utm_bitmask; + } else if (strisprefix(tagend, "exclusive")) { + um->unit_type_mask = utm_exclusive; + } else { + parse_error("invalid unit mask type"); + } + } else if (strisprefix(start, "default")) { + if (seen_default) + parse_error("duplicate default: tag"); + seen_default = 1; + um->default_mask = parse_hex(tagend); + } else { + parse_error("invalid unit mask tag"); + } + + valueend = skip_ws(valueend); + tagend = valueend; + start = valueend; + } +} + + +/* \t0x08 (M)odified cache state */ +static void parse_um_entry(struct op_described_um * entry, char const * line) +{ + char const * c = line; + + c = skip_ws(c); + entry->value = parse_hex(c); + c = skip_nonws(c); + + if (!*c) + parse_error("invalid unit mask entry"); + + c = skip_ws(c); + + if (!*c) + parse_error("invalid unit mask entry"); + + entry->desc = xstrdup(c); +} + + +static struct op_unit_mask * new_unit_mask(void) +{ + struct op_unit_mask * um = xmalloc(sizeof(struct op_unit_mask)); + memset(um, '\0', sizeof(struct op_unit_mask)); + list_add_tail(&um->um_next, &um_list); + + return um; +} + + +/* + * name:zero type:mandatory default:0x0 + * \t0x0 No unit mask + */ +static void read_unit_masks(char const * file) +{ + struct op_unit_mask * um = NULL; + char * line; + FILE * fp = fopen(file, "r"); + + if (!fp) { + fprintf(stderr, + "oprofile: could not open unit mask description file %s\n", file); + exit(EXIT_FAILURE); + } + + filename = file; + line_nr = 1; + + line = op_get_line(fp); + + while (line) { + if (empty_line(line) || comment_line(line)) + goto next; + + if (line[0] != '\t') { + um = new_unit_mask(); + parse_um(um, line); + } else { + if (!um) + parse_error("no unit mask name line"); + if (um->num >= MAX_UNIT_MASK) + parse_error("oprofile: maximum unit mask entries exceeded"); + + parse_um_entry(&um->um[um->num], line); + ++(um->num); + } + +next: + free(line); + line = op_get_line(fp); + ++line_nr; + } + + fclose(fp); +} + + +static u32 parse_counter_mask(char const * str) +{ + u32 mask = 0; + char const * numstart = str; + + while (*numstart) { + mask |= 1 << parse_int(numstart); + + while (*numstart && *numstart != ',') + ++numstart; + /* skip , unless we reach eos */ + if (*numstart) + ++numstart; + + numstart = skip_ws(numstart); + } + + return mask; +} + + +static struct op_unit_mask * find_um(char const * value) +{ + struct list_head * pos; + + list_for_each(pos, &um_list) { + struct op_unit_mask * um = list_entry(pos, struct op_unit_mask, um_next); + if (strcmp(value, um->name) == 0) + return um; + } + + fprintf(stderr, "oprofile: could not find unit mask %s\n", value); + exit(EXIT_FAILURE); +} + + +/* parse either a "tag:value" or a ": trailing description string" */ +static int next_token(char const ** cp, char ** name, char ** value) +{ + size_t tag_len; + size_t val_len; + char const * c = *cp; + char const * end; + char const * colon; + + c = skip_ws(c); + end = colon = c; + end = skip_nonws(end); + + colon = strchr(colon, ':'); + + if (!colon) { + if (*c) + parse_error("next_token(): garbage at end of line"); + return 0; + } + + if (colon >= end) + parse_error("next_token() expected ':'"); + + tag_len = colon - c; + val_len = end - (colon + 1); + + if (!tag_len) { + /* : trailing description */ + end = skip_ws(end); + *name = xstrdup("desc"); + *value = xstrdup(end); + end += strlen(end); + } else { + /* tag:value */ + *name = op_xstrndup(c, tag_len); + *value = op_xstrndup(colon + 1, val_len); + end = skip_ws(end); + } + + *cp = end; + return 1; +} + + +static struct op_event * new_event(void) +{ + struct op_event * event = xmalloc(sizeof(struct op_event)); + memset(event, '\0', sizeof(struct op_event)); + list_add_tail(&event->event_next, &events_list); + + return event; +} + + +/* event:0x00 counters:0 um:zero minimum:4096 name:ISSUES : Total issues */ +static void read_events(char const * file) +{ + struct op_event * event = NULL; + char * line; + char * name; + char * value; + char const * c; + int seen_event, seen_counters, seen_um, seen_minimum, seen_name; + FILE * fp = fopen(file, "r"); + + if (!fp) { + fprintf(stderr, "oprofile: could not open event description file %s\n", file); + exit(EXIT_FAILURE); + } + + filename = file; + line_nr = 1; + + line = op_get_line(fp); + + while (line) { + if (empty_line(line) || comment_line(line)) + goto next; + + seen_name = 0; + seen_event = 0; + seen_counters = 0; + seen_um = 0; + seen_minimum = 0; + event = new_event(); + + c = line; + while (next_token(&c, &name, &value)) { + if (strcmp(name, "name") == 0) { + if (seen_name) + parse_error("duplicate name: tag"); + seen_name = 1; + if (strchr(value, '/') != NULL) + parse_error("invalid event name"); + if (strchr(value, '.') != NULL) + parse_error("invalid event name"); + event->name = value; + } else if (strcmp(name, "event") == 0) { + if (seen_event) + parse_error("duplicate event: tag"); + seen_event = 1; + event->val = parse_hex(value); + free(value); + } else if (strcmp(name, "counters") == 0) { + if (seen_counters) + parse_error("duplicate counters: tag"); + seen_counters = 1; + event->counter_mask = parse_counter_mask(value); + free(value); + } else if (strcmp(name, "um") == 0) { + if (seen_um) + parse_error("duplicate um: tag"); + seen_um = 1; + event->unit = find_um(value); + event->unit->used = 1; + free(value); + } else if (strcmp(name, "minimum") == 0) { + if (seen_minimum) + parse_error("duplicate minimum: tag"); + seen_minimum = 1; + event->min_count = parse_int(value); + free(value); + } else if (strcmp(name, "desc") == 0) { + event->desc = value; + } else { + parse_error("unknown tag"); + } + + free(name); + } +next: + free(line); + line = op_get_line(fp); + ++line_nr; + } + + fclose(fp); +} + + +/* usefull for make check */ +static void check_unit_mask(struct op_unit_mask const * um, + char const * cpu_name) +{ + u32 i; + + if (!um->used) { + fprintf(stderr, "um %s is not used\n", um->name); + exit(EXIT_FAILURE); + } + + if (um->unit_type_mask == utm_mandatory && um->num != 1) { + fprintf(stderr, "mandatory um %s doesn't contain exactly one " + "entry (%s)\n", um->name, cpu_name); + exit(EXIT_FAILURE); + } else if (um->unit_type_mask == utm_bitmask) { + u32 default_mask = um->default_mask; + for (i = 0; i < um->num; ++i) + default_mask &= ~um->um[i].value; + + if (default_mask) { + fprintf(stderr, "um %s default mask is not valid " + "(%s)\n", um->name, cpu_name); + exit(EXIT_FAILURE); + } + } else { + for (i = 0; i < um->num; ++i) { + if (um->default_mask == um->um[i].value) + break; + } + + if (i == um->num) { + fprintf(stderr, "exclusive um %s default value is not " + "valid (%s)\n", um->name, cpu_name); + exit(EXIT_FAILURE); + } + } +} + + +static void load_events(op_cpu cpu_type) +{ + char const * cpu_name = op_get_cpu_name(cpu_type); + char * event_dir; + char * event_file; + char * um_file; + char * dir; + struct list_head * pos; + + if (!list_empty(&events_list)) + return; + + dir = getenv("OPROFILE_EVENTS_DIR"); + if (dir == NULL) + dir = OP_DATADIR; + + event_dir = xmalloc(strlen(dir) + strlen("/") + strlen(cpu_name) + + strlen("/") + 1); + strcpy(event_dir, dir); + strcat(event_dir, "/"); + + strcat(event_dir, cpu_name); + strcat(event_dir, "/"); + + event_file = xmalloc(strlen(event_dir) + strlen("events") + 1); + strcpy(event_file, event_dir); + strcat(event_file, "events"); + + um_file = xmalloc(strlen(event_dir) + strlen("unit_masks") + 1); + strcpy(um_file, event_dir); + strcat(um_file, "unit_masks"); + + read_unit_masks(um_file); + read_events(event_file); + + /* sanity check: all unit mask must be used */ + list_for_each(pos, &um_list) { + struct op_unit_mask * um = list_entry(pos, struct op_unit_mask, um_next); + + check_unit_mask(um, cpu_name); + } + + free(um_file); + free(event_file); + free(event_dir); +} + + +struct list_head * op_events(op_cpu cpu_type) +{ + load_events(cpu_type); + return &events_list; +} + + +static void delete_unit_mask(struct op_unit_mask * unit) +{ + u32 cur; + for (cur = 0 ; cur < unit->num ; ++cur) { + if (unit->um[cur].desc) + free(unit->um[cur].desc); + } + + if (unit->name) + free(unit->name); + + list_del(&unit->um_next); + free(unit); +} + + +static void delete_event(struct op_event * event) +{ + if (event->name) + free(event->name); + if (event->desc) + free(event->desc); + + list_del(&event->event_next); + free(event); +} + + +void op_free_events(void) +{ + struct list_head * pos, * pos2; + list_for_each_safe(pos, pos2, &events_list) { + struct op_event * event = list_entry(pos, struct op_event, event_next); + delete_event(event); + } + + list_for_each_safe(pos, pos2, &um_list) { + struct op_unit_mask * unit = list_entry(pos, struct op_unit_mask, um_next); + delete_unit_mask(unit); + } +} + + +static struct op_event * find_event(u32 nr) +{ + struct list_head * pos; + + list_for_each(pos, &events_list) { + struct op_event * event = list_entry(pos, struct op_event, event_next); + if (event->val == nr) + return event; + } + + return NULL; +} + + +static FILE * open_event_mapping_file(char const * cpu_name) +{ + char * ev_map_file; + char * dir; + dir = getenv("OPROFILE_EVENTS_DIR"); + if (dir == NULL) + dir = OP_DATADIR; + + ev_map_file = xmalloc(strlen(dir) + strlen("/") + strlen(cpu_name) + + strlen("/") + + strlen("event_mappings") + 1); + strcpy(ev_map_file, dir); + strcat(ev_map_file, "/"); + + strcat(ev_map_file, cpu_name); + strcat(ev_map_file, "/"); + strcat(ev_map_file, "event_mappings"); + filename = ev_map_file; + return (fopen(ev_map_file, "r")); +} + + +/** + * This function is PPC64-specific. + */ +static char const * get_mapping(u32 nr, FILE * fp) +{ + char * line; + char * name; + char * value; + char const * c; + char * map = NULL; + int seen_event = 0, seen_mmcr0 = 0, seen_mmcr1 = 0, seen_mmcra = 0; + u32 mmcr0 = 0; + u64 mmcr1 = 0; + u32 mmcra = 0; + int event_found = 0; + + line_nr = 1; + line = op_get_line(fp); + while (line && !event_found) { + if (empty_line(line) || comment_line(line)) + goto next; + + seen_event = 0; + seen_mmcr0 = 0; + seen_mmcr1 = 0; + seen_mmcra = 0; + mmcr0 = 0; + mmcr1 = 0; + mmcra = 0; + + c = line; + while (next_token(&c, &name, &value)) { + if (strcmp(name, "event") == 0) { + u32 evt; + if (seen_event) + parse_error("duplicate event tag"); + seen_event = 1; + evt = parse_hex(value); + if (evt == nr) + event_found = 1; + free(value); + } else if (strcmp(name, "mmcr0") == 0) { + if (seen_mmcr0) + parse_error("duplicate mmcr0 tag"); + seen_mmcr0 = 1; + mmcr0 = parse_hex(value); + free(value); + } else if (strcmp(name, "mmcr1") == 0) { + if (seen_mmcr1) + parse_error("duplicate mmcr1: tag"); + seen_mmcr1 = 1; + mmcr1 = parse_long_hex(value); + free(value); + } else if (strcmp(name, "mmcra") == 0) { + if (seen_mmcra) + parse_error("duplicate mmcra: tag"); + seen_mmcra = 1; + mmcra = parse_hex(value); + free(value); + } else { + parse_error("unknown tag"); + } + + free(name); + } +next: + free(line); + line = op_get_line(fp); + ++line_nr; + } + if (event_found) { + if (!seen_mmcr0 || !seen_mmcr1 || !seen_mmcra) { + fprintf(stderr, "Error: Missing information in line %d of event mapping file %s\n", line_nr, filename); + exit(EXIT_FAILURE); + } + map = xmalloc(70); + snprintf(map, 70, "mmcr0:%u mmcr1:%Lu mmcra:%u", + mmcr0, mmcr1, mmcra); + } + + return map; +} + + +char const * find_mapping_for_event(u32 nr, op_cpu cpu_type) +{ + char const * cpu_name = op_get_cpu_name(cpu_type); + FILE * fp = open_event_mapping_file(cpu_name); + char const * map = NULL; + switch (cpu_type) { + case CPU_PPC64_PA6T: + case CPU_PPC64_970: + case CPU_PPC64_970MP: + case CPU_PPC64_POWER4: + case CPU_PPC64_POWER5: + case CPU_PPC64_POWER5p: + case CPU_PPC64_POWER5pp: + case CPU_PPC64_POWER6: + if (!fp) { + fprintf(stderr, "oprofile: could not open event mapping file %s\n", filename); + exit(EXIT_FAILURE); + } else { + map = get_mapping(nr, fp); + } + break; + default: + break; + } + + if (fp) + fclose(fp); + + return map; +} + + +struct op_event * find_event_by_name(char const * name) +{ + struct list_head * pos; + + list_for_each(pos, &events_list) { + struct op_event * event = list_entry(pos, struct op_event, event_next); + if (strcmp(event->name, name) == 0) + return event; + } + + return NULL; +} + + +struct op_event * op_find_event(op_cpu cpu_type, u32 nr) +{ + struct op_event * event; + + load_events(cpu_type); + + event = find_event(nr); + + return event; +} + + +int op_check_events(int ctr, u32 nr, u32 um, op_cpu cpu_type) +{ + int ret = OP_OK_EVENT; + struct op_event * event; + size_t i; + u32 ctr_mask = 1 << ctr; + + load_events(cpu_type); + + event = find_event(nr); + + if (!event) { + ret |= OP_INVALID_EVENT; + return ret; + } + + if ((event->counter_mask & ctr_mask) == 0) + ret |= OP_INVALID_COUNTER; + + if (event->unit->unit_type_mask == utm_bitmask) { + for (i = 0; i < event->unit->num; ++i) + um &= ~(event->unit->um[i].value); + + if (um) + ret |= OP_INVALID_UM; + + } else { + for (i = 0; i < event->unit->num; ++i) { + if (event->unit->um[i].value == um) + break; + } + + if (i == event->unit->num) + ret |= OP_INVALID_UM; + } + + return ret; +} + + +void op_default_event(op_cpu cpu_type, struct op_default_event_descr * descr) +{ + descr->name = ""; + descr->um = 0x0; + /* A fixed value of CPU cycles; this should ensure good + * granulity even on faster CPUs, though it will generate more + * interrupts. + */ + descr->count = 100000; + + switch (cpu_type) { + case CPU_PPRO: + case CPU_PII: + case CPU_PIII: + case CPU_P6_MOBILE: + case CPU_CORE: + case CPU_CORE_2: + case CPU_ATHLON: + case CPU_HAMMER: + case CPU_FAMILY10: + descr->name = "CPU_CLK_UNHALTED"; + break; + + case CPU_RTC: + descr->name = "RTC_INTERRUPTS"; + descr->count = 1024; + break; + + case CPU_P4: + case CPU_P4_HT2: + descr->name = "GLOBAL_POWER_EVENTS"; + descr->um = 0x1; + break; + + case CPU_IA64: + case CPU_IA64_1: + case CPU_IA64_2: + descr->count = 1000000; + descr->name = "CPU_CYCLES"; + break; + + case CPU_AXP_EV4: + case CPU_AXP_EV5: + case CPU_AXP_PCA56: + case CPU_AXP_EV6: + case CPU_AXP_EV67: + descr->name = "CYCLES"; + break; + + // we could possibly use the CCNT + case CPU_ARM_XSCALE1: + case CPU_ARM_XSCALE2: + case CPU_ARM_MPCORE: + case CPU_ARM_V6: + case CPU_AVR32: + descr->name = "CPU_CYCLES"; + break; + + case CPU_PPC64_PA6T: + case CPU_PPC64_970: + case CPU_PPC64_970MP: + case CPU_PPC_7450: + case CPU_PPC64_POWER4: + case CPU_PPC64_POWER5: + case CPU_PPC64_POWER6: + case CPU_PPC64_POWER5p: + case CPU_PPC64_POWER5pp: + case CPU_PPC64_CELL: + descr->name = "CYCLES"; + break; + + case CPU_MIPS_20K: + descr->name = "CYCLES"; + break; + + case CPU_MIPS_24K: + descr->name = "INSTRUCTIONS"; + break; + + case CPU_MIPS_34K: + descr->name = "INSTRUCTIONS"; + break; + + case CPU_MIPS_5K: + case CPU_MIPS_25K: + descr->name = "CYCLES"; + break; + + case CPU_MIPS_R10000: + case CPU_MIPS_R12000: + descr->name = "INSTRUCTIONS_GRADUATED"; + break; + + case CPU_MIPS_RM7000: + case CPU_MIPS_RM9000: + descr->name = "INSTRUCTIONS_ISSUED"; + break; + + case CPU_MIPS_SB1: + descr->name = "INSN_SURVIVED_STAGE7"; + break; + + case CPU_MIPS_VR5432: + case CPU_MIPS_VR5500: + descr->name = "INSTRUCTIONS_EXECUTED"; + break; + + case CPU_PPC_E500: + case CPU_PPC_E500_2: + case CPU_PPC_E300: + descr->name = "CPU_CLK"; + break; + + // don't use default, if someone add a cpu he wants a compiler + // warning if he forgets to handle it here. + case CPU_TIMER_INT: + case CPU_NO_GOOD: + case MAX_CPU_TYPE: + break; + } +} diff --git a/libop/op_events.h b/libop/op_events.h new file mode 100644 index 0000000..f6462fc --- /dev/null +++ b/libop/op_events.h @@ -0,0 +1,125 @@ +/** + * @file op_events.h + * Details of PMC profiling events + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_EVENTS_H +#define OP_EVENTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "op_cpu_type.h" +#include "op_types.h" +#include "op_list.h" + +/** Describe an unit mask type. Events can optionally use a filter called + * the unit mask. the mask type can be a bitmask or a discrete value */ +enum unit_mask_type { + utm_mandatory, /**< useless but required by the hardware */ + utm_exclusive, /**< only one of the values is allowed */ + utm_bitmask /**< bitmask */ +}; + +/** up to thirty two allowed unit masks */ +#define MAX_UNIT_MASK 32 + + +/** Describe an unit mask. */ +struct op_unit_mask { + char * name; /**< name of unit mask type */ + u32 num; /**< number of possible unit masks */ + enum unit_mask_type unit_type_mask; + u32 default_mask; /**< only the gui use it */ + struct op_described_um { + u32 value; + char * desc; + } um[MAX_UNIT_MASK]; + struct list_head um_next; /**< next um in list */ + int used; /**< used by events file parser */ +}; + + +/** Describe an event. */ +struct op_event { + u32 counter_mask; /**< bitmask of allowed counter */ + u32 val; /**< event number */ + /** which unit mask if any allowed */ + struct op_unit_mask * unit; + char * name; /**< the event name */ + char * desc; /**< the event description */ + int min_count; /**< minimum counter value allowed */ + struct list_head event_next; /**< next event in list */ +}; + +/** Return the known events list. Idempotent */ +struct list_head * op_events(op_cpu cpu_type); + +/** Find a given event, returns NULL on error */ +struct op_event * op_find_event(op_cpu cpu_type, u32 nr); + +/** Find a given event by name */ +struct op_event * find_event_by_name(char const * name); + +/** + * Find a mapping for a given event ID for architectures requiring additional information + * from what is held in the events file. + */ +char const * find_mapping_for_event(u32 val, op_cpu cpu_type); + + +/** op_check_events() return code */ +enum op_event_check { + OP_OK_EVENT = 0, /**< event is valid and allowed */ + OP_INVALID_EVENT = 1, /**< event number is invalid */ + OP_INVALID_UM = 2, /**< unit mask is invalid */ + OP_INVALID_COUNTER = 4 /**< event is not allowed for the given counter */ +}; + +/** + * sanity check event values + * @param ctr counter number + * @param event value for counter + * @param um unit mask for counter + * @param cpu_type processor type + * + * Check that the counter event and unit mask values are allowed. + * + * The function returns bitmask of failure cause 0 otherwise + * + * \sa op_cpu, OP_EVENTS_OK + */ +int op_check_events(int ctr, u32 event, u32 um, op_cpu cpu_type); + +/** + * free memory used by any call to above function. Need to be called only once + */ +void op_free_events(void); + +struct op_default_event_descr { + char * name; + unsigned long count; + unsigned long um; +}; + +/** + * op_default_event - return the details of the default event + * @param cpu_type cpu type + * @param descr filled event description + * + * Fills in the event description if applicable + */ +void op_default_event(op_cpu cpu_type, struct op_default_event_descr * descr); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_EVENTS_H */ diff --git a/libop/op_get_interface.c b/libop/op_get_interface.c new file mode 100644 index 0000000..bdf72a5 --- /dev/null +++ b/libop/op_get_interface.c @@ -0,0 +1,32 @@ +/** + * @file op_get_interface.c + * Determine which oprofile kernel interface used + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Will Cohen + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "op_cpu_type.h" +#include "op_file.h" + +op_interface op_get_interface(void) +{ + static op_interface current_interface = OP_INTERFACE_NO_GOOD; + + if (current_interface != OP_INTERFACE_NO_GOOD) + return current_interface; + + if (op_file_readable("/proc/sys/dev/oprofile/cpu_type")) { + current_interface = OP_INTERFACE_24; + } else if (op_file_readable("/dev/oprofile/cpu_type")) { + current_interface = OP_INTERFACE_26; + } + + return current_interface; +} diff --git a/libop/op_hw_config.h b/libop/op_hw_config.h new file mode 100644 index 0000000..169b36b --- /dev/null +++ b/libop/op_hw_config.h @@ -0,0 +1,30 @@ +/** + * @file op_hw_config.h + * Configuration parameters that are dependent on CPU/architecture + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_HW_CONFIG_H +#define OP_HW_CONFIG_H + +/** maximum number of counters, up to 4 for Athlon (18 for P4). The primary + * use of this variable is for static/local array dimension. Never use it in + * loop or in array index access/index checking unless you know what you + * made. */ +#ifdef __alpha__ +#define OP_MAX_COUNTERS 20 +#else +#define OP_MAX_COUNTERS 8 +#endif + +/** maximum number of events between interrupts. Counters are 40 bits, but + * for convenience we only use 32 bits. The top bit is used for overflow + * detection, so user can set up to (2^31)-1 */ +#define OP_MAX_PERF_COUNT 2147483647UL + +#endif /* OP_HW_CONFIG_H */ diff --git a/libop/op_interface.h b/libop/op_interface.h new file mode 100644 index 0000000..fa2ecbd --- /dev/null +++ b/libop/op_interface.h @@ -0,0 +1,87 @@ +/** + * @file op_interface.h + * + * Module / user space interface for 2.4 + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_INTERFACE_H +#define OP_INTERFACE_H + +#include "op_config.h" +#include "op_types.h" + +/*@{\name notifications types encoded in op_note::type */ +/** fork(),vfork(),clone() */ +#define OP_FORK 1 +/** mapping */ +#define OP_MAP 2 +/** execve() */ +#define OP_EXEC 4 +/** init_module() */ +#define OP_DROP_MODULES 8 +/** exit() */ +#define OP_EXIT 16 +/*@}*/ + +/** Data type to transfer samples counts from the module to the daemon */ +struct op_sample { + unsigned long eip; /**< eip value where occur interrupt */ + u32 counter; /**< counter nr */ + u32 pid; /**< 32 bits can hold any pid */ + u32 tgid; /**< always equal to pid for kernel < 2.4.0 */ +}; + +/** the current kernel-side profiler state */ +enum oprof_state { + STOPPED = 0, + STOPPING = 1, + RUNNING = 2 +}; + +/** + * The head structure of a kernel sample buffer. + */ +struct op_buffer_head { + int cpu_nr; /**< the CPU number of this buffer */ + size_t count; /**< number of samples in this buffer */ + enum oprof_state state; /**< current profiler state */ + struct op_sample buffer[0]; /**< the sample buffer */ +} __attribute__((__packed__)); + +/** + * Data type used by the module to notify daemon of fork/exit/mapping etc. + * Meanings of fields depend on the type of notification encoded in the type + * field. + * \sa OP_FORK, OP_EXEC, OP_MAP, OP_DROP_MODULES and OP_EXIT + */ +struct op_note { + unsigned long addr; + unsigned long len; + unsigned long offset; + unsigned int hash; + unsigned int pid; + unsigned int tgid; + unsigned short type; +}; + +/** + * A path component. Directory name are stored as a stack of path components. + * Note than the name index acts also as an unique identifier + */ +struct op_hash_index { + /** index inside the string pool */ + u32 name; + /** parent component, zero if this component is the root */ + u32 parent; +} __attribute__((__packed__)); + +/** size of hash map in bytes */ +#define OP_HASH_MAP_SIZE (OP_HASH_MAP_NR * sizeof(struct op_hash_index) + POOL_SIZE) + +#endif /* OP_INTERFACE_H */ diff --git a/libop/op_mangle.c b/libop/op_mangle.c new file mode 100644 index 0000000..1efe5b1 --- /dev/null +++ b/libop/op_mangle.c @@ -0,0 +1,104 @@ +/** + * @file op_mangle.c + * Mangling of sample file names + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "op_mangle.h" + +#include <string.h> +#include <stdio.h> + +#include "op_libiberty.h" + +#include "op_sample_file.h" +#include "op_config.h" + +static void append_image(char * dest, int flags, int anon, char const * name, char const * anon_name) +{ + if ((flags & MANGLE_KERNEL) && !strchr(name, '/')) { + strcat(dest, "{kern}/"); + } else if (anon) { + strcat(dest, "{anon:"); + strcat(dest, anon_name); + strcat(dest,"}/"); + } else { + strcat(dest, "{root}/"); + } + + strcat(dest, name); + strcat(dest, "/"); +} + +char * op_mangle_filename(struct mangle_values const * values) +{ + char * mangled; + size_t len; + int anon = values->flags & MANGLE_ANON; + int cg_anon = values->flags & MANGLE_CG_ANON; + /* if dep_name != image_name we need to invert them (and so revert them + * unconditionally because if they are equal it doesn't hurt to invert + * them), see P:3, FIXME: this is a bit weirds, we prolly need to + * reword pp_interface */ + char const * image_name = values->dep_name; + char const * anon_name = values->anon_name; + char const * dep_name = values->image_name; + char const * cg_image_name = values->cg_image_name; + + len = strlen(op_samples_current_dir) + strlen(dep_name) + 1 + + strlen(values->event_name) + 1 + strlen(image_name) + 1; + + if (values->flags & MANGLE_CALLGRAPH) + len += strlen(cg_image_name) + 1; + + if (anon || cg_anon) + len += strlen(anon_name); + + /* provision for tgid, tid, unit_mask, cpu and some {root}, {dep}, + * {kern}, {anon} and {cg} marker */ + /* FIXME: too ugly */ + len += 256; + + mangled = xmalloc(len); + + strcpy(mangled, op_samples_current_dir); + append_image(mangled, values->flags, 0, image_name, anon_name); + + strcat(mangled, "{dep}" "/"); + append_image(mangled, values->flags, anon, dep_name, anon_name); + + if (values->flags & MANGLE_CALLGRAPH) { + strcat(mangled, "{cg}" "/"); + append_image(mangled, values->flags, cg_anon, + cg_image_name, anon_name); + } + + strcat(mangled, values->event_name); + sprintf(mangled + strlen(mangled), ".%d.%d.", + values->count, values->unit_mask); + + if (values->flags & MANGLE_TGID) { + sprintf(mangled + strlen(mangled), "%d.", values->tgid); + } else { + sprintf(mangled + strlen(mangled), "%s.", "all"); + } + + if (values->flags & MANGLE_TID) { + sprintf(mangled + strlen(mangled), "%d.", values->tid); + } else { + sprintf(mangled + strlen(mangled), "%s.", "all"); + } + + if (values->flags & MANGLE_CPU) { + sprintf(mangled + strlen(mangled), "%d", values->cpu); + } else { + sprintf(mangled + strlen(mangled), "%s", "all"); + } + + return mangled; +} diff --git a/libop/op_mangle.h b/libop/op_mangle.h new file mode 100644 index 0000000..9b600dc --- /dev/null +++ b/libop/op_mangle.h @@ -0,0 +1,66 @@ +/** + * @file op_mangle.h + * Mangling of sample file names + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_MANGLE_H +#define OP_MANGLE_H + +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +enum mangle_flags { + MANGLE_NONE = 0, + MANGLE_CPU = (1 << 0), + MANGLE_TGID = (1 << 1), + MANGLE_TID = (1 << 2), + MANGLE_KERNEL = (1 << 3), + MANGLE_CALLGRAPH = (1 << 4), + MANGLE_ANON = (1 << 5), + MANGLE_CG_ANON = (1 << 6), +}; + +/** + * Temporary structure for passing parameters to + * op_mangle_filename. + */ +struct mangle_values { + int flags; + + char const * image_name; + char const * anon_name; + char const * dep_name; + char const * cg_image_name; + char const * event_name; + int count; + unsigned int unit_mask; + pid_t tgid; + pid_t tid; + int cpu; +}; + +/** + * op_mangle_filename - mangle a sample filename + * @param values parameters to use as mangling input + * + * See also PP:3 for the encoding scheme + * + * Returns a char* pointer to the mangled string. Caller + * is responsible for freeing this string. + */ +char * op_mangle_filename(struct mangle_values const * values); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_MANGLE_H */ diff --git a/libop/op_parse_event.c b/libop/op_parse_event.c new file mode 100644 index 0000000..920d617 --- /dev/null +++ b/libop/op_parse_event.c @@ -0,0 +1,120 @@ +/** + * @file op_parse_event.c + * event parsing + * + * You can have silliness here. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "op_parse_event.h" +#include "op_string.h" + +static char * next_part(char const ** str) +{ + char const * c; + char * ret; + + if ((*str)[0] == '\0') + return NULL; + + if ((*str)[0] == ':') + ++(*str); + + c = *str; + + while (*c != '\0' && *c != ':') + ++c; + + if (c == *str) + return NULL; + + ret = op_xstrndup(*str, c - *str); + *str += c - *str; + return ret; +} + + +static int parse_ulong(char const * str) +{ + unsigned long value; + char * end; + value = strtoul(str, &end, 0); + if (end && *end) { + fprintf(stderr, "Invalid event part %s\n", str); + exit(EXIT_FAILURE); + } + + return value; +} + + +size_t parse_events(struct parsed_event * parsed_events, size_t max_events, + char const * const * events) +{ + size_t i = 0; + + while (events[i]) { + char const * cp = events[i]; + char * part = next_part(&cp); + + if (i >= max_events) { + fprintf(stderr, "Too many events specified: CPU " + "only has %lu counters.\n", + (unsigned long) max_events); + exit(EXIT_FAILURE); + } + + if (!part) { + fprintf(stderr, "Invalid event %s\n", cp); + exit(EXIT_FAILURE); + } + + parsed_events[i].name = part; + + part = next_part(&cp); + + if (!part) { + fprintf(stderr, "Invalid count for event %s\n", events[i]); + exit(EXIT_FAILURE); + } + + parsed_events[i].count = parse_ulong(part); + free(part); + + parsed_events[i].unit_mask = 0; + part = next_part(&cp); + + if (part) { + parsed_events[i].unit_mask = parse_ulong(part); + free(part); + } + + parsed_events[i].kernel = 1; + part = next_part(&cp); + + if (part) { + parsed_events[i].kernel = parse_ulong(part); + free(part); + } + + parsed_events[i].user = 1; + part = next_part(&cp); + + if (part) { + parsed_events[i].user = parse_ulong(part); + free(part); + } + + ++i; + } + + return i; +} diff --git a/libop/op_parse_event.h b/libop/op_parse_event.h new file mode 100644 index 0000000..247a355 --- /dev/null +++ b/libop/op_parse_event.h @@ -0,0 +1,42 @@ +/** + * @file op_parse_event.h + * event parsing + * + * You can have silliness here. + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_PARSE_EVENT_H +#define OP_PARSE_EVENT_H + +#include <stddef.h> + +struct parsed_event { + char * name; + int count; + int unit_mask; + int kernel; + int user; +}; + +/** + * @param parsed_events array of events to fill in + * @param max_events size of parsed_events + * @param events null terminated array of events string on the form + * event_name:count[:unit_mask:kernel:user] + * + * parse events given by the nil terminated array events and fill in + * parsed_events with results. Events validity are not checked except. + * A fatal error occur if number of events is greater than max_events. + * + * Return the number of events parsed. + */ +size_t parse_events(struct parsed_event * parsed_events, size_t max_events, + char const * const * events); + +#endif /* !OP_PARSE_EVENT_H */ diff --git a/libop/op_sample_file.h b/libop/op_sample_file.h new file mode 100644 index 0000000..4f9f1d0 --- /dev/null +++ b/libop/op_sample_file.h @@ -0,0 +1,42 @@ +/** + * @file op_sample_file.h + * Sample file format + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_SAMPLE_FILE_H +#define OP_SAMPLE_FILE_H + +#include "op_types.h" + +#include <stdint.h> +#include <time.h> + +/* header of the sample files */ +struct opd_header { + u8 magic[4]; + u32 version; + u32 cpu_type; + u32 ctr_event; + u32 ctr_um; + u32 ctr_count; + // for cg file the from_cg_is_kernel + u32 is_kernel; + double cpu_speed; + time_t mtime; + u32 cg_to_is_kernel; + /* spu_profile=1 says sample file contains Cell BE SPU profile data */ + u32 spu_profile; + uint64_t embedded_offset; + u64 anon_start; + u64 cg_to_anon_start; + /* binary compatibility reserve */ + u32 reserved1[1]; +}; + +#endif /* OP_SAMPLE_FILE_H */ diff --git a/libpopt/Android.mk b/libpopt/Android.mk new file mode 100644 index 0000000..b20cf55 --- /dev/null +++ b/libpopt/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + findme.c \ + popt.c \ + poptconfig.c \ + popthelp.c \ + poptparse.c + +LOCAL_CFLAGS += -DHAVE_CONFIG_H + +LOCAL_MODULE := libpopt + +include $(BUILD_STATIC_LIBRARY) diff --git a/libpopt/config.h b/libpopt/config.h new file mode 100644 index 0000000..73a0817 --- /dev/null +++ b/libpopt/config.h @@ -0,0 +1,329 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP + systems. This function is required for `alloca.c' support on those systems. + */ +#undef CRAY_STACKSEG_END + +/* Define to 1 if using `alloca.c'. */ +#undef C_ALLOCA + +/* Define to 1 if translation of program messages to the user's native + language is requested. */ +#undef ENABLE_NLS + +/* Define to 1 if you have `alloca', as a function or macro. */ +#undef HAVE_ALLOCA + +/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix). + */ +#undef HAVE_ALLOCA_H + +/* Define to 1 if you have the <argz.h> header file. */ +#undef HAVE_ARGZ_H + +/* Define to 1 if you have the `asprintf' function. */ +#undef HAVE_ASPRINTF + +/* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the + CoreFoundation framework. */ +#undef HAVE_CFLOCALECOPYCURRENT + +/* Define to 1 if you have the MacOS X function CFPreferencesCopyAppValue in + the CoreFoundation framework. */ +#undef HAVE_CFPREFERENCESCOPYAPPVALUE + +/* Define if the GNU dcgettext() function is already present or preinstalled. + */ +#undef HAVE_DCGETTEXT + +/* Define to 1 if you have the declaration of `feof_unlocked', and to 0 if you + don't. */ +#undef HAVE_DECL_FEOF_UNLOCKED + +/* Define to 1 if you have the declaration of `fgets_unlocked', and to 0 if + you don't. */ +#undef HAVE_DECL_FGETS_UNLOCKED + +/* Define to 1 if you have the declaration of `getc_unlocked', and to 0 if you + don't. */ +#undef HAVE_DECL_GETC_UNLOCKED + +/* Define to 1 if you have the declaration of `_snprintf', and to 0 if you + don't. */ +#undef HAVE_DECL__SNPRINTF + +/* Define to 1 if you have the declaration of `_snwprintf', and to 0 if you + don't. */ +#undef HAVE_DECL__SNWPRINTF + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the <float.h> header file. */ +#define HAVE_FLOAT_H 1 + +/* Define to 1 if you have the `fwprintf' function. */ +#undef HAVE_FWPRINTF + +/* Define to 1 if you have the `getcwd' function. */ +#undef HAVE_GETCWD + +/* Define to 1 if you have the `getegid' function. */ +#undef HAVE_GETEGID + +/* Define to 1 if you have the `geteuid' function. */ +#undef HAVE_GETEUID + +/* Define to 1 if you have the `getgid' function. */ +#undef HAVE_GETGID + +/* Define to 1 if you have the `getpagesize' function. */ +#undef HAVE_GETPAGESIZE + +/* Define if the GNU gettext() function is already present or preinstalled. */ +#undef HAVE_GETTEXT + +/* Define to 1 if you have the `getuid' function. */ +#undef HAVE_GETUID + +/* Define if you have the iconv() function. */ +#undef HAVE_ICONV + +/* Define if you have the 'intmax_t' type in <stdint.h> or <inttypes.h>. */ +#undef HAVE_INTMAX_T + +/* Define if <inttypes.h> exists and doesn't clash with <sys/types.h>. */ +#define HAVE_INTTYPES_H 1 + +/* Define if <inttypes.h> exists, doesn't clash with <sys/types.h>, and + declares uintmax_t. */ +#undef HAVE_INTTYPES_H_WITH_UINTMAX + +/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */ +#undef HAVE_LANGINFO_CODESET + +/* Define if your <locale.h> file defines LC_MESSAGES. */ +#undef HAVE_LC_MESSAGES + +/* Define to 1 if you have the <libintl.h> header file. */ +#undef HAVE_LIBINTL_H + +/* Define to 1 if you have the <limits.h> header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the <locale.h> header file. */ +#undef HAVE_LOCALE_H + +/* Define if you have the 'long double' type. */ +#undef HAVE_LONG_DOUBLE + +/* Define if you have the 'long long' type. */ +#define HAVE_LONG_LONG 1 + +/* Define to 1 if you have the <malloc.h> header file. */ +#define HAVE_MALLOC_H 1 + +/* Define to 1 if you have the <mcheck.h> header file. */ +#undef HAVE_MCHECK_H + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mempcpy' function. */ +#define HAVE_MEMPCPY 1 + +/* Define to 1 if you have a working `mmap' system call. */ +#define HAVE_MMAP 1 + +/* Define to 1 if you have the `mtrace' function. */ +#undef HAVE_MTRACE + +/* Define to 1 if you have the `munmap' function. */ +#define HAVE_MUNMAP 1 + +/* Define to 1 if you have the <nl_types.h> header file. */ +#undef HAVE_NL_TYPES_H + +/* Define if your printf() function supports format strings with positions. */ +#define HAVE_POSIX_PRINTF 1 + +/* Define to 1 if you have the `putenv' function. */ +#undef HAVE_PUTENV + +/* Define to 1 if you have the `setenv' function. */ +#undef HAVE_SETENV + +/* Define to 1 if you have the `setlocale' function. */ +#undef HAVE_SETLOCALE + +/* Define to 1 if you have the `setregid' function. */ +#undef HAVE_SETREGID + +/* Define to 1 if you have the `snprintf' function. */ +#define HAVE_SNPRINTF 1 + +/* Define to 1 if you have the <stddef.h> header file. */ +#define HAVE_STDDEF_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define if <stdint.h> exists, doesn't clash with <sys/types.h>, and declares + uintmax_t. */ +#undef HAVE_STDINT_H_WITH_UINTMAX + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `stpcpy' function. */ +#undef HAVE_STPCPY + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strtoul' function. */ +#undef HAVE_STRTOUL + +/* Define to 1 if you have the <sys/param.h> header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the `tsearch' function. */ +#undef HAVE_TSEARCH + +/* Define if you have the 'uintmax_t' type in <stdint.h> or <inttypes.h>. */ +#undef HAVE_UINTMAX_T + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define if you have the 'unsigned long long' type. */ +#undef HAVE_UNSIGNED_LONG_LONG + +/* Define if you have the 'wchar_t' type. */ +#undef HAVE_WCHAR_T + +/* Define to 1 if you have the `wcslen' function. */ +#undef HAVE_WCSLEN + +/* Define if you have the 'wint_t' type. */ +#undef HAVE_WINT_T + +/* Define to 1 if you have the `__argz_count' function. */ +#undef HAVE___ARGZ_COUNT + +/* Define to 1 if you have the `__argz_next' function. */ +#undef HAVE___ARGZ_NEXT + +/* Define to 1 if you have the `__argz_stringify' function. */ +#undef HAVE___ARGZ_STRINGIFY + +/* Define to 1 if you have the `__fsetlocking' function. */ +#undef HAVE___FSETLOCKING + +/* Define to 1 if you have the `__secure_getenv' function. */ +#undef HAVE___SECURE_GETENV + +/* Define as const if the declaration of iconv() needs const. */ +#undef ICONV_CONST + +/* Define if integer division by zero raises signal SIGFPE. */ +#undef INTDIV0_RAISES_SIGFPE + +/* Name of package */ +#define PACKAGE "popt" + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Full path to popt top_srcdir. */ +#undef POPT_SOURCE_PATH + +/* Define if <inttypes.h> exists and defines unusable PRI* macros. */ +#undef PRI_MACROS_BROKEN + +/* Define to 1 if the C compiler supports function prototypes. */ +#undef PROTOTYPES + +/* Define as the maximum value of type 'size_t', if the system doesn't define + it. */ +#undef SIZE_MAX + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at run-time. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown */ +#undef STACK_DIRECTION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Number of bits in a file offset, on hosts where this is settable. */ +#undef _FILE_OFFSET_BITS + +/* Define for large files, on AIX-style hosts. */ +#undef _LARGE_FILES + +/* Define like PROTOTYPES; this can be used by system headers. */ +#undef __PROTOTYPES + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `long' if <sys/types.h> does not define. */ +#undef off_t + +/* Define as the type of the result of subtracting two pointers, if the system + doesn't define it. */ +#undef ptrdiff_t + +/* Define to empty if the C compiler doesn't support this keyword. */ +#undef signed + +/* Define to `unsigned' if <sys/types.h> does not define. */ +#undef size_t + +/* Define to unsigned long or unsigned long long if <stdint.h> and + <inttypes.h> don't define. */ +#undef uintmax_t diff --git a/libpopt/findme.c b/libpopt/findme.c new file mode 100644 index 0000000..78363e5 --- /dev/null +++ b/libpopt/findme.c @@ -0,0 +1,52 @@ +/** \ingroup popt + * \file popt/findme.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" +#include "findme.h" +#include <unistd.h> + +const char * findProgramPath(const char * argv0) +{ + char * path = getenv("PATH"); + char * pathbuf; + char * start, * chptr; + char * buf; + + if (argv0 == NULL) return NULL; /* XXX can't happen */ + /* If there is a / in the argv[0], it has to be an absolute path */ + if (strchr(argv0, '/')) + return xstrdup(argv0); + + if (path == NULL) return NULL; + + start = pathbuf = alloca(strlen(path) + 1); + buf = malloc(strlen(path) + strlen(argv0) + sizeof("/")); + if (buf == NULL) return NULL; /* XXX can't happen */ + strcpy(pathbuf, path); + + chptr = NULL; + /*@-branchstate@*/ + do { + if ((chptr = strchr(start, ':'))) + *chptr = '\0'; + sprintf(buf, "%s/%s", start, argv0); + + if (!access(buf, X_OK)) + return buf; + + if (chptr) + start = chptr + 1; + else + start = NULL; + } while (start && *start); + /*@=branchstate@*/ + + free(buf); + + return NULL; +} diff --git a/libpopt/findme.h b/libpopt/findme.h new file mode 100644 index 0000000..a016b86 --- /dev/null +++ b/libpopt/findme.h @@ -0,0 +1,20 @@ +/** \ingroup popt + * \file popt/findme.h + */ + +/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#ifndef H_FINDME +#define H_FINDME + +/** + * Return absolute path to executable by searching PATH. + * @param argv0 name of executable + * @return (malloc'd) absolute path to executable (or NULL) + */ +/*@null@*/ const char * findProgramPath(/*@null@*/ const char * argv0) + /*@*/; + +#endif diff --git a/libpopt/popt.c b/libpopt/popt.c new file mode 100644 index 0000000..4f9e325 --- /dev/null +++ b/libpopt/popt.c @@ -0,0 +1,1262 @@ +/** \ingroup popt + * \file popt/popt.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist */ + +#undef MYDEBUG + +#include "system.h" + +#if HAVE_FLOAT_H +#include <float.h> +#endif +#include <math.h> + +#include "findme.h" +#include "poptint.h" + +#ifdef MYDEBUG +/*@unchecked@*/ +int _popt_debug = 0; +#endif + +#if !defined(HAVE_STRERROR) && !defined(__LCLINT__) +static char * strerror(int errno) +{ + extern int sys_nerr; + extern char * sys_errlist[]; + + if ((0 <= errno) && (errno < sys_nerr)) + return sys_errlist[errno]; + else + return POPT_("unknown errno"); +} +#endif + +#ifdef MYDEBUG +/*@unused@*/ +static void prtcon(const char *msg, poptContext con) +{ + if (msg) fprintf(stderr, "%s", msg); + fprintf(stderr, "\tcon %p os %p nextCharArg \"%s\" nextArg \"%s\" argv[%d] \"%s\"\n", + con, con->os, + (con->os->nextCharArg ? con->os->nextCharArg : ""), + (con->os->nextArg ? con->os->nextArg : ""), + con->os->next, + (con->os->argv && con->os->argv[con->os->next] + ? con->os->argv[con->os->next] : "")); +} +#endif + +void poptSetExecPath(poptContext con, const char * path, int allowAbsolute) +{ + con->execPath = _free(con->execPath); + con->execPath = xstrdup(path); + con->execAbsolute = allowAbsolute; + /*@-nullstate@*/ /* LCL: con->execPath not NULL */ + return; + /*@=nullstate@*/ +} + +static void invokeCallbacksPRE(poptContext con, const struct poptOption * opt) + /*@globals internalState@*/ + /*@modifies internalState@*/ +{ + if (opt != NULL) + for (; opt->longName || opt->shortName || opt->arg; opt++) { + if (opt->arg == NULL) continue; /* XXX program error. */ + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + void * arg = opt->arg; +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + invokeCallbacksPRE(con, arg); + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK && + (opt->argInfo & POPT_CBFLAG_PRE)) + { /*@-castfcnptr@*/ + poptCallbackType cb = (poptCallbackType)opt->arg; + /*@=castfcnptr@*/ + /* Perform callback. */ + /*@-noeffectuncon @*/ + cb(con, POPT_CALLBACK_REASON_PRE, NULL, NULL, opt->descrip); + /*@=noeffectuncon @*/ + } + } +} + +static void invokeCallbacksPOST(poptContext con, const struct poptOption * opt) + /*@globals internalState@*/ + /*@modifies internalState@*/ +{ + if (opt != NULL) + for (; opt->longName || opt->shortName || opt->arg; opt++) { + if (opt->arg == NULL) continue; /* XXX program error. */ + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + void * arg = opt->arg; +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + invokeCallbacksPOST(con, arg); + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK && + (opt->argInfo & POPT_CBFLAG_POST)) + { /*@-castfcnptr@*/ + poptCallbackType cb = (poptCallbackType)opt->arg; + /*@=castfcnptr@*/ + /* Perform callback. */ + /*@-noeffectuncon @*/ + cb(con, POPT_CALLBACK_REASON_POST, NULL, NULL, opt->descrip); + /*@=noeffectuncon @*/ + } + } +} + +static void invokeCallbacksOPTION(poptContext con, + const struct poptOption * opt, + const struct poptOption * myOpt, + /*@null@*/ const void * myData, int shorty) + /*@globals internalState@*/ + /*@modifies internalState@*/ +{ + const struct poptOption * cbopt = NULL; + + if (opt != NULL) + for (; opt->longName || opt->shortName || opt->arg; opt++) { + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + void * arg = opt->arg; +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + if (opt->arg != NULL) /* XXX program error */ + invokeCallbacksOPTION(con, opt->arg, myOpt, myData, shorty); + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK && + !(opt->argInfo & POPT_CBFLAG_SKIPOPTION)) { + /* Save callback info. */ + cbopt = opt; + } else if (cbopt != NULL && + ((myOpt->shortName && opt->shortName && shorty && + myOpt->shortName == opt->shortName) || + (myOpt->longName && opt->longName && + /*@-nullpass@*/ /* LCL: opt->longName != NULL */ + !strcmp(myOpt->longName, opt->longName))) + /*@=nullpass@*/ + ) + { /*@-castfcnptr@*/ + poptCallbackType cb = (poptCallbackType)cbopt->arg; + /*@=castfcnptr@*/ + const void * cbData = (cbopt->descrip ? cbopt->descrip : myData); + /* Perform callback. */ + if (cb != NULL) { /* XXX program error */ + /*@-noeffectuncon @*/ + cb(con, POPT_CALLBACK_REASON_OPTION, myOpt, + con->os->nextArg, cbData); + /*@=noeffectuncon @*/ + } + /* Terminate (unless explcitly continuing). */ + if (!(cbopt->argInfo & POPT_CBFLAG_CONTINUE)) + return; + } + } +} + +poptContext poptGetContext(const char * name, int argc, const char ** argv, + const struct poptOption * options, int flags) +{ + poptContext con = malloc(sizeof(*con)); + + if (con == NULL) return NULL; /* XXX can't happen */ + memset(con, 0, sizeof(*con)); + + con->os = con->optionStack; + con->os->argc = argc; + /*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */ + con->os->argv = argv; + /*@=dependenttrans =assignexpose@*/ + con->os->argb = NULL; + + if (!(flags & POPT_CONTEXT_KEEP_FIRST)) + con->os->next = 1; /* skip argv[0] */ + + con->leftovers = calloc( (argc + 1), sizeof(*con->leftovers) ); + /*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */ + con->options = options; + /*@=dependenttrans =assignexpose@*/ + con->aliases = NULL; + con->numAliases = 0; + con->flags = flags; + con->execs = NULL; + con->numExecs = 0; + con->finalArgvAlloced = argc * 2; + con->finalArgv = calloc( con->finalArgvAlloced, sizeof(*con->finalArgv) ); + con->execAbsolute = 1; + con->arg_strip = NULL; + + if (getenv("POSIXLY_CORRECT") || getenv("POSIX_ME_HARDER")) + con->flags |= POPT_CONTEXT_POSIXMEHARDER; + + if (name) { + char * t = malloc(strlen(name) + 1); + if (t) con->appName = strcpy(t, name); + } + + /*@-internalglobs@*/ + invokeCallbacksPRE(con, con->options); + /*@=internalglobs@*/ + + return con; +} + +static void cleanOSE(/*@special@*/ struct optionStackEntry *os) + /*@uses os @*/ + /*@releases os->nextArg, os->argv, os->argb @*/ + /*@modifies os @*/ +{ + os->nextArg = _free(os->nextArg); + os->argv = _free(os->argv); + os->argb = PBM_FREE(os->argb); +} + +/*@-boundswrite@*/ +void poptResetContext(poptContext con) +{ + int i; + + if (con == NULL) return; + while (con->os > con->optionStack) { + cleanOSE(con->os--); + } + con->os->argb = PBM_FREE(con->os->argb); + con->os->currAlias = NULL; + con->os->nextCharArg = NULL; + con->os->nextArg = NULL; + con->os->next = 1; /* skip argv[0] */ + + con->numLeftovers = 0; + con->nextLeftover = 0; + con->restLeftover = 0; + con->doExec = NULL; + + if (con->finalArgv != NULL) + for (i = 0; i < con->finalArgvCount; i++) { + /*@-unqualifiedtrans@*/ /* FIX: typedef double indirection. */ + con->finalArgv[i] = _free(con->finalArgv[i]); + /*@=unqualifiedtrans@*/ + } + + con->finalArgvCount = 0; + con->arg_strip = PBM_FREE(con->arg_strip); + /*@-nullstate@*/ /* FIX: con->finalArgv != NULL */ + return; + /*@=nullstate@*/ +} +/*@=boundswrite@*/ + +/* Only one of longName, shortName should be set, not both. */ +/*@-boundswrite@*/ +static int handleExec(/*@special@*/ poptContext con, + /*@null@*/ const char * longName, char shortName) + /*@uses con->execs, con->numExecs, con->flags, con->doExec, + con->finalArgv, con->finalArgvAlloced, con->finalArgvCount @*/ + /*@modifies con @*/ +{ + poptItem item; + int i; + + if (con->execs == NULL || con->numExecs <= 0) /* XXX can't happen */ + return 0; + + for (i = con->numExecs - 1; i >= 0; i--) { + item = con->execs + i; + if (longName && !(item->option.longName && + !strcmp(longName, item->option.longName))) + continue; + else if (shortName != item->option.shortName) + continue; + break; + } + if (i < 0) return 0; + + + if (con->flags & POPT_CONTEXT_NO_EXEC) + return 1; + + if (con->doExec == NULL) { + con->doExec = con->execs + i; + return 1; + } + + /* We already have an exec to do; remember this option for next + time 'round */ + if ((con->finalArgvCount + 1) >= (con->finalArgvAlloced)) { + con->finalArgvAlloced += 10; + con->finalArgv = realloc(con->finalArgv, + sizeof(*con->finalArgv) * con->finalArgvAlloced); + } + + i = con->finalArgvCount++; + if (con->finalArgv != NULL) /* XXX can't happen */ + { char *s = malloc((longName ? strlen(longName) : 0) + 3); + if (s != NULL) { /* XXX can't happen */ + if (longName) + sprintf(s, "--%s", longName); + else + sprintf(s, "-%c", shortName); + con->finalArgv[i] = s; + } else + con->finalArgv[i] = NULL; + } + + /*@-nullstate@*/ /* FIX: con->finalArgv[] == NULL */ + return 1; + /*@=nullstate@*/ +} +/*@=boundswrite@*/ + +/* Only one of longName, shortName may be set at a time */ +static int handleAlias(/*@special@*/ poptContext con, + /*@null@*/ const char * longName, char shortName, + /*@exposed@*/ /*@null@*/ const char * nextCharArg) + /*@uses con->aliases, con->numAliases, con->optionStack, con->os, + con->os->currAlias, con->os->currAlias->option.longName @*/ + /*@modifies con @*/ +{ + poptItem item = con->os->currAlias; + int rc; + int i; + + if (item) { + if (longName && (item->option.longName && + !strcmp(longName, item->option.longName))) + return 0; + if (shortName && shortName == item->option.shortName) + return 0; + } + + if (con->aliases == NULL || con->numAliases <= 0) /* XXX can't happen */ + return 0; + + for (i = con->numAliases - 1; i >= 0; i--) { + item = con->aliases + i; + if (longName && !(item->option.longName && + !strcmp(longName, item->option.longName))) + continue; + else if (shortName != item->option.shortName) + continue; + break; + } + if (i < 0) return 0; + + if ((con->os - con->optionStack + 1) == POPT_OPTION_DEPTH) + return POPT_ERROR_OPTSTOODEEP; + +/*@-boundsread@*/ + if (nextCharArg && *nextCharArg) + con->os->nextCharArg = nextCharArg; +/*@=boundsread@*/ + + con->os++; + con->os->next = 0; + con->os->stuffed = 0; + con->os->nextArg = NULL; + con->os->nextCharArg = NULL; + con->os->currAlias = con->aliases + i; + rc = poptDupArgv(con->os->currAlias->argc, con->os->currAlias->argv, + &con->os->argc, &con->os->argv); + con->os->argb = NULL; + + return (rc ? rc : 1); +} + +/*@-bounds -boundswrite @*/ +static int execCommand(poptContext con) + /*@globals internalState @*/ + /*@modifies internalState @*/ +{ + poptItem item = con->doExec; + const char ** argv; + int argc = 0; + int rc; + + if (item == NULL) /*XXX can't happen*/ + return POPT_ERROR_NOARG; + + if (item->argv == NULL || item->argc < 1 || + (!con->execAbsolute && strchr(item->argv[0], '/'))) + return POPT_ERROR_NOARG; + + argv = malloc(sizeof(*argv) * + (6 + item->argc + con->numLeftovers + con->finalArgvCount)); + if (argv == NULL) return POPT_ERROR_MALLOC; + + if (!strchr(item->argv[0], '/') && con->execPath != NULL) { + char *s = alloca(strlen(con->execPath) + strlen(item->argv[0]) + sizeof("/")); + sprintf(s, "%s/%s", con->execPath, item->argv[0]); + argv[argc] = s; + } else + argv[argc] = findProgramPath(item->argv[0]); + if (argv[argc++] == NULL) return POPT_ERROR_NOARG; + + if (item->argc > 1) { + memcpy(argv + argc, item->argv + 1, sizeof(*argv) * (item->argc - 1)); + argc += (item->argc - 1); + } + + if (con->finalArgv != NULL && con->finalArgvCount > 0) { + memcpy(argv + argc, con->finalArgv, + sizeof(*argv) * con->finalArgvCount); + argc += con->finalArgvCount; + } + + if (con->leftovers != NULL && con->numLeftovers > 0) { + memcpy(argv + argc, con->leftovers, sizeof(*argv) * con->numLeftovers); + argc += con->numLeftovers; + } + + argv[argc] = NULL; + +#if defined(hpux) || defined(__hpux) + rc = setresgid(getgid(), getgid(),-1); + if (rc) return POPT_ERROR_ERRNO; + rc = setresuid(getuid(), getuid(),-1); + if (rc) return POPT_ERROR_ERRNO; +#else +/* + * XXX " ... on BSD systems setuid() should be preferred over setreuid()" + * XXX sez' Timur Bakeyev <mc@bat.ru> + * XXX from Norbert Warmuth <nwarmuth@privat.circular.de> + */ +#if defined(HAVE_SETUID) + rc = setgid(getgid()); + if (rc) return POPT_ERROR_ERRNO; + rc = setuid(getuid()); + if (rc) return POPT_ERROR_ERRNO; +#elif defined (HAVE_SETREUID) + rc = setregid(getgid(), getgid()); + if (rc) return POPT_ERROR_ERRNO; + rc = setreuid(getuid(), getuid()); + if (rc) return POPT_ERROR_ERRNO; +#else + ; /* Can't drop privileges */ +#endif +#endif + + if (argv[0] == NULL) + return POPT_ERROR_NOARG; + +#ifdef MYDEBUG +if (_popt_debug) + { const char ** avp; + fprintf(stderr, "==> execvp(%s) argv[%d]:", argv[0], argc); + for (avp = argv; *avp; avp++) + fprintf(stderr, " '%s'", *avp); + fprintf(stderr, "\n"); + } +#endif + + rc = execvp(argv[0], (char *const *)argv); + + return POPT_ERROR_ERRNO; +} +/*@=bounds =boundswrite @*/ + +/*@-boundswrite@*/ +/*@observer@*/ /*@null@*/ static const struct poptOption * +findOption(const struct poptOption * opt, /*@null@*/ const char * longName, + char shortName, + /*@null@*/ /*@out@*/ poptCallbackType * callback, + /*@null@*/ /*@out@*/ const void ** callbackData, + int singleDash) + /*@modifies *callback, *callbackData */ +{ + const struct poptOption * cb = NULL; + + /* This happens when a single - is given */ + if (singleDash && !shortName && (longName && *longName == '\0')) + shortName = '-'; + + for (; opt->longName || opt->shortName || opt->arg; opt++) { + + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + const struct poptOption * opt2; + void * arg = opt->arg; + +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + if (arg == NULL) continue; /* XXX program error */ + opt2 = findOption(arg, longName, shortName, callback, + callbackData, singleDash); + if (opt2 == NULL) continue; + /* Sub-table data will be inheirited if no data yet. */ + if (!(callback && *callback)) return opt2; + if (!(callbackData && *callbackData == NULL)) return opt2; + /*@-observertrans -dependenttrans @*/ + *callbackData = opt->descrip; + /*@=observertrans =dependenttrans @*/ + return opt2; + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK) { + cb = opt; + } else if (longName && opt->longName && + (!singleDash || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) && + /*@-nullpass@*/ /* LCL: opt->longName != NULL */ + !strcmp(longName, opt->longName)) + /*@=nullpass@*/ + { + break; + } else if (shortName && shortName == opt->shortName) { + break; + } + } + + if (!opt->longName && !opt->shortName) + return NULL; + /*@-modobserver -mods @*/ + if (callback) *callback = NULL; + if (callbackData) *callbackData = NULL; + if (cb) { + if (callback) + /*@-castfcnptr@*/ + *callback = (poptCallbackType)cb->arg; + /*@=castfcnptr@*/ + if (!(cb->argInfo & POPT_CBFLAG_INC_DATA)) { + if (callbackData) + /*@-observertrans@*/ /* FIX: typedef double indirection. */ + *callbackData = cb->descrip; + /*@=observertrans@*/ + } + } + /*@=modobserver =mods @*/ + + return opt; +} +/*@=boundswrite@*/ + +static const char * findNextArg(/*@special@*/ poptContext con, + unsigned argx, int delete_arg) + /*@uses con->optionStack, con->os, + con->os->next, con->os->argb, con->os->argc, con->os->argv @*/ + /*@modifies con @*/ +{ + struct optionStackEntry * os = con->os; + const char * arg; + + do { + int i; + arg = NULL; + while (os->next == os->argc && os > con->optionStack) os--; + if (os->next == os->argc && os == con->optionStack) break; + if (os->argv != NULL) + for (i = os->next; i < os->argc; i++) { + /*@-sizeoftype@*/ + if (os->argb && PBM_ISSET(i, os->argb)) + /*@innercontinue@*/ continue; + if (*os->argv[i] == '-') + /*@innercontinue@*/ continue; + if (--argx > 0) + /*@innercontinue@*/ continue; + arg = os->argv[i]; + if (delete_arg) { + if (os->argb == NULL) os->argb = PBM_ALLOC(os->argc); + if (os->argb != NULL) /* XXX can't happen */ + PBM_SET(i, os->argb); + } + /*@innerbreak@*/ break; + /*@=sizeoftype@*/ + } + if (os > con->optionStack) os--; + } while (arg == NULL); + return arg; +} + +/*@-boundswrite@*/ +static /*@only@*/ /*@null@*/ const char * +expandNextArg(/*@special@*/ poptContext con, const char * s) + /*@uses con->optionStack, con->os, + con->os->next, con->os->argb, con->os->argc, con->os->argv @*/ + /*@modifies con @*/ +{ + const char * a = NULL; + size_t alen; + char *t, *te; + size_t tn = strlen(s) + 1; + char c; + + te = t = malloc(tn);; + if (t == NULL) return NULL; /* XXX can't happen */ + while ((c = *s++) != '\0') { + switch (c) { +#if 0 /* XXX can't do this */ + case '\\': /* escape */ + c = *s++; + /*@switchbreak@*/ break; +#endif + case '!': + if (!(s[0] == '#' && s[1] == ':' && s[2] == '+')) + /*@switchbreak@*/ break; + /* XXX Make sure that findNextArg deletes only next arg. */ + if (a == NULL) { + if ((a = findNextArg(con, 1, 1)) == NULL) + /*@switchbreak@*/ break; + } + s += 3; + + alen = strlen(a); + tn += alen; + *te = '\0'; + t = realloc(t, tn); + te = t + strlen(t); + strncpy(te, a, alen); te += alen; + continue; + /*@notreached@*/ /*@switchbreak@*/ break; + default: + /*@switchbreak@*/ break; + } + *te++ = c; + } + *te = '\0'; + t = realloc(t, strlen(t) + 1); /* XXX memory leak, hard to plug */ + return t; +} +/*@=boundswrite@*/ + +static void poptStripArg(/*@special@*/ poptContext con, int which) + /*@uses con->arg_strip, con->optionStack @*/ + /*@defines con->arg_strip @*/ + /*@modifies con @*/ +{ + /*@-sizeoftype@*/ + if (con->arg_strip == NULL) + con->arg_strip = PBM_ALLOC(con->optionStack[0].argc); + if (con->arg_strip != NULL) /* XXX can't happen */ + PBM_SET(which, con->arg_strip); + /*@=sizeoftype@*/ + /*@-compdef@*/ /* LCL: con->arg_strip udefined? */ + return; + /*@=compdef@*/ +} + +int poptSaveLong(long * arg, int argInfo, long aLong) +{ + /* XXX Check alignment, may fail on funky platforms. */ + if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1))) + return POPT_ERROR_NULLARG; + + if (argInfo & POPT_ARGFLAG_NOT) + aLong = ~aLong; + switch (argInfo & POPT_ARGFLAG_LOGICALOPS) { + case 0: + *arg = aLong; + break; + case POPT_ARGFLAG_OR: + *arg |= aLong; + break; + case POPT_ARGFLAG_AND: + *arg &= aLong; + break; + case POPT_ARGFLAG_XOR: + *arg ^= aLong; + break; + default: + return POPT_ERROR_BADOPERATION; + /*@notreached@*/ break; + } + return 0; +} + +int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong) +{ + /* XXX Check alignment, may fail on funky platforms. */ + if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1))) + return POPT_ERROR_NULLARG; + + if (argInfo & POPT_ARGFLAG_NOT) + aLong = ~aLong; + switch (argInfo & POPT_ARGFLAG_LOGICALOPS) { + case 0: + *arg = aLong; + break; + case POPT_ARGFLAG_OR: + *arg |= aLong; + break; + case POPT_ARGFLAG_AND: + *arg &= aLong; + break; + case POPT_ARGFLAG_XOR: + *arg ^= aLong; + break; + default: + return POPT_ERROR_BADOPERATION; + /*@notreached@*/ break; + } + return 0; +} + +/*@-boundswrite@*/ +/* returns 'val' element, -1 on last item, POPT_ERROR_* on error */ +int poptGetNextOpt(poptContext con) +{ + const struct poptOption * opt = NULL; + int done = 0; + + if (con == NULL) + return -1; + while (!done) { + const char * origOptString = NULL; + poptCallbackType cb = NULL; + const void * cbData = NULL; + const char * longArg = NULL; + int canstrip = 0; + int shorty = 0; + + while (!con->os->nextCharArg && con->os->next == con->os->argc + && con->os > con->optionStack) { + cleanOSE(con->os--); + } + if (!con->os->nextCharArg && con->os->next == con->os->argc) { + /*@-internalglobs@*/ + invokeCallbacksPOST(con, con->options); + /*@=internalglobs@*/ + if (con->doExec) return execCommand(con); + return -1; + } + + /* Process next long option */ + if (!con->os->nextCharArg) { + char * localOptString, * optString; + int thisopt; + + /*@-sizeoftype@*/ + if (con->os->argb && PBM_ISSET(con->os->next, con->os->argb)) { + con->os->next++; + continue; + } + /*@=sizeoftype@*/ + thisopt = con->os->next; + if (con->os->argv != NULL) /* XXX can't happen */ + origOptString = con->os->argv[con->os->next++]; + + if (origOptString == NULL) /* XXX can't happen */ + return POPT_ERROR_BADOPT; + + if (con->restLeftover || *origOptString != '-' || + (*origOptString == '-' && origOptString[1] == '\0')) + { + if (con->flags & POPT_CONTEXT_POSIXMEHARDER) + con->restLeftover = 1; + if (con->flags & POPT_CONTEXT_ARG_OPTS) { + con->os->nextArg = xstrdup(origOptString); + return 0; + } + if (con->leftovers != NULL) /* XXX can't happen */ + con->leftovers[con->numLeftovers++] = origOptString; + continue; + } + + /* Make a copy we can hack at */ + localOptString = optString = + strcpy(alloca(strlen(origOptString) + 1), origOptString); + + if (optString[0] == '\0') + return POPT_ERROR_BADOPT; + + if (optString[1] == '-' && !optString[2]) { + con->restLeftover = 1; + continue; + } else { + char *oe; + int singleDash; + + optString++; + if (*optString == '-') + singleDash = 0, optString++; + else + singleDash = 1; + + /* XXX aliases with arg substitution need "--alias=arg" */ + if (handleAlias(con, optString, '\0', NULL)) + continue; + + if (handleExec(con, optString, '\0')) + continue; + + /* Check for "--long=arg" option. */ + for (oe = optString; *oe && *oe != '='; oe++) + {}; + if (*oe == '=') { + *oe++ = '\0'; + /* XXX longArg is mapped back to persistent storage. */ + longArg = origOptString + (oe - localOptString); + } + + opt = findOption(con->options, optString, '\0', &cb, &cbData, + singleDash); + if (!opt && !singleDash) + return POPT_ERROR_BADOPT; + } + + if (!opt) { + con->os->nextCharArg = origOptString + 1; + } else { + if (con->os == con->optionStack && + opt->argInfo & POPT_ARGFLAG_STRIP) + { + canstrip = 1; + poptStripArg(con, thisopt); + } + shorty = 0; + } + } + + /* Process next short option */ + /*@-branchstate@*/ /* FIX: W2DO? */ + if (con->os->nextCharArg) { + origOptString = con->os->nextCharArg; + + con->os->nextCharArg = NULL; + + if (handleAlias(con, NULL, *origOptString, origOptString + 1)) + continue; + + if (handleExec(con, NULL, *origOptString)) { + /* Restore rest of short options for further processing */ + origOptString++; + if (*origOptString != '\0') + con->os->nextCharArg = origOptString; + continue; + } + + opt = findOption(con->options, NULL, *origOptString, &cb, + &cbData, 0); + if (!opt) + return POPT_ERROR_BADOPT; + shorty = 1; + + origOptString++; + if (*origOptString != '\0') + con->os->nextCharArg = origOptString; + } + /*@=branchstate@*/ + + if (opt == NULL) return POPT_ERROR_BADOPT; /* XXX can't happen */ + if (opt->arg && (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE) { + if (poptSaveInt((int *)opt->arg, opt->argInfo, 1L)) + return POPT_ERROR_BADOPERATION; + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL) { + if (opt->arg) { + if (poptSaveInt((int *)opt->arg, opt->argInfo, (long)opt->val)) + return POPT_ERROR_BADOPERATION; + } + } else if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_NONE) { + con->os->nextArg = _free(con->os->nextArg); + /*@-usedef@*/ /* FIX: W2DO? */ + if (longArg) { + /*@=usedef@*/ + longArg = expandNextArg(con, longArg); + con->os->nextArg = longArg; + } else if (con->os->nextCharArg) { + longArg = expandNextArg(con, con->os->nextCharArg); + con->os->nextArg = longArg; + con->os->nextCharArg = NULL; + } else { + while (con->os->next == con->os->argc && + con->os > con->optionStack) { + cleanOSE(con->os--); + } + if (con->os->next == con->os->argc) { + if (!(opt->argInfo & POPT_ARGFLAG_OPTIONAL)) + /*@-compdef@*/ /* FIX: con->os->argv not defined */ + return POPT_ERROR_NOARG; + /*@=compdef@*/ + con->os->nextArg = NULL; + } else { + + /* + * Make sure this isn't part of a short arg or the + * result of an alias expansion. + */ + if (con->os == con->optionStack && + (opt->argInfo & POPT_ARGFLAG_STRIP) && + canstrip) { + poptStripArg(con, con->os->next); + } + + if (con->os->argv != NULL) { /* XXX can't happen */ + /* XXX watchout: subtle side-effects live here. */ + longArg = con->os->argv[con->os->next++]; + longArg = expandNextArg(con, longArg); + con->os->nextArg = longArg; + } + } + } + longArg = NULL; + + if (opt->arg) { + switch (opt->argInfo & POPT_ARG_MASK) { + case POPT_ARG_STRING: + /* XXX memory leak, hard to plug */ + *((const char **) opt->arg) = (con->os->nextArg) + ? xstrdup(con->os->nextArg) : NULL; + /*@switchbreak@*/ break; + + case POPT_ARG_INT: + case POPT_ARG_LONG: + { long aLong = 0; + char *end; + + if (con->os->nextArg) { + aLong = strtol(con->os->nextArg, &end, 0); + if (!(end && *end == '\0')) + return POPT_ERROR_BADNUMBER; + } + + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_LONG) { + if (aLong == LONG_MIN || aLong == LONG_MAX) + return POPT_ERROR_OVERFLOW; + if (poptSaveLong((long *)opt->arg, opt->argInfo, aLong)) + return POPT_ERROR_BADOPERATION; + } else { + if (aLong > INT_MAX || aLong < INT_MIN) + return POPT_ERROR_OVERFLOW; + if (poptSaveInt((int *)opt->arg, opt->argInfo, aLong)) + return POPT_ERROR_BADOPERATION; + } + } /*@switchbreak@*/ break; + + case POPT_ARG_FLOAT: + case POPT_ARG_DOUBLE: + { double aDouble = 0.0; + char *end; + + if (con->os->nextArg) { + /*@-mods@*/ + int saveerrno = errno; + errno = 0; + aDouble = strtod(con->os->nextArg, &end); + if (errno == ERANGE) + return POPT_ERROR_OVERFLOW; + errno = saveerrno; + /*@=mods@*/ + if (*end != '\0') + return POPT_ERROR_BADNUMBER; + } + + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_DOUBLE) { + *((double *) opt->arg) = aDouble; + } else { +#define _ABS(a) ((((a) - 0.0) < DBL_EPSILON) ? -(a) : (a)) + if ((_ABS(aDouble) - FLT_MAX) > DBL_EPSILON) + return POPT_ERROR_OVERFLOW; + if ((FLT_MIN - _ABS(aDouble)) > DBL_EPSILON) + return POPT_ERROR_OVERFLOW; + *((float *) opt->arg) = aDouble; + } + } /*@switchbreak@*/ break; + default: + fprintf(stdout, + POPT_("option type (%d) not implemented in popt\n"), + (opt->argInfo & POPT_ARG_MASK)); + exit(EXIT_FAILURE); + /*@notreached@*/ /*@switchbreak@*/ break; + } + } + } + + if (cb) { + /*@-internalglobs@*/ + invokeCallbacksOPTION(con, con->options, opt, cbData, shorty); + /*@=internalglobs@*/ + } else if (opt->val && ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_VAL)) + done = 1; + + if ((con->finalArgvCount + 2) >= (con->finalArgvAlloced)) { + con->finalArgvAlloced += 10; + con->finalArgv = realloc(con->finalArgv, + sizeof(*con->finalArgv) * con->finalArgvAlloced); + } + + if (con->finalArgv != NULL) + { char *s = malloc((opt->longName ? strlen(opt->longName) : 0) + 3); + if (s != NULL) { /* XXX can't happen */ + if (opt->longName) + sprintf(s, "%s%s", + ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"), + opt->longName); + else + sprintf(s, "-%c", opt->shortName); + con->finalArgv[con->finalArgvCount++] = s; + } else + con->finalArgv[con->finalArgvCount++] = NULL; + } + + if (opt->arg && (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE) + /*@-ifempty@*/ ; /*@=ifempty@*/ + else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL) + /*@-ifempty@*/ ; /*@=ifempty@*/ + else if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_NONE) { + if (con->finalArgv != NULL && con->os->nextArg) + con->finalArgv[con->finalArgvCount++] = + /*@-nullpass@*/ /* LCL: con->os->nextArg != NULL */ + xstrdup(con->os->nextArg); + /*@=nullpass@*/ + } + } + + return (opt ? opt->val : -1); /* XXX can't happen */ +} +/*@=boundswrite@*/ + +const char * poptGetOptArg(poptContext con) +{ + const char * ret = NULL; + /*@-branchstate@*/ + if (con) { + ret = con->os->nextArg; + con->os->nextArg = NULL; + } + /*@=branchstate@*/ + return ret; +} + +const char * poptGetArg(poptContext con) +{ + const char * ret = NULL; + if (con && con->leftovers != NULL && con->nextLeftover < con->numLeftovers) + ret = con->leftovers[con->nextLeftover++]; + return ret; +} + +const char * poptPeekArg(poptContext con) +{ + const char * ret = NULL; + if (con && con->leftovers != NULL && con->nextLeftover < con->numLeftovers) + ret = con->leftovers[con->nextLeftover]; + return ret; +} + +/*@-boundswrite@*/ +const char ** poptGetArgs(poptContext con) +{ + if (con == NULL || + con->leftovers == NULL || con->numLeftovers == con->nextLeftover) + return NULL; + + /* some apps like [like RPM ;-) ] need this NULL terminated */ + con->leftovers[con->numLeftovers] = NULL; + + /*@-nullret -nullstate @*/ /* FIX: typedef double indirection. */ + return (con->leftovers + con->nextLeftover); + /*@=nullret =nullstate @*/ +} +/*@=boundswrite@*/ + +poptContext poptFreeContext(poptContext con) +{ + poptItem item; + int i; + + if (con == NULL) return con; + poptResetContext(con); + con->os->argb = _free(con->os->argb); + + if (con->aliases != NULL) + for (i = 0; i < con->numAliases; i++) { + item = con->aliases + i; + /*@-modobserver -observertrans -dependenttrans@*/ + item->option.longName = _free(item->option.longName); + item->option.descrip = _free(item->option.descrip); + item->option.argDescrip = _free(item->option.argDescrip); + /*@=modobserver =observertrans =dependenttrans@*/ + item->argv = _free(item->argv); + } + con->aliases = _free(con->aliases); + + if (con->execs != NULL) + for (i = 0; i < con->numExecs; i++) { + item = con->execs + i; + /*@-modobserver -observertrans -dependenttrans@*/ + item->option.longName = _free(item->option.longName); + item->option.descrip = _free(item->option.descrip); + item->option.argDescrip = _free(item->option.argDescrip); + /*@=modobserver =observertrans =dependenttrans@*/ + item->argv = _free(item->argv); + } + con->execs = _free(con->execs); + + con->leftovers = _free(con->leftovers); + con->finalArgv = _free(con->finalArgv); + con->appName = _free(con->appName); + con->otherHelp = _free(con->otherHelp); + con->execPath = _free(con->execPath); + con->arg_strip = PBM_FREE(con->arg_strip); + + con = _free(con); + return con; +} + +int poptAddAlias(poptContext con, struct poptAlias alias, + /*@unused@*/ int flags) +{ + poptItem item = alloca(sizeof(*item)); + memset(item, 0, sizeof(*item)); + item->option.longName = alias.longName; + item->option.shortName = alias.shortName; + item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN; + item->option.arg = 0; + item->option.val = 0; + item->option.descrip = NULL; + item->option.argDescrip = NULL; + item->argc = alias.argc; + item->argv = alias.argv; + return poptAddItem(con, item, 0); +} + +/*@-boundswrite@*/ +/*@-mustmod@*/ /* LCL: con not modified? */ +int poptAddItem(poptContext con, poptItem newItem, int flags) +{ + poptItem * items, item; + int * nitems; + + switch (flags) { + case 1: + items = &con->execs; + nitems = &con->numExecs; + break; + case 0: + items = &con->aliases; + nitems = &con->numAliases; + break; + default: + return 1; + /*@notreached@*/ break; + } + + *items = realloc((*items), ((*nitems) + 1) * sizeof(**items)); + if ((*items) == NULL) + return 1; + + item = (*items) + (*nitems); + + item->option.longName = + (newItem->option.longName ? xstrdup(newItem->option.longName) : NULL); + item->option.shortName = newItem->option.shortName; + item->option.argInfo = newItem->option.argInfo; + item->option.arg = newItem->option.arg; + item->option.val = newItem->option.val; + item->option.descrip = + (newItem->option.descrip ? xstrdup(newItem->option.descrip) : NULL); + item->option.argDescrip = + (newItem->option.argDescrip ? xstrdup(newItem->option.argDescrip) : NULL); + item->argc = newItem->argc; + item->argv = newItem->argv; + + (*nitems)++; + + return 0; +} +/*@=mustmod@*/ +/*@=boundswrite@*/ + +const char * poptBadOption(poptContext con, int flags) +{ + struct optionStackEntry * os = NULL; + + if (con != NULL) + os = (flags & POPT_BADOPTION_NOALIAS) ? con->optionStack : con->os; + + /*@-nullderef@*/ /* LCL: os->argv != NULL */ + return (os && os->argv ? os->argv[os->next - 1] : NULL); + /*@=nullderef@*/ +} + +const char * poptStrerror(const int error) +{ + switch (error) { + case POPT_ERROR_NOARG: + return POPT_("missing argument"); + case POPT_ERROR_BADOPT: + return POPT_("unknown option"); + case POPT_ERROR_BADOPERATION: + return POPT_("mutually exclusive logical operations requested"); + case POPT_ERROR_NULLARG: + return POPT_("opt->arg should not be NULL"); + case POPT_ERROR_OPTSTOODEEP: + return POPT_("aliases nested too deeply"); + case POPT_ERROR_BADQUOTE: + return POPT_("error in parameter quoting"); + case POPT_ERROR_BADNUMBER: + return POPT_("invalid numeric value"); + case POPT_ERROR_OVERFLOW: + return POPT_("number too large or too small"); + case POPT_ERROR_MALLOC: + return POPT_("memory allocation failed"); + case POPT_ERROR_ERRNO: + return strerror(errno); + default: + return POPT_("unknown error"); + } +} + +int poptStuffArgs(poptContext con, const char ** argv) +{ + int argc; + int rc; + + if ((con->os - con->optionStack) == POPT_OPTION_DEPTH) + return POPT_ERROR_OPTSTOODEEP; + + for (argc = 0; argv[argc]; argc++) + {}; + + con->os++; + con->os->next = 0; + con->os->nextArg = NULL; + con->os->nextCharArg = NULL; + con->os->currAlias = NULL; + rc = poptDupArgv(argc, argv, &con->os->argc, &con->os->argv); + con->os->argb = NULL; + con->os->stuffed = 1; + + return rc; +} + +const char * poptGetInvocationName(poptContext con) +{ + return (con->os->argv ? con->os->argv[0] : ""); +} + +/*@-boundswrite@*/ +int poptStrippedArgv(poptContext con, int argc, char ** argv) +{ + int numargs = argc; + int j = 1; + int i; + + /*@-sizeoftype@*/ + if (con->arg_strip) + for (i = 1; i < argc; i++) { + if (PBM_ISSET(i, con->arg_strip)) + numargs--; + } + + for (i = 1; i < argc; i++) { + if (con->arg_strip && PBM_ISSET(i, con->arg_strip)) + continue; + argv[j] = (j < numargs) ? argv[i] : NULL; + j++; + } + /*@=sizeoftype@*/ + + return numargs; +} +/*@=boundswrite@*/ diff --git a/libpopt/popt.h b/libpopt/popt.h new file mode 100644 index 0000000..4f85d9e --- /dev/null +++ b/libpopt/popt.h @@ -0,0 +1,564 @@ +/** \file popt/popt.h + * \ingroup popt + */ + +/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#ifndef H_POPT +#define H_POPT + +#include <stdio.h> /* for FILE * */ + +#define POPT_OPTION_DEPTH 10 + +/** \ingroup popt + * \name Arg type identifiers + */ +/*@{*/ +#define POPT_ARG_NONE 0 /*!< no arg */ +#define POPT_ARG_STRING 1 /*!< arg will be saved as string */ +#define POPT_ARG_INT 2 /*!< arg will be converted to int */ +#define POPT_ARG_LONG 3 /*!< arg will be converted to long */ +#define POPT_ARG_INCLUDE_TABLE 4 /*!< arg points to table */ +#define POPT_ARG_CALLBACK 5 /*!< table-wide callback... must be + set first in table; arg points + to callback, descrip points to + callback data to pass */ +#define POPT_ARG_INTL_DOMAIN 6 /*!< set the translation domain + for this table and any + included tables; arg points + to the domain string */ +#define POPT_ARG_VAL 7 /*!< arg should take value val */ +#define POPT_ARG_FLOAT 8 /*!< arg will be converted to float */ +#define POPT_ARG_DOUBLE 9 /*!< arg will be converted to double */ + +#define POPT_ARG_MASK 0x0000FFFF +/*@}*/ + +/** \ingroup popt + * \name Arg modifiers + */ +/*@{*/ +#define POPT_ARGFLAG_ONEDASH 0x80000000 /*!< allow -longoption */ +#define POPT_ARGFLAG_DOC_HIDDEN 0x40000000 /*!< don't show in help/usage */ +#define POPT_ARGFLAG_STRIP 0x20000000 /*!< strip this arg from argv(only applies to long args) */ +#define POPT_ARGFLAG_OPTIONAL 0x10000000 /*!< arg may be missing */ + +#define POPT_ARGFLAG_OR 0x08000000 /*!< arg will be or'ed */ +#define POPT_ARGFLAG_NOR 0x09000000 /*!< arg will be nor'ed */ +#define POPT_ARGFLAG_AND 0x04000000 /*!< arg will be and'ed */ +#define POPT_ARGFLAG_NAND 0x05000000 /*!< arg will be nand'ed */ +#define POPT_ARGFLAG_XOR 0x02000000 /*!< arg will be xor'ed */ +#define POPT_ARGFLAG_NOT 0x01000000 /*!< arg will be negated */ +#define POPT_ARGFLAG_LOGICALOPS \ + (POPT_ARGFLAG_OR|POPT_ARGFLAG_AND|POPT_ARGFLAG_XOR) + +#define POPT_BIT_SET (POPT_ARG_VAL|POPT_ARGFLAG_OR) + /*!< set arg bit(s) */ +#define POPT_BIT_CLR (POPT_ARG_VAL|POPT_ARGFLAG_NAND) + /*!< clear arg bit(s) */ + +#define POPT_ARGFLAG_SHOW_DEFAULT 0x00800000 /*!< show default value in --help */ + +/*@}*/ + +/** \ingroup popt + * \name Callback modifiers + */ +/*@{*/ +#define POPT_CBFLAG_PRE 0x80000000 /*!< call the callback before parse */ +#define POPT_CBFLAG_POST 0x40000000 /*!< call the callback after parse */ +#define POPT_CBFLAG_INC_DATA 0x20000000 /*!< use data from the include line, + not the subtable */ +#define POPT_CBFLAG_SKIPOPTION 0x10000000 /*!< don't callback with option */ +#define POPT_CBFLAG_CONTINUE 0x08000000 /*!< continue callbacks with option */ +/*@}*/ + +/** \ingroup popt + * \name Error return values + */ +/*@{*/ +#define POPT_ERROR_NOARG -10 /*!< missing argument */ +#define POPT_ERROR_BADOPT -11 /*!< unknown option */ +#define POPT_ERROR_OPTSTOODEEP -13 /*!< aliases nested too deeply */ +#define POPT_ERROR_BADQUOTE -15 /*!< error in paramter quoting */ +#define POPT_ERROR_ERRNO -16 /*!< errno set, use strerror(errno) */ +#define POPT_ERROR_BADNUMBER -17 /*!< invalid numeric value */ +#define POPT_ERROR_OVERFLOW -18 /*!< number too large or too small */ +#define POPT_ERROR_BADOPERATION -19 /*!< mutually exclusive logical operations requested */ +#define POPT_ERROR_NULLARG -20 /*!< opt->arg should not be NULL */ +#define POPT_ERROR_MALLOC -21 /*!< memory allocation failed */ +/*@}*/ + +/** \ingroup popt + * \name poptBadOption() flags + */ +/*@{*/ +#define POPT_BADOPTION_NOALIAS (1 << 0) /*!< don't go into an alias */ +/*@}*/ + +/** \ingroup popt + * \name poptGetContext() flags + */ +/*@{*/ +#define POPT_CONTEXT_NO_EXEC (1 << 0) /*!< ignore exec expansions */ +#define POPT_CONTEXT_KEEP_FIRST (1 << 1) /*!< pay attention to argv[0] */ +#define POPT_CONTEXT_POSIXMEHARDER (1 << 2) /*!< options can't follow args */ +#define POPT_CONTEXT_ARG_OPTS (1 << 4) /*!< return args as options with value 0 */ +/*@}*/ + +/** \ingroup popt + */ +struct poptOption { +/*@observer@*/ /*@null@*/ + const char * longName; /*!< may be NULL */ + char shortName; /*!< may be NUL */ + int argInfo; +/*@shared@*/ /*@null@*/ + void * arg; /*!< depends on argInfo */ + int val; /*!< 0 means don't return, just update flag */ +/*@observer@*/ /*@null@*/ + const char * descrip; /*!< description for autohelp -- may be NULL */ +/*@observer@*/ /*@null@*/ + const char * argDescrip; /*!< argument description for autohelp */ +}; + +/** \ingroup popt + * A popt alias argument for poptAddAlias(). + */ +struct poptAlias { +/*@owned@*/ /*@null@*/ + const char * longName; /*!< may be NULL */ + char shortName; /*!< may be NUL */ + int argc; +/*@owned@*/ + const char ** argv; /*!< must be free()able */ +}; + +/** \ingroup popt + * A popt alias or exec argument for poptAddItem(). + */ +/*@-exporttype@*/ +typedef struct poptItem_s { + struct poptOption option; /*!< alias/exec name(s) and description. */ + int argc; /*!< (alias) no. of args. */ +/*@owned@*/ + const char ** argv; /*!< (alias) args, must be free()able. */ +} * poptItem; +/*@=exporttype@*/ + +/** \ingroup popt + * \name Auto-generated help/usage + */ +/*@{*/ + +/** + * Empty table marker to enable displaying popt alias/exec options. + */ +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption poptAliasOptions[]; +/*@=exportvar@*/ +#define POPT_AUTOALIAS { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptAliasOptions, \ + 0, "Options implemented via popt alias/exec:", NULL }, + +/** + * Auto help table options. + */ +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption poptHelpOptions[]; +/*@=exportvar@*/ + +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption * poptHelpOptionsI18N; +/*@=exportvar@*/ + +#define POPT_AUTOHELP { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptHelpOptions, \ + 0, "Help options:", NULL }, + +#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL } +/*@}*/ + +/** \ingroup popt + */ +/*@-exporttype@*/ +typedef /*@abstract@*/ struct poptContext_s * poptContext; +/*@=exporttype@*/ + +/** \ingroup popt + */ +#ifndef __cplusplus +/*@-exporttype -typeuse@*/ +typedef struct poptOption * poptOption; +/*@=exporttype =typeuse@*/ +#endif + +/*@-exportconst@*/ +enum poptCallbackReason { + POPT_CALLBACK_REASON_PRE = 0, + POPT_CALLBACK_REASON_POST = 1, + POPT_CALLBACK_REASON_OPTION = 2 +}; +/*@=exportconst@*/ + +#ifdef __cplusplus +extern "C" { +#endif +/*@-type@*/ + +/** \ingroup popt + * Table callback prototype. + * @param con context + * @param reason reason for callback + * @param opt option that triggered callback + * @param arg @todo Document. + * @param data @todo Document. + */ +typedef void (*poptCallbackType) (poptContext con, + enum poptCallbackReason reason, + /*@null@*/ const struct poptOption * opt, + /*@null@*/ const char * arg, + /*@null@*/ const void * data) + /*@globals internalState @*/ + /*@modifies internalState @*/; + +/** \ingroup popt + * Initialize popt context. + * @param name context name (usually argv[0] program name) + * @param argc no. of arguments + * @param argv argument array + * @param options address of popt option table + * @param flags or'd POPT_CONTEXT_* bits + * @return initialized popt context + */ +/*@only@*/ /*@null@*/ +poptContext poptGetContext( + /*@dependent@*/ /*@keep@*/ const char * name, + int argc, /*@dependent@*/ /*@keep@*/ const char ** argv, + /*@dependent@*/ /*@keep@*/ const struct poptOption * options, + int flags) + /*@*/; + +/** \ingroup popt + * Reinitialize popt context. + * @param con context + */ +/*@unused@*/ +void poptResetContext(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return value of next option found. + * @param con context + * @return next option val, -1 on last item, POPT_ERROR_* on error + */ +int poptGetNextOpt(/*@null@*/poptContext con) + /*@globals fileSystem, internalState @*/ + /*@modifies con, fileSystem, internalState @*/; + +/** \ingroup popt + * Return next option argument (if any). + * @param con context + * @return option argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptGetOptArg(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return next argument. + * @param con context + * @return next argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptGetArg(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Peek at current argument. + * @param con context + * @return current argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptPeekArg(/*@null@*/poptContext con) + /*@*/; + +/** \ingroup popt + * Return remaining arguments. + * @param con context + * @return argument array, NULL terminated + */ +/*@observer@*/ /*@null@*/ +const char ** poptGetArgs(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return the option which caused the most recent error. + * @param con context + * @param flags + * @return offending option + */ +/*@observer@*/ +const char * poptBadOption(/*@null@*/poptContext con, int flags) + /*@*/; + +/** \ingroup popt + * Destroy context. + * @param con context + * @return NULL always + */ +/*@null@*/ +poptContext poptFreeContext( /*@only@*/ /*@null@*/ poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Add arguments to context. + * @param con context + * @param argv argument array, NULL terminated + * @return 0 on success, POPT_ERROR_OPTSTOODEEP on failure + */ +/*@unused@*/ +int poptStuffArgs(poptContext con, /*@keep@*/ const char ** argv) + /*@modifies con @*/; + +/** \ingroup popt + * Add alias to context. + * @todo Pass alias by reference, not value. + * @deprecated Use poptAddItem instead. + * @param con context + * @param alias alias to add + * @param flags (unused) + * @return 0 on success + */ +/*@unused@*/ +int poptAddAlias(poptContext con, struct poptAlias alias, int flags) + /*@modifies con @*/; + +/** \ingroup popt + * Add alias/exec item to context. + * @param con context + * @param newItem alias/exec item to add + * @param flags 0 for alias, 1 for exec + * @return 0 on success + */ +int poptAddItem(poptContext con, poptItem newItem, int flags) + /*@modifies con @*/; + +/** \ingroup popt + * Read configuration file. + * @param con context + * @param fn file name to read + * @return 0 on success, POPT_ERROR_ERRNO on failure + */ +int poptReadConfigFile(poptContext con, const char * fn) + /*@globals errno, fileSystem, internalState @*/ + /*@modifies con->execs, con->numExecs, + errno, fileSystem, internalState @*/; + +/** \ingroup popt + * Read default configuration from /etc/popt and $HOME/.popt. + * @param con context + * @param useEnv (unused) + * @return 0 on success, POPT_ERROR_ERRNO on failure + */ +/*@unused@*/ +int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv) + /*@globals fileSystem, internalState @*/ + /*@modifies con->execs, con->numExecs, + fileSystem, internalState @*/; + +/** \ingroup popt + * Duplicate an argument array. + * @note: The argument array is malloc'd as a single area, so only argv must + * be free'd. + * + * @param argc no. of arguments + * @param argv argument array + * @retval argcPtr address of returned no. of arguments + * @retval argvPtr address of returned argument array + * @return 0 on success, POPT_ERROR_NOARG on failure + */ +int poptDupArgv(int argc, /*@null@*/ const char **argv, + /*@null@*/ /*@out@*/ int * argcPtr, + /*@null@*/ /*@out@*/ const char *** argvPtr) + /*@modifies *argcPtr, *argvPtr @*/; + +/** \ingroup popt + * Parse a string into an argument array. + * The parse allows ', ", and \ quoting, but ' is treated the same as " and + * both may include \ quotes. + * @note: The argument array is malloc'd as a single area, so only argv must + * be free'd. + * + * @param s string to parse + * @retval argcPtr address of returned no. of arguments + * @retval argvPtr address of returned argument array + */ +int poptParseArgvString(const char * s, + /*@out@*/ int * argcPtr, /*@out@*/ const char *** argvPtr) + /*@modifies *argcPtr, *argvPtr @*/; + +/** \ingroup popt + * Parses an input configuration file and returns an string that is a + * command line. For use with popt. You must free the return value when done. + * + * Given the file: +\verbatim +# this line is ignored + # this one too +aaa + bbb + ccc +bla=bla + +this_is = fdsafdas + bad_line= + reall bad line + reall bad line = again +5555= 55555 + test = with lots of spaces +\endverbatim +* +* The result is: +\verbatim +--aaa --bbb --ccc --bla="bla" --this_is="fdsafdas" --5555="55555" --test="with lots of spaces" +\endverbatim +* +* Passing this to poptParseArgvString() yields an argv of: +\verbatim +'--aaa' +'--bbb' +'--ccc' +'--bla=bla' +'--this_is=fdsafdas' +'--5555=55555' +'--test=with lots of spaces' +\endverbatim + * + * @bug NULL is returned if file line is too long. + * @bug Silently ignores invalid lines. + * + * @param fp file handle to read + * @param *argstrp return string of options (malloc'd) + * @param flags unused + * @return 0 on success + * @see poptParseArgvString + */ +/*@-fcnuse@*/ +int poptConfigFileToString(FILE *fp, /*@out@*/ char ** argstrp, int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, *argstrp, fileSystem @*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Return formatted error string for popt failure. + * @param error popt error + * @return error string + */ +/*@observer@*/ +const char * poptStrerror(const int error) + /*@*/; + +/** \ingroup popt + * Limit search for executables. + * @param con context + * @param path single path to search for executables + * @param allowAbsolute absolute paths only? + */ +/*@unused@*/ +void poptSetExecPath(poptContext con, const char * path, int allowAbsolute) + /*@modifies con @*/; + +/** \ingroup popt + * Print detailed description of options. + * @param con context + * @param fp ouput file handle + * @param flags (unused) + */ +void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/; + +/** \ingroup popt + * Print terse description of options. + * @param con context + * @param fp ouput file handle + * @param flags (unused) + */ +void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/; + +/** \ingroup popt + * Provide text to replace default "[OPTION...]" in help/usage output. + * @param con context + * @param text replacement text + */ +/*@-fcnuse@*/ +void poptSetOtherOptionHelp(poptContext con, const char * text) + /*@modifies con @*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Return argv[0] from context. + * @param con context + * @return argv[0] + */ +/*@-fcnuse@*/ +/*@observer@*/ +const char * poptGetInvocationName(poptContext con) + /*@*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Shuffle argv pointers to remove stripped args, returns new argc. + * @param con context + * @param argc no. of args + * @param argv arg vector + * @return new argc + */ +/*@-fcnuse@*/ +int poptStrippedArgv(poptContext con, int argc, char ** argv) + /*@modifies *argv @*/; +/*@=fcnuse@*/ + +/** + * Save a long, performing logical operation with value. + * @warning Alignment check may be too strict on certain platorms. + * @param arg integer pointer, aligned on int boundary. + * @param argInfo logical operation (see POPT_ARGFLAG_*) + * @param aLong value to use + * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION + */ +/*@-incondefs@*/ +/*@unused@*/ +int poptSaveLong(/*@null@*/ long * arg, int argInfo, long aLong) + /*@modifies *arg @*/ + /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/; +/*@=incondefs@*/ + +/** + * Save an integer, performing logical operation with value. + * @warning Alignment check may be too strict on certain platorms. + * @param arg integer pointer, aligned on int boundary. + * @param argInfo logical operation (see POPT_ARGFLAG_*) + * @param aLong value to use + * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION + */ +/*@-incondefs@*/ +/*@unused@*/ +int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong) + /*@modifies *arg @*/ + /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/; +/*@=incondefs@*/ + +/*@=type@*/ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libpopt/poptconfig.c b/libpopt/poptconfig.c new file mode 100644 index 0000000..e5cba45 --- /dev/null +++ b/libpopt/poptconfig.c @@ -0,0 +1,182 @@ +/** \ingroup popt + * \file popt/poptconfig.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" +#include "poptint.h" +/*@access poptContext @*/ + +/*@-compmempass@*/ /* FIX: item->option.longName kept, not dependent. */ +static void configLine(poptContext con, char * line) + /*@modifies con @*/ +{ + size_t nameLength; + const char * entryType; + const char * opt; + poptItem item = alloca(sizeof(*item)); + int i, j; + + if (con->appName == NULL) + return; + nameLength = strlen(con->appName); + +/*@-boundswrite@*/ + memset(item, 0, sizeof(*item)); + + if (strncmp(line, con->appName, nameLength)) return; + + line += nameLength; + if (*line == '\0' || !isspace(*line)) return; + + while (*line != '\0' && isspace(*line)) line++; + entryType = line; + while (*line == '\0' || !isspace(*line)) line++; + *line++ = '\0'; + + while (*line != '\0' && isspace(*line)) line++; + if (*line == '\0') return; + opt = line; + while (*line == '\0' || !isspace(*line)) line++; + *line++ = '\0'; + + while (*line != '\0' && isspace(*line)) line++; + if (*line == '\0') return; + + /*@-temptrans@*/ /* FIX: line alias is saved */ + if (opt[0] == '-' && opt[1] == '-') + item->option.longName = opt + 2; + else if (opt[0] == '-' && opt[2] == '\0') + item->option.shortName = opt[1]; + /*@=temptrans@*/ + + if (poptParseArgvString(line, &item->argc, &item->argv)) return; + + /*@-modobserver@*/ + item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN; + for (i = 0, j = 0; i < item->argc; i++, j++) { + const char * f; + if (!strncmp(item->argv[i], "--POPTdesc=", sizeof("--POPTdesc=")-1)) { + f = item->argv[i] + sizeof("--POPTdesc="); + if (f[0] == '$' && f[1] == '"') f++; + item->option.descrip = f; + item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN; + j--; + } else + if (!strncmp(item->argv[i], "--POPTargs=", sizeof("--POPTargs=")-1)) { + f = item->argv[i] + sizeof("--POPTargs="); + if (f[0] == '$' && f[1] == '"') f++; + item->option.argDescrip = f; + item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN; + item->option.argInfo |= POPT_ARG_STRING; + j--; + } else + if (j != i) + item->argv[j] = item->argv[i]; + } + if (j != i) { + item->argv[j] = NULL; + item->argc = j; + } + /*@=modobserver@*/ +/*@=boundswrite@*/ + + /*@-nullstate@*/ /* FIX: item->argv[] may be NULL */ + if (!strcmp(entryType, "alias")) + (void) poptAddItem(con, item, 0); + else if (!strcmp(entryType, "exec")) + (void) poptAddItem(con, item, 1); + /*@=nullstate@*/ +} +/*@=compmempass@*/ + +int poptReadConfigFile(poptContext con, const char * fn) +{ + const char * file, * chptr, * end; + char * buf; +/*@dependent@*/ char * dst; + int fd, rc; + off_t fileLength; + + fd = open(fn, O_RDONLY); + if (fd < 0) + return (errno == ENOENT ? 0 : POPT_ERROR_ERRNO); + + fileLength = lseek(fd, 0, SEEK_END); + if (fileLength == -1 || lseek(fd, 0, 0) == -1) { + rc = errno; + (void) close(fd); + errno = rc; + return POPT_ERROR_ERRNO; + } + + file = alloca(fileLength + 1); + if (read(fd, (char *)file, fileLength) != fileLength) { + rc = errno; + (void) close(fd); + errno = rc; + return POPT_ERROR_ERRNO; + } + if (close(fd) == -1) + return POPT_ERROR_ERRNO; + +/*@-boundswrite@*/ + dst = buf = alloca(fileLength + 1); + + chptr = file; + end = (file + fileLength); + /*@-infloops@*/ /* LCL: can't detect chptr++ */ + while (chptr < end) { + switch (*chptr) { + case '\n': + *dst = '\0'; + dst = buf; + while (*dst && isspace(*dst)) dst++; + if (*dst && *dst != '#') + configLine(con, dst); + chptr++; + /*@switchbreak@*/ break; + case '\\': + *dst++ = *chptr++; + if (chptr < end) { + if (*chptr == '\n') + dst--, chptr++; + /* \ at the end of a line does not insert a \n */ + else + *dst++ = *chptr++; + } + /*@switchbreak@*/ break; + default: + *dst++ = *chptr++; + /*@switchbreak@*/ break; + } + } + /*@=infloops@*/ +/*@=boundswrite@*/ + + return 0; +} + +int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv) +{ + char * fn, * home; + int rc; + + if (con->appName == NULL) return 0; + + rc = poptReadConfigFile(con, "/etc/popt"); + if (rc) return rc; + + if ((home = getenv("HOME"))) { + fn = alloca(strlen(home) + 20); + strcpy(fn, home); + strcat(fn, "/.popt"); + rc = poptReadConfigFile(con, fn); + if (rc) return rc; + } + + return 0; +} diff --git a/libpopt/popthelp.c b/libpopt/popthelp.c new file mode 100644 index 0000000..9430a2d --- /dev/null +++ b/libpopt/popthelp.c @@ -0,0 +1,819 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */ + +/** \ingroup popt + * \file popt/popthelp.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" + +//#define POPT_WCHAR_HACK +#ifdef POPT_WCHAR_HACK +#include <wchar.h> /* for mbsrtowcs */ +/*@access mbstate_t @*/ +#endif +#include "poptint.h" + +/*@access poptContext@*/ + +/** + * Display arguments. + * @param con context + * @param foo (unused) + * @param key option(s) + * @param arg (unused) + * @param data (unused) + */ +static void displayArgs(poptContext con, + /*@unused@*/ enum poptCallbackReason foo, + struct poptOption * key, + /*@unused@*/ const char * arg, /*@unused@*/ void * data) + /*@globals fileSystem@*/ + /*@modifies fileSystem@*/ +{ + if (key->shortName == '?') + poptPrintHelp(con, stdout, 0); + else + poptPrintUsage(con, stdout, 0); + exit(0); +} + +#ifdef NOTYET +/*@unchecked@*/ +static int show_option_defaults = 0; +#endif + +/** + * Empty table marker to enable displaying popt alias/exec options. + */ +/*@observer@*/ /*@unchecked@*/ +struct poptOption poptAliasOptions[] = { + POPT_TABLEEND +}; + +/** + * Auto help table options. + */ +/*@-castfcnptr@*/ +/*@observer@*/ /*@unchecked@*/ +struct poptOption poptHelpOptions[] = { + { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL }, + { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL }, + { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL }, + POPT_TABLEEND +} ; + +/*@observer@*/ /*@unchecked@*/ +static struct poptOption poptHelpOptions2[] = { +/*@-readonlytrans@*/ + { NULL, '\0', POPT_ARG_INTL_DOMAIN, PACKAGE, 0, NULL, NULL}, +/*@=readonlytrans@*/ + { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL }, + { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL }, + { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL }, +#ifdef NOTYET + { "defaults", '\0', POPT_ARG_NONE, &show_option_defaults, 0, + N_("Display option defaults in message"), NULL }, +#endif + POPT_TABLEEND +} ; + +/*@observer@*/ /*@unchecked@*/ +struct poptOption * poptHelpOptionsI18N = poptHelpOptions2; +/*@=castfcnptr@*/ + +/** + * @param table option(s) + */ +/*@observer@*/ /*@null@*/ static const char * +getTableTranslationDomain(/*@null@*/ const struct poptOption *table) + /*@*/ +{ + const struct poptOption *opt; + + if (table != NULL) + for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) { + if (opt->argInfo == POPT_ARG_INTL_DOMAIN) + return opt->arg; + } + return NULL; +} + +/** + * @param opt option(s) + * @param translation_domain translation domain + */ +/*@observer@*/ /*@null@*/ static const char * +getArgDescrip(const struct poptOption * opt, + /*@-paramuse@*/ /* FIX: i18n macros disabled with lclint */ + /*@null@*/ const char * translation_domain) + /*@=paramuse@*/ + /*@*/ +{ + if (!(opt->argInfo & POPT_ARG_MASK)) return NULL; + + if (opt == (poptHelpOptions + 1) || opt == (poptHelpOptions + 2)) + if (opt->argDescrip) return POPT_(opt->argDescrip); + + if (opt->argDescrip) return D_(translation_domain, opt->argDescrip); + + switch (opt->argInfo & POPT_ARG_MASK) { + case POPT_ARG_NONE: return POPT_("NONE"); +#ifdef DYING + case POPT_ARG_VAL: return POPT_("VAL"); +#else + case POPT_ARG_VAL: return NULL; +#endif + case POPT_ARG_INT: return POPT_("INT"); + case POPT_ARG_LONG: return POPT_("LONG"); + case POPT_ARG_STRING: return POPT_("STRING"); + case POPT_ARG_FLOAT: return POPT_("FLOAT"); + case POPT_ARG_DOUBLE: return POPT_("DOUBLE"); + default: return POPT_("ARG"); + } +} + +/** + * Display default value for an option. + * @param lineLength display positions remaining + * @param opt option(s) + * @param translation_domain translation domain + * @return + */ +static /*@only@*/ /*@null@*/ char * +singleOptionDefaultValue(size_t lineLength, + const struct poptOption * opt, + /*@-paramuse@*/ /* FIX: i18n macros disabled with lclint */ + /*@null@*/ const char * translation_domain) + /*@=paramuse@*/ + /*@*/ +{ + const char * defstr = D_(translation_domain, "default"); + char * le = malloc(4*lineLength + 1); + char * l = le; + + if (le == NULL) return NULL; /* XXX can't happen */ +/*@-boundswrite@*/ + *le = '\0'; + *le++ = '('; + strcpy(le, defstr); le += strlen(le); + *le++ = ':'; + *le++ = ' '; + if (opt->arg) /* XXX programmer error */ + switch (opt->argInfo & POPT_ARG_MASK) { + case POPT_ARG_VAL: + case POPT_ARG_INT: + { long aLong = *((int *)opt->arg); + le += sprintf(le, "%ld", aLong); + } break; + case POPT_ARG_LONG: + { long aLong = *((long *)opt->arg); + le += sprintf(le, "%ld", aLong); + } break; + case POPT_ARG_FLOAT: + { double aDouble = *((float *)opt->arg); + le += sprintf(le, "%g", aDouble); + } break; + case POPT_ARG_DOUBLE: + { double aDouble = *((double *)opt->arg); + le += sprintf(le, "%g", aDouble); + } break; + case POPT_ARG_STRING: + { const char * s = *(const char **)opt->arg; + if (s == NULL) { + strcpy(le, "null"); le += strlen(le); + } else { + size_t slen = 4*lineLength - (le - l) - sizeof("\"...\")"); + *le++ = '"'; + strncpy(le, s, slen); le[slen] = '\0'; le += strlen(le); + if (slen < strlen(s)) { + strcpy(le, "..."); le += strlen(le); + } + *le++ = '"'; + } + } break; + case POPT_ARG_NONE: + default: + l = _free(l); + return NULL; + /*@notreached@*/ break; + } + *le++ = ')'; + *le = '\0'; +/*@=boundswrite@*/ + + return l; +} + +/** + * Display help text for an option. + * @param fp output file handle + * @param maxLeftCol largest argument display width + * @param opt option(s) + * @param translation_domain translation domain + */ +static void singleOptionHelp(FILE * fp, size_t maxLeftCol, + const struct poptOption * opt, + /*@null@*/ const char * translation_domain) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + size_t indentLength = maxLeftCol + 5; + size_t lineLength = 79 - indentLength; + const char * help = D_(translation_domain, opt->descrip); + const char * argDescrip = getArgDescrip(opt, translation_domain); + size_t helpLength; + char * defs = NULL; + char * left; + size_t nb = maxLeftCol + 1; + int displaypad = 0; + + /* Make sure there's more than enough room in target buffer. */ + if (opt->longName) nb += strlen(opt->longName); + if (argDescrip) nb += strlen(argDescrip); + +/*@-boundswrite@*/ + left = malloc(nb); + if (left == NULL) return; /* XXX can't happen */ + left[0] = '\0'; + left[maxLeftCol] = '\0'; + + if (opt->longName && opt->shortName) + sprintf(left, "-%c, %s%s", opt->shortName, + ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"), + opt->longName); + else if (opt->shortName != '\0') + sprintf(left, "-%c", opt->shortName); + else if (opt->longName) + sprintf(left, "%s%s", + ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"), + opt->longName); + if (!*left) goto out; + + if (argDescrip) { + char * le = left + strlen(left); + + if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) + *le++ = '['; + + /* Choose type of output */ + /*@-branchstate@*/ + if (opt->argInfo & POPT_ARGFLAG_SHOW_DEFAULT) { + defs = singleOptionDefaultValue(lineLength, opt, translation_domain); + if (defs) { + char * t = malloc((help ? strlen(help) : 0) + + strlen(defs) + sizeof(" ")); + if (t) { + char * te = t; + *te = '\0'; + if (help) { + strcpy(te, help); te += strlen(te); + } + *te++ = ' '; + strcpy(te, defs); + defs = _free(defs); + } + defs = t; + } + } + /*@=branchstate@*/ + + if (opt->argDescrip == NULL) { + switch (opt->argInfo & POPT_ARG_MASK) { + case POPT_ARG_NONE: + break; + case POPT_ARG_VAL: +#ifdef NOTNOW /* XXX pug ugly nerdy output */ + { long aLong = opt->val; + int ops = (opt->argInfo & POPT_ARGFLAG_LOGICALOPS); + int negate = (opt->argInfo & POPT_ARGFLAG_NOT); + + /* Don't bother displaying typical values */ + if (!ops && (aLong == 0L || aLong == 1L || aLong == -1L)) + break; + *le++ = '['; + switch (ops) { + case POPT_ARGFLAG_OR: + *le++ = '|'; + /*@innerbreak@*/ break; + case POPT_ARGFLAG_AND: + *le++ = '&'; + /*@innerbreak@*/ break; + case POPT_ARGFLAG_XOR: + *le++ = '^'; + /*@innerbreak@*/ break; + default: + /*@innerbreak@*/ break; + } + *le++ = (opt->longName != NULL ? '=' : ' '); + if (negate) *le++ = '~'; + /*@-formatconst@*/ + le += sprintf(le, (ops ? "0x%lx" : "%ld"), aLong); + /*@=formatconst@*/ + *le++ = ']'; + } +#endif + break; + case POPT_ARG_INT: + case POPT_ARG_LONG: + case POPT_ARG_FLOAT: + case POPT_ARG_DOUBLE: + case POPT_ARG_STRING: + *le++ = (opt->longName != NULL ? '=' : ' '); + strcpy(le, argDescrip); le += strlen(le); + break; + default: + break; + } + } else { + size_t lelen; + + *le++ = '='; + strcpy(le, argDescrip); + lelen = strlen(le); + le += lelen; + +#ifdef POPT_WCHAR_HACK + { const char * scopy = argDescrip; + mbstate_t t; + size_t n; + + memset ((void *)&t, '\0', sizeof (t)); /* In initial state. */ + /* Determine number of characters. */ + n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t); + + displaypad = (int) (lelen-n); + } +#endif + } + if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) + *le++ = ']'; + *le = '\0'; + } +/*@=boundswrite@*/ + + if (help) + fprintf(fp," %-*s ", maxLeftCol+displaypad, left); + else { + fprintf(fp," %s\n", left); + goto out; + } + + left = _free(left); +/*@-branchstate@*/ + if (defs) { + help = defs; + defs = NULL; + } +/*@=branchstate@*/ + + helpLength = strlen(help); +/*@-boundsread@*/ + while (helpLength > lineLength) { + const char * ch; + char format[16]; + + ch = help + lineLength - 1; + while (ch > help && !isspace(*ch)) ch--; + if (ch == help) break; /* give up */ + while (ch > (help + 1) && isspace(*ch)) ch--; + ch++; + + sprintf(format, "%%.%ds\n%%%ds", (int) (ch - help), (int) indentLength); + /*@-formatconst@*/ + fprintf(fp, format, help, " "); + /*@=formatconst@*/ + help = ch; + while (isspace(*help) && *help) help++; + helpLength = strlen(help); + } +/*@=boundsread@*/ + + if (helpLength) fprintf(fp, "%s\n", help); + +out: + /*@-dependenttrans@*/ + defs = _free(defs); + /*@=dependenttrans@*/ + left = _free(left); +} + +/** + * Find display width for longest argument string. + * @param opt option(s) + * @param translation_domain translation domain + * @return display width + */ +static size_t maxArgWidth(const struct poptOption * opt, + /*@null@*/ const char * translation_domain) + /*@*/ +{ + size_t max = 0; + size_t len = 0; + const char * s; + + if (opt != NULL) + while (opt->longName || opt->shortName || opt->arg) { + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + if (opt->arg) /* XXX program error */ + len = maxArgWidth(opt->arg, translation_domain); + if (len > max) max = len; + } else if (!(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) { + len = sizeof(" ")-1; + if (opt->shortName != '\0') len += sizeof("-X")-1; + if (opt->shortName != '\0' && opt->longName) len += sizeof(", ")-1; + if (opt->longName) { + len += ((opt->argInfo & POPT_ARGFLAG_ONEDASH) + ? sizeof("-")-1 : sizeof("--")-1); + len += strlen(opt->longName); + } + + s = getArgDescrip(opt, translation_domain); + +#ifdef POPT_WCHAR_HACK + /* XXX Calculate no. of display characters. */ + if (s) { + const char * scopy = s; + mbstate_t t; + size_t n; + +/*@-boundswrite@*/ + memset ((void *)&t, '\0', sizeof (t)); /* In initial state. */ +/*@=boundswrite@*/ + /* Determine number of characters. */ + n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t); + len += sizeof("=")-1 + n; + } +#else + if (s) + len += sizeof("=")-1 + strlen(s); +#endif + + if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) len += sizeof("[]")-1; + if (len > max) max = len; + } + + opt++; + } + + return max; +} + +/** + * Display popt alias and exec help. + * @param fp output file handle + * @param items alias/exec array + * @param nitems no. of alias/exec entries + * @param left largest argument display width + * @param translation_domain translation domain + */ +static void itemHelp(FILE * fp, + /*@null@*/ poptItem items, int nitems, size_t left, + /*@null@*/ const char * translation_domain) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + poptItem item; + int i; + + if (items != NULL) + for (i = 0, item = items; i < nitems; i++, item++) { + const struct poptOption * opt; + opt = &item->option; + if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) + singleOptionHelp(fp, left, opt, translation_domain); + } +} + +/** + * Display help text for a table of options. + * @param con context + * @param fp output file handle + * @param table option(s) + * @param left largest argument display width + * @param translation_domain translation domain + */ +static void singleTableHelp(poptContext con, FILE * fp, + /*@null@*/ const struct poptOption * table, size_t left, + /*@null@*/ const char * translation_domain) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + const struct poptOption * opt; + const char *sub_transdom; + + if (table == poptAliasOptions) { + itemHelp(fp, con->aliases, con->numAliases, left, NULL); + itemHelp(fp, con->execs, con->numExecs, left, NULL); + return; + } + + if (table != NULL) + for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) { + if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) + singleOptionHelp(fp, left, opt, translation_domain); + } + + if (table != NULL) + for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) { + if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_INCLUDE_TABLE) + continue; + sub_transdom = getTableTranslationDomain(opt->arg); + if (sub_transdom == NULL) + sub_transdom = translation_domain; + + if (opt->descrip) + fprintf(fp, "\n%s\n", D_(sub_transdom, opt->descrip)); + + singleTableHelp(con, fp, opt->arg, left, sub_transdom); + } +} + +/** + * @param con context + * @param fp output file handle + */ +static int showHelpIntro(poptContext con, FILE * fp) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + int len = 6; + const char * fn; + + fprintf(fp, POPT_("Usage:")); + if (!(con->flags & POPT_CONTEXT_KEEP_FIRST)) { +/*@-boundsread@*/ + /*@-nullderef -type@*/ /* LCL: wazzup? */ + fn = con->optionStack->argv[0]; + /*@=nullderef =type@*/ +/*@=boundsread@*/ + if (fn == NULL) return len; + if (strchr(fn, '/')) fn = strrchr(fn, '/') + 1; + fprintf(fp, " %s", fn); + len += strlen(fn) + 1; + } + + return len; +} + +void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags) +{ + size_t leftColWidth; + + (void) showHelpIntro(con, fp); + if (con->otherHelp) + fprintf(fp, " %s\n", con->otherHelp); + else + fprintf(fp, " %s\n", POPT_("[OPTION...]")); + + leftColWidth = maxArgWidth(con->options, NULL); + singleTableHelp(con, fp, con->options, leftColWidth, NULL); +} + +/** + * Display usage text for an option. + * @param fp output file handle + * @param cursor current display position + * @param opt option(s) + * @param translation_domain translation domain + */ +static size_t singleOptionUsage(FILE * fp, size_t cursor, + const struct poptOption * opt, + /*@null@*/ const char *translation_domain) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + size_t len = 4; + char shortStr[2] = { '\0', '\0' }; + const char * item = shortStr; + const char * argDescrip = getArgDescrip(opt, translation_domain); + + if (opt->shortName != '\0' && opt->longName != NULL) { + len += 2; + if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++; + len += strlen(opt->longName); + } else if (opt->shortName != '\0') { + len++; + shortStr[0] = opt->shortName; + shortStr[1] = '\0'; + } else if (opt->longName) { + len += strlen(opt->longName); + if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++; + item = opt->longName; + } + + if (len == 4) return cursor; + +#ifdef POPT_WCHAR_HACK + /* XXX Calculate no. of display characters. */ + if (argDescrip) { + const char * scopy = argDescrip; + mbstate_t t; + size_t n; + +/*@-boundswrite@*/ + memset ((void *)&t, '\0', sizeof (t)); /* In initial state. */ +/*@=boundswrite@*/ + /* Determine number of characters. */ + n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t); + len += sizeof("=")-1 + n; + } +#else + if (argDescrip) + len += sizeof("=")-1 + strlen(argDescrip); +#endif + + if ((cursor + len) > 79) { + fprintf(fp, "\n "); + cursor = 7; + } + + if (opt->longName && opt->shortName) { + fprintf(fp, " [-%c|-%s%s%s%s]", + opt->shortName, ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "" : "-"), + opt->longName, + (argDescrip ? " " : ""), + (argDescrip ? argDescrip : "")); + } else { + fprintf(fp, " [-%s%s%s%s]", + ((opt->shortName || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) ? "" : "-"), + item, + (argDescrip ? (opt->shortName != '\0' ? " " : "=") : ""), + (argDescrip ? argDescrip : "")); + } + + return cursor + len + 1; +} + +/** + * Display popt alias and exec usage. + * @param fp output file handle + * @param cursor current display position + * @param item alias/exec array + * @param nitems no. of ara/exec entries + * @param translation_domain translation domain + */ +static size_t itemUsage(FILE * fp, size_t cursor, + /*@null@*/ poptItem item, int nitems, + /*@null@*/ const char * translation_domain) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + int i; + + /*@-branchstate@*/ /* FIX: W2DO? */ + if (item != NULL) + for (i = 0; i < nitems; i++, item++) { + const struct poptOption * opt; + opt = &item->option; + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) { + translation_domain = (const char *)opt->arg; + } else if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) { + cursor = singleOptionUsage(fp, cursor, opt, translation_domain); + } + } + /*@=branchstate@*/ + + return cursor; +} + +/** + * Keep track of option tables already processed. + */ +typedef struct poptDone_s { + int nopts; + int maxopts; + const void ** opts; +} * poptDone; + +/** + * Display usage text for a table of options. + * @param con context + * @param fp output file handle + * @param cursor current display position + * @param opt option(s) + * @param translation_domain translation domain + * @param done tables already processed + * @return + */ +static size_t singleTableUsage(poptContext con, FILE * fp, size_t cursor, + /*@null@*/ const struct poptOption * opt, + /*@null@*/ const char * translation_domain, + /*@null@*/ poptDone done) + /*@globals fileSystem @*/ + /*@modifies *fp, done, fileSystem @*/ +{ + /*@-branchstate@*/ /* FIX: W2DO? */ + if (opt != NULL) + for (; (opt->longName || opt->shortName || opt->arg) ; opt++) { + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) { + translation_domain = (const char *)opt->arg; + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + if (done) { + int i = 0; + for (i = 0; i < done->nopts; i++) { +/*@-boundsread@*/ + const void * that = done->opts[i]; +/*@=boundsread@*/ + if (that == NULL || that != opt->arg) + /*@innercontinue@*/ continue; + /*@innerbreak@*/ break; + } + /* Skip if this table has already been processed. */ + if (opt->arg == NULL || i < done->nopts) + continue; +/*@-boundswrite@*/ + if (done->nopts < done->maxopts) + done->opts[done->nopts++] = (const void *) opt->arg; +/*@=boundswrite@*/ + } + cursor = singleTableUsage(con, fp, cursor, opt->arg, + translation_domain, done); + } else if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) { + cursor = singleOptionUsage(fp, cursor, opt, translation_domain); + } + } + /*@=branchstate@*/ + + return cursor; +} + +/** + * Return concatenated short options for display. + * @todo Sub-tables should be recursed. + * @param opt option(s) + * @param fp output file handle + * @retval str concatenation of short options + * @return length of display string + */ +static int showShortOptions(const struct poptOption * opt, FILE * fp, + /*@null@*/ char * str) + /*@globals fileSystem @*/ + /*@modifies *str, *fp, fileSystem @*/ + /*@requires maxRead(str) >= 0 @*/ +{ + /* bufsize larger then the ascii set, lazy alloca on top level call. */ + char * s = (str != NULL ? str : memset(alloca(300), 0, 300)); + int len = 0; + +/*@-boundswrite@*/ + if (opt != NULL) + for (; (opt->longName || opt->shortName || opt->arg); opt++) { + if (opt->shortName && !(opt->argInfo & POPT_ARG_MASK)) + s[strlen(s)] = opt->shortName; + else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) + if (opt->arg) /* XXX program error */ + len = showShortOptions(opt->arg, fp, s); + } +/*@=boundswrite@*/ + + /* On return to top level, print the short options, return print length. */ + if (s == str && *s != '\0') { + fprintf(fp, " [-%s]", s); + len = strlen(s) + sizeof(" [-]")-1; + } + return len; +} + +void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags) +{ + poptDone done = memset(alloca(sizeof(*done)), 0, sizeof(*done)); + size_t cursor; + + done->nopts = 0; + done->maxopts = 64; + cursor = done->maxopts * sizeof(*done->opts); +/*@-boundswrite@*/ + done->opts = memset(alloca(cursor), 0, cursor); + /*@-keeptrans@*/ + done->opts[done->nopts++] = (const void *) con->options; + /*@=keeptrans@*/ +/*@=boundswrite@*/ + + cursor = showHelpIntro(con, fp); + cursor += showShortOptions(con->options, fp, NULL); + cursor = singleTableUsage(con, fp, cursor, con->options, NULL, done); + cursor = itemUsage(fp, cursor, con->aliases, con->numAliases, NULL); + cursor = itemUsage(fp, cursor, con->execs, con->numExecs, NULL); + + if (con->otherHelp) { + cursor += strlen(con->otherHelp) + 1; + if (cursor > 79) fprintf(fp, "\n "); + fprintf(fp, " %s", con->otherHelp); + } + + fprintf(fp, "\n"); +} + +void poptSetOtherOptionHelp(poptContext con, const char * text) +{ + con->otherHelp = _free(con->otherHelp); + con->otherHelp = xstrdup(text); +} diff --git a/libpopt/poptint.h b/libpopt/poptint.h new file mode 100644 index 0000000..5e75712 --- /dev/null +++ b/libpopt/poptint.h @@ -0,0 +1,116 @@ +/** \ingroup popt + * \file popt/poptint.h + */ + +/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#ifndef H_POPTINT +#define H_POPTINT + +/** + * Wrapper to free(3), hides const compilation noise, permit NULL, return NULL. + * @param p memory to free + * @retval NULL always + */ +/*@unused@*/ static inline /*@null@*/ void * +_free(/*@only@*/ /*@null@*/ const void * p) + /*@modifies p @*/ +{ + if (p != NULL) free((void *)p); + return NULL; +} + +/* Bit mask macros. */ +/*@-exporttype -redef @*/ +typedef unsigned int __pbm_bits; +/*@=exporttype =redef @*/ +#define __PBM_NBITS (8 * sizeof (__pbm_bits)) +#define __PBM_IX(d) ((d) / __PBM_NBITS) +#define __PBM_MASK(d) ((__pbm_bits) 1 << (((unsigned)(d)) % __PBM_NBITS)) +/*@-exporttype -redef @*/ +typedef struct { + __pbm_bits bits[1]; +} pbm_set; +/*@=exporttype =redef @*/ +#define __PBM_BITS(set) ((set)->bits) + +#define PBM_ALLOC(d) calloc(__PBM_IX (d) + 1, sizeof(__pbm_bits)) +#define PBM_FREE(s) _free(s); +#define PBM_SET(d, s) (__PBM_BITS (s)[__PBM_IX (d)] |= __PBM_MASK (d)) +#define PBM_CLR(d, s) (__PBM_BITS (s)[__PBM_IX (d)] &= ~__PBM_MASK (d)) +#define PBM_ISSET(d, s) ((__PBM_BITS (s)[__PBM_IX (d)] & __PBM_MASK (d)) != 0) + +struct optionStackEntry { + int argc; +/*@only@*/ /*@null@*/ + const char ** argv; +/*@only@*/ /*@null@*/ + pbm_set * argb; + int next; +/*@only@*/ /*@null@*/ + const char * nextArg; +/*@observer@*/ /*@null@*/ + const char * nextCharArg; +/*@dependent@*/ /*@null@*/ + poptItem currAlias; + int stuffed; +}; + +struct poptContext_s { + struct optionStackEntry optionStack[POPT_OPTION_DEPTH]; +/*@dependent@*/ + struct optionStackEntry * os; +/*@owned@*/ /*@null@*/ + const char ** leftovers; + int numLeftovers; + int nextLeftover; +/*@keep@*/ + const struct poptOption * options; + int restLeftover; +/*@only@*/ /*@null@*/ + const char * appName; +/*@only@*/ /*@null@*/ + poptItem aliases; + int numAliases; + int flags; +/*@owned@*/ /*@null@*/ + poptItem execs; + int numExecs; +/*@only@*/ /*@null@*/ + const char ** finalArgv; + int finalArgvCount; + int finalArgvAlloced; +/*@dependent@*/ /*@null@*/ + poptItem doExec; +/*@only@*/ + const char * execPath; + int execAbsolute; +/*@only@*/ /*@relnull@*/ + const char * otherHelp; +/*@null@*/ + pbm_set * arg_strip; +}; + +#ifdef HAVE_LIBINTL_H +#include <libintl.h> +#endif + +#if defined(HAVE_GETTEXT) && !defined(__LCLINT__) +#define _(foo) gettext(foo) +#else +#define _(foo) foo +#endif + +#if defined(HAVE_DCGETTEXT) && !defined(__LCLINT__) +#define D_(dom, str) dgettext(dom, str) +#define POPT_(foo) D_("popt", foo) +#else +#define D_(dom, str) str +#define POPT_(foo) foo +#endif + +#define N_(foo) foo + +#endif diff --git a/libpopt/poptparse.c b/libpopt/poptparse.c new file mode 100644 index 0000000..a0dea80 --- /dev/null +++ b/libpopt/poptparse.c @@ -0,0 +1,227 @@ +/** \ingroup popt + * \file popt/poptparse.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" + +#define POPT_ARGV_ARRAY_GROW_DELTA 5 + +/*@-boundswrite@*/ +int poptDupArgv(int argc, const char **argv, + int * argcPtr, const char *** argvPtr) +{ + size_t nb = (argc + 1) * sizeof(*argv); + const char ** argv2; + char * dst; + int i; + + if (argc <= 0 || argv == NULL) /* XXX can't happen */ + return POPT_ERROR_NOARG; + for (i = 0; i < argc; i++) { + if (argv[i] == NULL) + return POPT_ERROR_NOARG; + nb += strlen(argv[i]) + 1; + } + + dst = malloc(nb); + if (dst == NULL) /* XXX can't happen */ + return POPT_ERROR_MALLOC; + argv2 = (void *) dst; + dst += (argc + 1) * sizeof(*argv); + + /*@-branchstate@*/ + for (i = 0; i < argc; i++) { + argv2[i] = dst; + dst += strlen(strcpy(dst, argv[i])) + 1; + } + /*@=branchstate@*/ + argv2[argc] = NULL; + + if (argvPtr) { + *argvPtr = argv2; + } else { + free(argv2); + argv2 = NULL; + } + if (argcPtr) + *argcPtr = argc; + return 0; +} +/*@=boundswrite@*/ + +/*@-bounds@*/ +int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr) +{ + const char * src; + char quote = '\0'; + int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA; + const char ** argv = malloc(sizeof(*argv) * argvAlloced); + int argc = 0; + int buflen = strlen(s) + 1; + char * buf = memset(alloca(buflen), 0, buflen); + int rc = POPT_ERROR_MALLOC; + + if (argv == NULL) return rc; + argv[argc] = buf; + + for (src = s; *src != '\0'; src++) { + if (quote == *src) { + quote = '\0'; + } else if (quote != '\0') { + if (*src == '\\') { + src++; + if (!*src) { + rc = POPT_ERROR_BADQUOTE; + goto exit; + } + if (*src != quote) *buf++ = '\\'; + } + *buf++ = *src; + } else if (isspace(*src)) { + if (*argv[argc] != '\0') { + buf++, argc++; + if (argc == argvAlloced) { + argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA; + argv = realloc(argv, sizeof(*argv) * argvAlloced); + if (argv == NULL) goto exit; + } + argv[argc] = buf; + } + } else switch (*src) { + case '"': + case '\'': + quote = *src; + /*@switchbreak@*/ break; + case '\\': + src++; + if (!*src) { + rc = POPT_ERROR_BADQUOTE; + goto exit; + } + /*@fallthrough@*/ + default: + *buf++ = *src; + /*@switchbreak@*/ break; + } + } + + if (strlen(argv[argc])) { + argc++, buf++; + } + + rc = poptDupArgv(argc, argv, argcPtr, argvPtr); + +exit: + if (argv) free(argv); + return rc; +} +/*@=bounds@*/ + +/* still in the dev stage. + * return values, perhaps 1== file erro + * 2== line to long + * 3== umm.... more? + */ +int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ int flags) +{ + char line[999]; + char * argstr; + char * p; + char * q; + char * x; + int t; + int argvlen = 0; + size_t maxlinelen = sizeof(line); + size_t linelen; + int maxargvlen = 480; + int linenum = 0; + + *argstrp = NULL; + + /* | this_is = our_line + * p q x + */ + + if (fp == NULL) + return POPT_ERROR_NULLARG; + + argstr = calloc(maxargvlen, sizeof(*argstr)); + if (argstr == NULL) return POPT_ERROR_MALLOC; + + while (fgets(line, (int)maxlinelen, fp) != NULL) { + linenum++; + p = line; + + /* loop until first non-space char or EOL */ + while( *p != '\0' && isspace(*p) ) + p++; + + linelen = strlen(p); + if (linelen >= maxlinelen-1) + return POPT_ERROR_OVERFLOW; /* XXX line too long */ + + if (*p == '\0' || *p == '\n') continue; /* line is empty */ + if (*p == '#') continue; /* comment line */ + + q = p; + + while (*q != '\0' && (!isspace(*q)) && *q != '=') + q++; + + if (isspace(*q)) { + /* a space after the name, find next non space */ + *q++='\0'; + while( *q != '\0' && isspace((int)*q) ) q++; + } + if (*q == '\0') { + /* single command line option (ie, no name=val, just name) */ + q[-1] = '\0'; /* kill off newline from fgets() call */ + argvlen += (t = q - p) + (sizeof(" --")-1); + if (argvlen >= maxargvlen) { + maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2; + argstr = realloc(argstr, maxargvlen); + if (argstr == NULL) return POPT_ERROR_MALLOC; + } + strcat(argstr, " --"); + strcat(argstr, p); + continue; + } + if (*q != '=') + continue; /* XXX for now, silently ignore bogus line */ + + /* *q is an equal sign. */ + *q++ = '\0'; + + /* find next non-space letter of value */ + while (*q != '\0' && isspace(*q)) + q++; + if (*q == '\0') + continue; /* XXX silently ignore missing value */ + + /* now, loop and strip all ending whitespace */ + x = p + linelen; + while (isspace(*--x)) + *x = 0; /* null out last char if space (including fgets() NL) */ + + /* rest of line accept */ + t = x - p; + argvlen += t + (sizeof("' --='")-1); + if (argvlen >= maxargvlen) { + maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2; + argstr = realloc(argstr, maxargvlen); + if (argstr == NULL) return POPT_ERROR_MALLOC; + } + strcat(argstr, " --"); + strcat(argstr, p); + strcat(argstr, "=\""); + strcat(argstr, q); + strcat(argstr, "\""); + } + + *argstrp = argstr; + return 0; +} diff --git a/libpopt/system.h b/libpopt/system.h new file mode 100644 index 0000000..c3d46cb --- /dev/null +++ b/libpopt/system.h @@ -0,0 +1,81 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined (__GLIBC__) && defined(__LCLINT__) +/*@-declundef@*/ +/*@unchecked@*/ +extern __const __int32_t *__ctype_tolower; +/*@unchecked@*/ +extern __const __int32_t *__ctype_toupper; +/*@=declundef@*/ +#endif + +#include <ctype.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> + +#if HAVE_MCHECK_H +#include <mcheck.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef __NeXT +/* access macros are not declared in non posix mode in unistd.h - + don't try to use posix on NeXTstep 3.3 ! */ +#include <libc.h> +#endif + +#if defined(__LCLINT__) +/*@-declundef -incondefs @*/ /* LCL: missing annotation */ +/*@only@*/ /*@out@*/ +void * alloca (size_t __size) + /*@ensures MaxSet(result) == (__size - 1) @*/ + /*@*/; +/*@=declundef =incondefs @*/ +#endif + +/* AIX requires this to be the first thing in the file. */ +#ifndef __GNUC__ +# if HAVE_ALLOCA_H +# include <alloca.h> +# else +# ifdef _AIX +#pragma alloca +# else +# ifndef alloca /* predefined by HP cc +Olibcalls */ +char *alloca (); +# endif +# endif +# endif +#elif defined(__GNUC__) && defined(__STRICT_ANSI__) +#define alloca __builtin_alloca +#endif + +/*@-redecl -redef@*/ +/*@mayexit@*/ /*@only@*/ /*@unused@*/ +char * xstrdup (const char *str) + /*@*/; +/*@=redecl =redef@*/ + +#if HAVE_MCHECK_H && defined(__GNUC__) +#define vmefail() (fprintf(stderr, "virtual memory exhausted.\n"), exit(EXIT_FAILURE), NULL) +#define xstrdup(_str) (strcpy((malloc(strlen(_str)+1) ? : vmefail()), (_str))) +#else +#define xstrdup(_str) strdup(_str) +#endif /* HAVE_MCHECK_H && defined(__GNUC__) */ + +#if HAVE___SECURE_GETENV && !defined(__LCLINT__) +#define getenv(_s) __secure_getenv(_s) +#endif + +#include "popt.h" diff --git a/libutil/Android.mk b/libutil/Android.mk new file mode 100644 index 0000000..29f3bfb --- /dev/null +++ b/libutil/Android.mk @@ -0,0 +1,21 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + op_cpufreq.c \ + op_deviceio.c \ + op_file.c \ + op_fileio.c \ + op_get_time.c \ + op_libiberty.c \ + op_lockfile.c \ + op_popt.c \ + op_string.c \ + op_version.c + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. + +LOCAL_MODULE := libutil + +include $(BUILD_STATIC_LIBRARY) diff --git a/libutil/fscanf.c b/libutil/fscanf.c new file mode 100644 index 0000000..d567324 --- /dev/null +++ b/libutil/fscanf.c @@ -0,0 +1,22 @@ +#include <stdio.h> + +// ugly hack because we don't have fscanf + +int fscanf(FILE* stream, const char* format, int* value) +{ + int c; + int r = 0; + do { + c = fgetc(stream); + if (c>='0' && c<='9') { + r = r*10 + (c-'0'); + continue; + } + break; + } while (1); + + *value = r; + + // gahhhh + return 1; +} diff --git a/libutil/op_cpufreq.c b/libutil/op_cpufreq.c new file mode 100644 index 0000000..78a6333 --- /dev/null +++ b/libutil/op_cpufreq.c @@ -0,0 +1,64 @@ +/** + * @file op_cpufreq.c + * get cpu frequency definition + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "op_fileio.h" + +double op_cpu_frequency(void) +{ + double fval = 0.0; + unsigned long uval; + char * line = NULL; + + FILE * fp = op_try_open_file("/proc/cpuinfo", "r"); + if (!fp) + return 0.0; + + while (1) { + line = op_get_line(fp); + + if (!line) + break; + + if (line[0] == '\0') { + free(line); + continue; + } + + /* x86/parisc/ia64/x86_64 */ + if (sscanf(line, "cpu MHz : %lf", &fval) == 1) + break; + /* ppc/ppc64 */ + if (sscanf(line, "clock : %lfMHz", &fval) == 1) + break; + /* alpha */ + if (sscanf(line, "cycle frequency [Hz] : %lu", &uval) == 1) { + fval = uval / 1E6; + break; + } + /* sparc64 if CONFIG_SMP only */ + if (sscanf(line, "Cpu0ClkTck : %lx", &uval) == 1) { + fval = uval / 1E6; + break; + } + /* s390 doesn't provide cpu freq, checked up to 2.6-test4 */ + + free(line); + } + + if (line) + free(line); + op_close_file(fp); + + return fval; +} diff --git a/libutil/op_cpufreq.h b/libutil/op_cpufreq.h new file mode 100644 index 0000000..0a71172 --- /dev/null +++ b/libutil/op_cpufreq.h @@ -0,0 +1,30 @@ +/** + * @file op_cpufreq.h + * get cpu frequency declaration + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_CPUFREQ_H +#define OP_CPUFREQ_H + +#if defined(__cplusplus) +extern "C" { +#endif + +/* + * return the estimated cpu frequency in Mhz through + * parsing /proc/cpuinfo, return 0 if this information + * is not avalaible e.g. sparc64 with a non SMP kernel + */ +double op_cpu_frequency(void); + +#if defined(__cplusplus) +} +#endif + +#endif /* !OP_CPUFREQ_H */ diff --git a/libutil/op_deviceio.c b/libutil/op_deviceio.c new file mode 100644 index 0000000..8210304 --- /dev/null +++ b/libutil/op_deviceio.c @@ -0,0 +1,42 @@ +/** + * @file op_deviceio.c + * Reading from a special device + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "op_deviceio.h" + +#include <sys/types.h> +#include <fcntl.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +fd_t op_open_device(char const * name) +{ + return open(name, O_RDONLY); +} + + +ssize_t op_read_device(fd_t devfd, void * buf, size_t size) +{ + ssize_t count; + + lseek(devfd, 0, SEEK_SET); + + count = read(devfd, buf, size); + + if (count < 0 && errno != EINTR && errno != EAGAIN) { + perror("oprofiled:op_read_device: "); + exit(EXIT_FAILURE); + } + + return count; +} diff --git a/libutil/op_deviceio.h b/libutil/op_deviceio.h new file mode 100644 index 0000000..f3bd4c2 --- /dev/null +++ b/libutil/op_deviceio.h @@ -0,0 +1,57 @@ +/** + * @file op_deviceio.h + * Reading from a special device + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_DEVICEIO_H +#define OP_DEVICEIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "op_types.h" + +#include <unistd.h> + +/** + * op_open_device - open a special char device for reading + * @param name file name of device file + * + * Open the special file name. Returns the file descriptor + * for the file or -1 on error. + */ +fd_t op_open_device(char const * name); + +/** + * op_read_device - read from a special char device + * @param devfd file descriptor of device + * @param buf buffer + * @param size size of buffer + * + * Read size bytes from a device into buffer buf. + * A seek to the start of the device file is done first + * then a read is requested in one go of size bytes. + * + * It is the caller's responsibility to do further op_read_device() + * calls if the number of bytes read is not what is requested + * (where this is applicable). + * + * The number of bytes read is returned, or a negative number + * on failure (in which case errno will be set). If the call is + * interrupted, then errno will be EINTR, and the client should + * arrange for re-starting the read if necessary. + */ +ssize_t op_read_device(fd_t devfd, void * buf, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_DEVICEIO_H */ diff --git a/libutil/op_file.c b/libutil/op_file.c new file mode 100644 index 0000000..e3e6cb6 --- /dev/null +++ b/libutil/op_file.c @@ -0,0 +1,185 @@ +/** + * @file op_file.c + * Useful file management helpers + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <fnmatch.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <limits.h> + +#include "op_file.h" +#include "op_libiberty.h" + +int op_file_readable(char const * file) +{ + struct stat st; + return !stat(file, &st) && S_ISREG(st.st_mode) && !access(file, R_OK); +} + + +time_t op_get_mtime(char const * file) +{ + struct stat st; + + if (stat(file, &st)) + return 0; + + return st.st_mtime; +} + + +int create_dir(char const * dir) +{ + if (mkdir(dir, 0755)) { + /* FIXME: Does not verify existing is a dir */ + if (errno == EEXIST) + return 0; + return errno; + } + + return 0; +} + + +int create_path(char const * path) +{ + int ret = 0; + + char * str = xstrdup(path); + + char * pos = str[0] == '/' ? str + 1 : str; + + for ( ; (pos = strchr(pos, '/')) != NULL; ++pos) { + *pos = '\0'; + ret = create_dir(str); + *pos = '/'; + if (ret) + break; + } + + free(str); + return ret; +} + + +inline static int is_dot_or_dotdot(char const * name) +{ + return name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); +} + + +/* If non-null is returned, the caller is responsible for freeing + * the memory allocated for the return value. */ +static char * make_pathname_from_dirent(char const * basedir, + struct dirent * ent, + struct stat * st_buf) +{ + int name_len; + char * name; + name_len = strlen(basedir) + strlen("/") + strlen(ent->d_name) + 1; + name = xmalloc(name_len); + sprintf(name, "%s/%s", basedir, ent->d_name); + if (stat(name, st_buf) != 0) { + free(name); + name = NULL; + } + return name; +} + + +int get_matching_pathnames(void * name_list, get_pathname_callback getpathname, + char const * base_dir, char const * filter, + enum recursion_type recursion) +{ +/* The algorithm below depends on recursion type (of which there are 3) + * and whether the current dirent matches the filter. There are 6 possible + * different behaviors, which is why we define 6 case below in the switch + * statement of the algorithm. Actually, when the recursion type is + * MATCH_DIR_ONLY_RECURSION, the behavior is the same, whether or not the dir + * entry matches the filter. However, the behavior of the recursion types + * NO_RECURSION and MATCH_ANY_ENTRY_RECURSION do depend on the dir entry + * filter match, so for simplicity, we perform this match for all recursion + * types and logically OR the match result with the value of the passed + * recursion_type. + */ +#define NO_MATCH 0 +#define MATCH 1 + + DIR * dir; + struct dirent * ent; + struct stat stat_buffer; + int match; + char * name = NULL; + + if (!(dir = opendir(base_dir))) + return -1; + while ((ent = readdir(dir)) != 0) { + if (is_dot_or_dotdot(ent->d_name)) + continue; + if (fnmatch(filter, ent->d_name, 0) == 0) + match = 1; + else + match = 0; + + switch (recursion | match) { + case NO_RECURSION + NO_MATCH: + case MATCH_ANY_ENTRY_RECURSION + NO_MATCH: + // nothing to do but continue the loop + break; + case NO_RECURSION + MATCH: + getpathname(ent->d_name, name_list); + break; + case MATCH_ANY_ENTRY_RECURSION + MATCH: + name = make_pathname_from_dirent(base_dir, ent, + &stat_buffer); + if (name && S_ISDIR(stat_buffer.st_mode) && + !S_ISLNK(stat_buffer.st_mode)) { + get_matching_pathnames( + name_list, getpathname, + name, filter, recursion); + } else { + getpathname(name, name_list); + } + free(name); + break; + case MATCH_DIR_ONLY_RECURSION + NO_MATCH: + case MATCH_DIR_ONLY_RECURSION + MATCH: + name = make_pathname_from_dirent(base_dir, ent, + &stat_buffer); + if (name && S_ISDIR(stat_buffer.st_mode) && + !S_ISLNK(stat_buffer.st_mode)) { + /* Check if full directory name contains + * match to the filter; if so, add it to + * name_list and quit; else, recurse. + */ + if (!fnmatch(filter, name, 0)) { + getpathname(name, name_list); + } else { + get_matching_pathnames( + name_list, getpathname, + name, filter, recursion); + } + } + free(name); + break; + } + } + closedir(dir); + + return 0; +} diff --git a/libutil/op_file.h b/libutil/op_file.h new file mode 100644 index 0000000..d22862c --- /dev/null +++ b/libutil/op_file.h @@ -0,0 +1,109 @@ +/** + * @file op_file.h + * Useful file management helpers + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_FILE_H +#define OP_FILE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +/** + * op_file_readable - is a file readable + * @param file file name + * + * Return true if the given file is readable and regular. + * + * Beware of race conditions ! + */ +int op_file_readable(char const * file); + +/** + * op_get_mtime - get mtime of file + * @param file file name + * + * Returns the mtime of the given file or 0 on failure + */ +time_t op_get_mtime(char const * file); + +/** + * create_dir - create a directory + * @param dir the directory name to create + * + * Returns 0 on success. + */ +int create_dir(char const * dir); + + +/** + * create_path - create a path + * @param path the path to create + * + * create directory for each dir components in path + * the last path component is not considered as a directory + * but as a filename + * + * Returns 0 on success. + */ +int create_path(char const * path); + +/** + * Clients of get_matching_pathnames must provide their own implementation + * of get_pathname_callback. + */ +typedef void (*get_pathname_callback)(char const * pathname, void * name_list); + +/* This enum is intended solely for the use of get_matching_pathnames(), + * bit 0 is reserved for internal use..*/ +enum recursion_type { + NO_RECURSION = 2, + MATCH_ANY_ENTRY_RECURSION = 4, + MATCH_DIR_ONLY_RECURSION = 8, +}; +/** + * @param name_list where to store result + * @param get_pathname_callback client-provided callback function + * @param base_dir directory from where lookup starts + * @param filter a pathname filter + * @param recursion recursion_type -- see above enum and following description: + * NO_RECURSION: Find matching files from passed base_dir and call + * get_pathname_callback to add entry to name_list to be returned. + * MATCH_ANY_ENTRY_RECURSION: Starting at base_dir, for each entry in the + * dir that matches the filter: if entry is of type 'dir', recurse; + * else call get_pathname_callback to add entry to name_list to be + * returned. + * MATCH_DIR_ONLY_RECURSION: Starting at base_dir, if an entry in the + * dir is of type 'dir' and its complete pathname contains a match to + * the filter, call get_pathname_callback to add entry to name_list to + * be returned; else recurse. + * + * Returns 0 on success. + * + * Return a list of pathnames under base_dir, filtered by filter and optionally + * looking in sub-directory. See description above of the recursion_type + * parameter for more details. + * NOTE: For C clients: Your implementation of the get_pathname_callback + * function will probably dynamically allocate storage for elements + * added to name_list. If so, remember to free that memory when it's + * no longer needed. + */ +int get_matching_pathnames(void * name_list, get_pathname_callback, + char const * base_dir, char const * filter, + enum recursion_type recursion); + + +#ifdef __cplusplus +} +#endif + +#endif /* OP_FILE_H */ diff --git a/libutil/op_fileio.c b/libutil/op_fileio.c new file mode 100644 index 0000000..9b3e21d --- /dev/null +++ b/libutil/op_fileio.c @@ -0,0 +1,228 @@ +/** + * @file op_fileio.c + * Reading from / writing to files + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <unistd.h> + +#include "op_fileio.h" + +#include "op_libiberty.h" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +static FILE * op_do_open_file(char const * name, char const * mode, int fatal) +{ + FILE * fp; + + fp = fopen(name, mode); + + if (!fp) { + if (fatal) { + fprintf(stderr,"oprofiled:op_do_open_file: %s: %s", + name, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + return fp; +} + + +FILE * op_try_open_file(char const * name, char const * mode) +{ + return op_do_open_file(name, mode, 0); +} + + +FILE * op_open_file(char const * name, char const * mode) +{ + return op_do_open_file(name, mode, 1); +} + + +void op_close_file(FILE * fp) +{ + if (fclose(fp)) + perror("oprofiled:op_close_file: "); +} + + +void op_write_file(FILE * fp, void const * buf, size_t size) +{ + size_t written; + + if (size == 0) + return; + + written = fwrite(buf, size, 1, fp); + + if (written != 1) { + fprintf(stderr, + "oprofiled:op_write_file: wrote less than expected: %lu bytes.\n", + (unsigned long)size); + exit(EXIT_FAILURE); + } +} + + +void op_write_u8(FILE * fp, u8 val) +{ + op_write_file(fp, &val, sizeof(val)); +} + + +void op_write_u32(FILE * fp, u32 val) +{ + op_write_file(fp, &val, sizeof(val)); +} + + +void op_write_u64(FILE * fp, u64 val) +{ + op_write_file(fp, &val, sizeof(val)); +} + + +u32 op_read_int_from_file(char const * filename, int fatal) +{ + FILE * fp; + u32 value; + + fp = fopen(filename, "r"); + if (fp == NULL) { + if (!fatal) + return (u32)-1; + fprintf(stderr, + "op_read_int_from_file: Failed to open %s, reason %s\n", + filename, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (fscanf(fp, "%u", &value) != 1) { + fclose(fp); + if (!fatal) + return (u32)-1; + fprintf(stderr, + "op_read_int_from_file: Failed to convert contents of file %s to integer\n", + filename); + exit(EXIT_FAILURE); + } + + fclose(fp); + + return value; +} + + +char * op_get_line(FILE * fp) +{ + char * buf; + char * cp; + int c; + size_t max = 512; + + buf = xmalloc(max); + cp = buf; + + while (1) { + switch (c = getc(fp)) { + case EOF: + free(buf); + return NULL; + break; + + case '\n': + case '\0': + *cp = '\0'; + return buf; + break; + + default: + *cp = (char)c; + cp++; + if (((size_t)(cp - buf)) == max) { + buf = xrealloc(buf, max + 128); + cp = buf + max; + max += 128; + } + break; + } + } +} + + +/* FIXME the debug info stuff should be handled by binutils */ +unsigned long +calc_crc32(unsigned long crc, unsigned char * buf, size_t len) +{ + static const unsigned long crc32_table[256] = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, + 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, + 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, + 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, + 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, + 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, + 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, + 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, + 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, + 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, + 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, + 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, + 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, + 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, + 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, + 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, + 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, + 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, + 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, + 0x2d02ef8d + }; + unsigned char * end; + + crc = ~crc & 0xffffffff; + for (end = buf + len; buf < end; ++buf) + crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8); + return ~crc & 0xffffffff; +} diff --git a/libutil/op_fileio.h b/libutil/op_fileio.h new file mode 100644 index 0000000..49b126d --- /dev/null +++ b/libutil/op_fileio.h @@ -0,0 +1,140 @@ +/** + * @file op_fileio.h + * Reading from / writing to files + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_FILEIO_H +#define OP_FILEIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "op_types.h" + +#include <stdio.h> + +/** + * op_try_open_file - open a file + * @param name file name + * @param mode mode string + * + * Open a file name. + * Returns file handle or %NULL on failure. + */ +FILE * op_try_open_file(char const * name, char const * mode); + +/** + * op_open_file - open a file + * @param name file name + * @param mode mode string + * + * Open a file name. + * Failure to open is fatal. + */ +FILE * op_open_file(char const * name, char const * mode); + +/** + * op_read_int_from_file - parse an ASCII value from a file into an integer + * @param filename name of file to parse integer value from + * @param fatal non-zero if any error must be fatal + * + * Reads an ASCII integer from the given file. If an error occur and fatal is + * zero (u32)-1 is returned else the value read in is returned. + */ +u32 op_read_int_from_file(char const * filename, int fatal); + +/** + * op_close_file - close a file + * @param fp file pointer + * + * Closes a file pointer. A non-fatal + * error message is produced if the + * close fails. + */ +void op_close_file(FILE * fp); + +/** + * op_write_file - write to a file + * @param fp file pointer + * @param buf buffer + * @param size nr. of bytes to write + * + * Write size bytes of buffer buf to a file. + * Failure is fatal. + */ +void op_write_file(FILE * fp, void const * buf, size_t size); + +/** + * op_write_u32 - write four bytes to a file + * @param fp file pointer + * @param val value to write + * + * Write an unsigned four-byte value val to a file. + * Failure is fatal. + * + * No byte-swapping is done. + */ +void op_write_u32(FILE * fp, u32 val); + +/** + * op_write_u64 - write eight bytes to a file + * @param fp file pointer + * @param val value to write + * + * Write an unsigned eight-byte value val to a file. + * Failure is fatal. + * + * No byte-swapping is done. + */ +void op_write_u64(FILE * fp, u64 val); + +/** + * op_write_u8 - write a byte to a file + * @param fp file pointer + * @param val value to write + * + * Write an unsigned byte value val to a file. + * Failure is fatal. + */ +void op_write_u8(FILE * fp, u8 val); + +/** + * op_get_line - read an ASCII line from a file + * @param fp file pointer + * + * Get a line of ASCII text from a file. The file is read + * up to the first '\0' or '\n'. A trailing '\n' is deleted. + * + * Returns the dynamically-allocated string containing + * that line. At the end of a file NULL will be returned. + * be returned. + * + * The string returned must be free()d by the caller. + * + * getline() is not a proper solution to replace this function + */ +char * op_get_line(FILE * fp); + +/** + * calc_crc32 + * @param crc current value + * @param buf pointer to buffer + * @param len + * + * Returns current crc computed from the crc argument and the + * characters in len characters in buf. + */ +unsigned long calc_crc32(unsigned long crc, unsigned char * buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_FILEIO_H */ diff --git a/libutil/op_get_time.c b/libutil/op_get_time.c new file mode 100644 index 0000000..c094c63 --- /dev/null +++ b/libutil/op_get_time.c @@ -0,0 +1,24 @@ +/** + * @file op_get_time.c + * Get current time as a string + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "op_get_time.h" + +#include <time.h> + +char * op_get_time(void) +{ + time_t t = time(NULL); + + if (t == -1) + return ""; + + return ctime(&t); +} diff --git a/libutil/op_get_time.h b/libutil/op_get_time.h new file mode 100644 index 0000000..8f96273 --- /dev/null +++ b/libutil/op_get_time.h @@ -0,0 +1,33 @@ +/** + * @file op_get_time.h + * Get current time as a string + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_GET_TIME_H +#define OP_GET_TIME_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * op_get_time - get current date and time + * + * Returns a string representing the current date + * and time, or an empty string on error. + * + * The string is statically allocated and should not be freed. + */ +char * op_get_time(void); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_GET_TIME_H */ diff --git a/libutil/op_growable_buffer.c b/libutil/op_growable_buffer.c new file mode 100644 index 0000000..d138f36 --- /dev/null +++ b/libutil/op_growable_buffer.c @@ -0,0 +1,46 @@ +/** + * @file op_growable_buffer.c + * a growable buffer implementation + * + * @remark Copyright 2007 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#include "op_growable_buffer.h" +#include "op_libiberty.h" + +#include <string.h> +#include <stdlib.h> + +void init_buffer(struct growable_buffer * b) +{ + b->max_size = 0; + b->size = 0; + b->p = NULL; +} + + +void free_buffer(struct growable_buffer * b) +{ + free(b->p); +} + + +static void grow_buffer(struct growable_buffer * b) +{ + size_t new_size = (b->max_size + b->size) * 2; + b->p = xrealloc(b->p, new_size); + b->max_size = new_size; +} + + +void add_data(struct growable_buffer * b, void const * data, size_t len) +{ + size_t old_size = b->size; + b->size += len; + if (b->size > b->max_size) + grow_buffer(b); + memcpy(b->p + old_size, data, len); +} diff --git a/libutil/op_growable_buffer.h b/libutil/op_growable_buffer.h new file mode 100644 index 0000000..491969a --- /dev/null +++ b/libutil/op_growable_buffer.h @@ -0,0 +1,45 @@ +/** + * @file op_growable_buffer.h + * a growable buffer interface + * + * @remark Copyright 2007 OProfile authors + * @remark Read the file COPYING + * + * @author Philippe Elie + */ + +#ifndef OP_GROWABLE_BUFFER_H +#define OP_GROWABLE_BUFFER_H + +#include <stddef.h> + +struct growable_buffer { + void * p; + size_t size; + size_t max_size; +}; + +/** + * init_buffer - initialize an empty buffer + * @param buffer the buffer to initialize + * + * init_buffer do not do any allocation, the first allocation will occur + * when add_data() with a non zero len param will be called. + */ +void init_buffer(struct growable_buffer * buffer); + +/** + * free_buffer - free the memory allocated for this buffer + * @param buffer the buffer to free + */ +void free_buffer(struct growable_buffer * buffer); + +/** + * add_data - add data to this buffer + * @param b the buffer where to add data + * @param data a pointer to the data to add + * @param len number of byte to add to the buffer + */ +void add_data(struct growable_buffer * b, void const * data, size_t len); + +#endif /* !OP_GROWABLE_BUFFER_H */ diff --git a/libutil/op_libiberty.c b/libutil/op_libiberty.c new file mode 100644 index 0000000..0cf45d3 --- /dev/null +++ b/libutil/op_libiberty.c @@ -0,0 +1,38 @@ +/** + * @file op_libiberty.c + * Wrapper for libiberty - always use this instead of + * libiberty.h + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <string.h> + +#include "op_libiberty.h" + +#ifndef HAVE_XCALLOC +/* some system have a valid libiberty without xcalloc */ +void * xcalloc(size_t n_elem, size_t sz) +{ + void * ptr = xmalloc(n_elem * sz); + + memset(ptr, '\0', n_elem * sz); + + return ptr; +} +#endif + +#ifndef HAVE_XMEMDUP +void * xmemdup (void const * input, size_t copy_size, size_t alloc_size) +{ + void * output = xcalloc(1, alloc_size); + + memcpy(output, input, copy_size); + + return output; +} +#endif diff --git a/libutil/op_libiberty.h b/libutil/op_libiberty.h new file mode 100644 index 0000000..ea02a50 --- /dev/null +++ b/libutil/op_libiberty.h @@ -0,0 +1,81 @@ +/** + * @file op_libiberty.h + * Wrapper for libiberty - always use this instead of + * libiberty.h + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_LIBIBERTY_H +#define OP_LIBIBERTY_H + +#include <stddef.h> + +#include "config.h" + +#ifdef MALLOC_ATTRIBUTE_OK +#define OP_ATTRIB_MALLOC __attribute__((malloc)) +#else +#define OP_ATTRIB_MALLOC +#endif + +#ifdef HAVE_LIBIBERTY_H +#include <libiberty.h> +#else + +#ifdef __cplusplus +extern "C" { +#endif + +/* some system have a libiberty.a but no libiberty.h so we must provide + * ourself the missing proto */ +#ifndef HAVE_LIBIBERTY_H + +/* Set the program name used by xmalloc. */ +void xmalloc_set_program_name(char const *); + +/* Allocate memory without fail. If malloc fails, this will print a + message to stderr (using the name set by xmalloc_set_program_name, + if any) and then call xexit. */ +void * xmalloc(size_t) OP_ATTRIB_MALLOC; + +/* Reallocate memory without fail. This works like xmalloc. Note, + realloc type functions are not suitable for attribute malloc since + they may return the same address across multiple calls. */ +void * xrealloc(void *, size_t); + +/* Allocate memory without fail and set it to zero. This works like xmalloc */ +void * xcalloc(size_t, size_t) OP_ATTRIB_MALLOC; + +/* Copy a string into a memory buffer without fail. */ +char * xstrdup(char const *) OP_ATTRIB_MALLOC; + +/** + * Duplicates a region of memory without fail. First, alloc_size bytes + * are allocated, then copy_size bytes from input are copied into + * it, and the new memory is returned. If fewer bytes are copied than were + * allocated, the remaining memory is zeroed. + */ +void * xmemdup(void const *, size_t, size_t) OP_ATTRIB_MALLOC; + +#endif /* !HAVE_LIBIBERTY_H */ + +#ifdef ANDROID +#define xmalloc(s) malloc(s) +#define xrealloc(p,s) realloc(p,s) +#define xstrdup(str) strdup(str) +#define xmalloc_set_program_name(n) +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* !HAVE_LIBIBERTY_H */ + +#endif /* OP_LIBIBERTY_H */ diff --git a/libutil/op_list.h b/libutil/op_list.h new file mode 100644 index 0000000..ed0cd8a --- /dev/null +++ b/libutil/op_list.h @@ -0,0 +1,177 @@ +/** + * @file op_list.h + * Kernel-style lists + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Linux kernel authors + */ + +#ifndef OP_LIST_H +#define OP_LIST_H + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head * next, * prev; +}; + +/** + * list_init - init a new entry + * @param ptr the list to init + * + * Init a list head to create an empty list from it + */ +static __inline__ void list_init(struct list_head * ptr) +{ + ptr->next = ptr; + ptr->prev = ptr; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new_entry, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new_entry; + new_entry->next = next; + new_entry->prev = prev; + prev->next = new_entry; +} + +/** + * list_add - add a new entry + * @param new new entry to be added + * @param head list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static __inline__ void list_add(struct list_head * new_entry, struct list_head * head) +{ + __list_add(new_entry, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @param new new entry to be added + * @param head list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static __inline__ void list_add_tail(struct list_head * new_entry, struct list_head * head) +{ + __list_add(new_entry, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @param entry the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static __inline__ void list_del(struct list_head * entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @param entry the element to delete from the list. + */ +static __inline__ void list_del_init(struct list_head * entry) +{ + __list_del(entry->prev, entry->next); + list_init(entry); +} + +/** + * list_empty - tests whether a list is empty + * @param head the list to test. + */ +static __inline__ int list_empty(struct list_head const * head) +{ + return head->next == head; +} + +/** + * list_splice - join two lists + * @param list the new list to add. + * @param head the place to add it in the first list. + */ +static __inline__ void list_splice(struct list_head * list, struct list_head * head) +{ + struct list_head * first = list->next; + + if (first != list) { + struct list_head * last = list->prev; + struct list_head * at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @param ptr the &struct list_head pointer. + * @param type the type of the struct this is embedded in. + * @param member the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @param pos the &struct list_head to use as a loop counter. + * @param head the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @param pos the &struct list_head to use as a loop counter. + * @param n another &struct list_head to use as temporary storage + * @param head the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) + +#endif /* OP_LIST_H */ diff --git a/libutil/op_lockfile.c b/libutil/op_lockfile.c new file mode 100644 index 0000000..26e3249 --- /dev/null +++ b/libutil/op_lockfile.c @@ -0,0 +1,69 @@ +/** + * @file op_lockfile.c + * PID-based lockfile management + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include "op_lockfile.h" +#include "op_file.h" + +#include <errno.h> + +#include <sys/types.h> +#include <stdio.h> +#include <signal.h> +#include <unistd.h> + +static pid_t op_read_lock_file(char const * file) +{ + FILE * fp; + pid_t value; + + fp = fopen(file, "r"); + if (fp == NULL) + return 0; + + if (fscanf(fp, "%d", &value) != 1) { + fclose(fp); + return 0; + } + + fclose(fp); + + return value; +} + + +int op_write_lock_file(char const * file) +{ + FILE * fp; + + if (op_file_readable(file)) { + pid_t pid = op_read_lock_file(file); + + /* FIXME: ESRCH vs. EPERM */ + if (kill(pid, 0)) { + int err = unlink(file); + fprintf(stderr, "Removing stale lock file %s\n", + file); + if (err) + return err; + } else { + return EEXIST; + } + } + + fp = fopen(file, "w"); + if (!fp) + return errno; + + fprintf(fp, "%d", getpid()); + fclose(fp); + + return 0; +} diff --git a/libutil/op_lockfile.h b/libutil/op_lockfile.h new file mode 100644 index 0000000..fcc269c --- /dev/null +++ b/libutil/op_lockfile.h @@ -0,0 +1,34 @@ +/** + * @file op_lockfile.h + * PID-based lockfile management + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_LOCKFILE_H +#define OP_LOCKFILE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +/** + * op_write_lock_file - write a lock file + * \return errno on failure, or 0 on success + * + * Write the pid into the given lock file. Stale + * lock files are detected and reset. + */ +int op_write_lock_file(char const * file); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_LOCKFILE_H */ diff --git a/libutil/op_popt.c b/libutil/op_popt.c new file mode 100644 index 0000000..de96364 --- /dev/null +++ b/libutil/op_popt.c @@ -0,0 +1,43 @@ +/** + * @file op_popt.c + * Wrapper for libpopt - always use this rather + * than popt.h + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <stdlib.h> +#include "op_libiberty.h" +#include "op_popt.h" + +poptContext op_poptGetContext(char const * name, + int argc, char const ** argv, + struct poptOption const * options, int flags) +{ + poptContext optcon; + int c; + + xmalloc_set_program_name(argv[0]); + +#ifdef CONST_POPT + optcon = poptGetContext(name, argc, argv, options, flags); +#else + optcon = poptGetContext((char *)name, argc, (char **)argv, options, flags); +#endif + + c = poptGetNextOpt(optcon); + + if (c < -1) { + fprintf(stderr, "%s: %s: %s\n", argv[0], + poptBadOption(optcon, POPT_BADOPTION_NOALIAS), + poptStrerror(c)); + poptPrintHelp(optcon, stderr, 0); + exit(EXIT_FAILURE); + } + + return optcon; +} diff --git a/libutil/op_popt.h b/libutil/op_popt.h new file mode 100644 index 0000000..c3dfa6c --- /dev/null +++ b/libutil/op_popt.h @@ -0,0 +1,42 @@ +/** + * @file op_popt.h + * Wrapper for libpopt - always use this rather + * than popt.h + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_POPT_H +#define OP_POPT_H + +#include <popt.h> + +// not in some versions of popt.h +#ifndef POPT_TABLEEND +#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL } +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * op_poptGetContext - wrapper for popt + * + * Use this instead of poptGetContext to cope with + * different popt versions. This also handle unrecognized + * options. All error are fatal. + */ +poptContext op_poptGetContext(char const * name, + int argc, char const ** argv, + struct poptOption const * options, int flags); + +#ifdef __cplusplus +} +#endif + +#endif /* OP_POPT_H */ diff --git a/libutil/op_string.c b/libutil/op_string.c new file mode 100644 index 0000000..d440299 --- /dev/null +++ b/libutil/op_string.c @@ -0,0 +1,62 @@ +/** + * @file op_string.c + * general purpose C string handling implementation. + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <string.h> +#include "op_libiberty.h" + + +char * op_xstrndup(char const * s, size_t len) +{ + return xmemdup(s, len, len + 1); +} + + +size_t op_hash_string(char const * str) +{ + size_t hash = 0; + for (; *str; ++str) + hash ^= (hash << 16) ^ (hash >> 8) ^ *str; + return hash; +} + + +int strisprefix(char const * str, char const * prefix) +{ + return strstr(str, prefix) == str; +} + + +char const * skip_ws(char const * c) +{ + while (*c == ' ' || *c == '\t' || *c == '\n') + ++c; + return c; +} + + +char const * skip_nonws(char const * c) +{ + while (*c && *c != ' ' && *c != '\t' && *c != '\n') + ++c; + return c; +} + + +int empty_line(char const * c) +{ + return !*skip_ws(c); +} + + +int comment_line(char const * c) +{ + return *skip_ws(c) == '#'; +} diff --git a/libutil/op_string.h b/libutil/op_string.h new file mode 100644 index 0000000..993fa6f --- /dev/null +++ b/libutil/op_string.h @@ -0,0 +1,81 @@ +/** + * @file op_string.h + * general purpose C string handling declarations. + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_STRING_H +#define OP_STRING_H + +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @param s: input string + * @param len: len char to copy + * + * Allocate and copy len character from s to a newly allocated buffer then + * append a '\0' terminator. Return the newly allocated string + */ +char * op_xstrndup(char const * s, size_t len); + +/** + * @param s: string to hash + * + * Generate a hash code from a string + */ +size_t op_hash_string(char const * s); + +/** + * @param str: string to test + * @param prefix: prefix string + * + * return non zero if prefix parameters is a prefix of str + */ +int strisprefix(char const * str, char const * prefix); + +/** + * @param c: input string + * + * return a pointer to the first location in c which is not a blank space + * where blank space are in " \t\n" + */ +char const * skip_ws(char const * c); + +/** + * @param c: input string + * + * return a pointer to the first location in c which is a blank space + * where blank space are in " \t\n" + */ +char const * skip_nonws(char const * c); + +/** + * @param c: input string + * + * return non zero if c string contains only blank space + * where blank space are in " \t\n" + */ +int empty_line(char const * c); + +/** + * @param c: input string + * + * return non zero if c string is a comment. Comment are lines with optional + * blank space at left then a '#' character. Blank space are in " \t\n" + */ +int comment_line(char const * c); + +#ifdef __cplusplus +} +#endif + +#endif /* !OP_STRING_H */ diff --git a/libutil/op_types.h b/libutil/op_types.h new file mode 100644 index 0000000..c025b23 --- /dev/null +++ b/libutil/op_types.h @@ -0,0 +1,37 @@ +/** + * @file op_types.h + * General-utility types + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_TYPES_H +#define OP_TYPES_H + +#ifndef __KERNEL__ + +#include <sys/types.h> + +/*@{\name miscellaneous types */ +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +typedef int fd_t; +/*@}*/ + +/** generic type for holding addresses */ +typedef unsigned long long vma_t; + +/** generic type to hold a sample count in pp tools */ +typedef u64 count_type; + +#else +#include <linux/types.h> +#endif + +#endif /* OP_TYPES_H */ diff --git a/libutil/op_version.c b/libutil/op_version.c new file mode 100644 index 0000000..99a844e --- /dev/null +++ b/libutil/op_version.c @@ -0,0 +1,24 @@ +/** + * @file op_version.c + * output version string + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "op_version.h" +#include "config.h" + +void show_version(char const * app_name) +{ + /* Do not change the version format: it is documented in html doc */ + printf("%s: " PACKAGE " " VERSION " compiled on " + __DATE__ " " __TIME__ "\n", app_name); + exit(EXIT_SUCCESS); +} diff --git a/libutil/op_version.h b/libutil/op_version.h new file mode 100644 index 0000000..43a8365 --- /dev/null +++ b/libutil/op_version.h @@ -0,0 +1,26 @@ +/** + * @file op_version.h + * output version string + * + * @remark Copyright 2003 OProfile authors + * @remark Read the file COPYING + * + * @author John Levon + * @author Philippe Elie + */ + +#ifndef OP_VERSION_H +#define OP_VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** output the version string */ +void show_version(char const * app_name); + +#ifdef __cplusplus +} +#endif + +#endif /* !OP_VERSION_H */ diff --git a/opcontrol/Android.mk b/opcontrol/Android.mk new file mode 100644 index 0000000..56211ad --- /dev/null +++ b/opcontrol/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + opcontrol.cpp + +LOCAL_STATIC_LIBRARIES := \ + libpopt libutil libdb libabi libop + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/.. \ + $(LOCAL_PATH)/../libop + +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE:= opcontrol + +include $(BUILD_EXECUTABLE) diff --git a/opcontrol/opcontrol.cpp b/opcontrol/opcontrol.cpp new file mode 100644 index 0000000..2d9cb2f --- /dev/null +++ b/opcontrol/opcontrol.cpp @@ -0,0 +1,542 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Binary implementation of the original opcontrol script due to missing tools + * like awk, test, etc. + */ + +#include <unistd.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> + +#include "op_config.h" + +#if 0 +#define verbose(fmt...) printf(fmt) +#else +#define verbose(fmt...) +#endif + +/* Experiments found that using a small interval may hang the device, and the + * more events tracked simultaneously, the longer the interval has to be. + */ +int min_count[3] = {150000, 200000, 250000}; +int list_events; +int show_usage; +int setup; +int quick; +int num_events; +int start; +int stop; +int reset; + +int selected_events[3]; +int selected_counts[3]; + +char kernel_range[512]; +char vmlinux[512]; + +struct option long_options[] = { + {"help", 0, &show_usage, 1}, + {"list-events", 0, &list_events, 1}, + {"reset", 0, &reset, 1}, + {"setup", 0, &setup, 1}, + {"quick", 0, &quick, 1}, + {"event", 1, 0, 'e'}, + {"vmlinux", 1, 0, 'v'}, + {"kernel-range", 1, 0, 'r'}, + {"start", 0, &start, 1}, + {"stop", 0, &stop, 1}, + {"shutdown", 0, 0, 'h'}, + {"status", 0, 0, 't'}, + {0, 0, 0, 0}, +}; + +struct event_info { + int id; + const char *name; + const char *explanation; +} event_info[] = { + {0x00, "IFU_IFETCH_MISS", + "number of instruction fetch misses"}, + {0x01, "CYCLES_IFU_MEM_STALL", + "cycles instruction fetch pipe is stalled"}, + {0x02, "CYCLES_DATA_STALL", + "cycles stall occurs for due to data dependency"}, + {0x03, "ITLB_MISS", + "number of Instruction MicroTLB misses"}, + {0x04, "DTLB_MISS", + "number of Data MicroTLB misses"}, + {0x05, "BR_INST_EXECUTED", + "branch instruction executed w/ or w/o program flow change"}, + {0x06, "BR_INST_MISS_PRED", + "branch mispredicted"}, + {0x07, "INSN_EXECUTED", + "instructions executed"}, + {0x09, "DCACHE_ACCESS", + "data cache access, cacheable locations"}, + {0x0a, "DCACHE_ACCESS_ALL", + "data cache access, all locations"}, + {0x0b, "DCACHE_MISS", + "data cache miss"}, + {0x0c, "DCACHE_WB", + "data cache writeback, 1 event for every half cacheline"}, + {0x0d, "PC_CHANGE", + "number of times the program counter was changed without a mode switch"}, + {0x0f, "TLB_MISS", + "Main TLB miss"}, + {0x10, "EXP_EXTERNAL", + "Explicit external data access"}, + {0x11, "LSU_STALL", + "cycles stalled because Load Store request queue is full"}, + {0x12, "WRITE_DRAIN", + "Times write buffer was drained"}, + {0xff, "CPU_CYCLES", + "clock cycles counter"}, +}; + +void usage() { + printf("\nopcontrol: usage:\n" + " --list-events list event types\n" + " --help this message\n" + " --setup setup directories\n" + " --quick setup and select CPU_CYCLES:150000\n" + " --status show configuration\n" + " --start start data collection\n" + " --stop stop data collection\n" + " --reset clears out data from current session\n" + " --shutdown kill the oprofile daeman\n" + " --event=eventspec\n" + " Choose an event. May be specified multiple times.\n" + " eventspec is in the form of name[:count], where :\n" + " name: event name, see \"opcontrol --list-events\"\n" + " count: reset counter value\n" + " --vmlinux=file vmlinux kernel image\n" + " --kernel-range=start,end\n" + " kernel range vma address in hexadecimal\n" + ); +} + +void setup_session_dir() { + int fd; + + fd = open(OP_DATA_DIR, O_RDONLY); + if (fd != -1) { + system("rm -r "OP_DATA_DIR); + close(fd); + } + + if (mkdir(OP_DATA_DIR, 755)) { + fprintf(stderr, "Cannot create directory \"%s\": %s\n", + OP_DATA_DIR, strerror(errno)); + } + if (mkdir(OP_DATA_DIR"/samples", 644)) { + fprintf(stderr, "Cannot create directory \"%s\": %s\n", + OP_DATA_DIR"/samples", strerror(errno)); + } +} + +int do_setup() { + char dir[1024]; + + setup_session_dir(); + + if (mkdir(OP_DRIVER_BASE, 644)) { + fprintf(stderr, "Cannot create directory "OP_DRIVER_BASE": %s\n", + strerror(errno)); + return -1; + } + if (system("mount -t oprofilefs nodev "OP_DRIVER_BASE)) { + return -1; + } + return 0; +} + +void do_list_events() +{ + unsigned int i; + + printf("%-20s: %s\n", "name", "meaning"); + printf("----------------------------------------" + "--------------------------------------\n"); + for (i = 0; i < sizeof(event_info)/sizeof(struct event_info); i++) { + printf("%-20s: %s\n", event_info[i].name, event_info[i].explanation); + } +} + +int find_event_id_from_name(const char *name) { + unsigned int i; + + for (i = 0; i < sizeof(event_info)/sizeof(struct event_info); i++) { + if (!strcmp(name, event_info[i].name)) { + return i; + } + } + return -1; +} + +const char * find_event_name_from_id(int id) { + unsigned int i; + + for (i = 0; i < sizeof(event_info)/sizeof(struct event_info); i++) { + if (event_info[i].id == id) { + return event_info[i].name; + } + } + return NULL; +} + +int process_event(const char *event_spec) { + char event_name[512]; + char count_name[512]; + unsigned int i; + int event_id; + int count_val; + + strncpy(event_name, event_spec, 512); + count_name[0] = 0; + + /* First, check if the name is followed by ":" */ + for (i = 0; i < strlen(event_name); i++) { + if (event_name[i] == 0) { + break; + } + if (event_name[i] == ':') { + strncpy(count_name, event_name+i+1, 512); + event_name[i] = 0; + break; + } + } + event_id = find_event_id_from_name(event_name); + if (event_id == -1) { + fprintf(stderr, "Unknown event name: %s\n", event_name); + return -1; + } + + /* Use defualt count */ + if (count_name[0] == 0) { + count_val = min_count[0]; + } else { + count_val = atoi(count_name); + } + + selected_events[num_events] = event_id; + selected_counts[num_events++] = count_val; + verbose("event_id is %d\n", event_id); + verbose("count_val is %d\n", count_val); + return 0; +} + +int echo_dev(const char* str, int val, const char* file, int counter) +{ + char fullname[512]; + char content[128]; + int fd; + + if (counter >= 0) { + snprintf(fullname, 512, OP_DRIVER_BASE"/%d/%s", counter, file); + } + else { + snprintf(fullname, 512, OP_DRIVER_BASE"/%s", file); + } + fd = open(fullname, O_WRONLY); + if (fd<0) { + fprintf(stderr, "Cannot open %s: %s\n", fullname, strerror(errno)); + return fd; + } + if (str == 0) { + sprintf(content, "%d", val); + } + else { + strncpy(content, str, 128); + } + verbose("Configure %s (%s)\n", fullname, content); + write(fd, content, strlen(content)); + close(fd); + return 0; +} + +int read_num(const char* file) +{ + char buffer[256]; + int fd = open(file, O_RDONLY); + if (fd<0) return -1; + int rd = read(fd, buffer, sizeof(buffer)-1); + buffer[rd] = 0; + return atoi(buffer); +} + +void do_status() +{ + int num; + char fullname[512]; + int i; + + printf("Driver directory: %s\n", OP_DRIVER_BASE); + printf("Session directory: %s\n", OP_DATA_DIR); + for (i = 0; i < 3; i++) { + sprintf(fullname, OP_DRIVER_BASE"/%d/enabled", i); + num = read_num(fullname); + if (num > 0) { + printf("Counter %d:\n", i); + + /* event name */ + sprintf(fullname, OP_DRIVER_BASE"/%d/event", i); + num = read_num(fullname); + printf(" name: %s\n", find_event_name_from_id(num)); + + /* profile interval */ + sprintf(fullname, OP_DRIVER_BASE"/%d/count", i); + num = read_num(fullname); + printf(" count: %d\n", num); + } + else { + printf("Counter %d disabled\n", i); + } + } + + num = read_num(OP_DATA_DIR"/lock"); + if (num >= 0) { + int fd; + /* Still needs to check if this lock is left-over */ + sprintf(fullname, "/proc/%d", num); + fd = open(fullname, O_RDONLY); + if (fd == -1) { + printf("Session directory is not clean - do \"opcontrol --setup\"" + " before you continue\n"); + return; + } + else { + close(fd); + printf("oprofiled pid: %d\n", num); + num = read_num(OP_DRIVER_BASE"/enable"); + printf("profiler is%s running\n", num == 0 ? " not" : ""); + num = read_num(OP_DRIVER_BASE"/stats/cpu0/sample_received"); + printf(" %9u samples received\n", num); + num = read_num(OP_DRIVER_BASE"/stats/cpu0/sample_lost_overflow"); + printf(" %9u samples lost overflow\n", num); +#if 0 + /* FIXME - backtrace seems broken */ + num = read_num(OP_DRIVER_BASE"/stats/cpu0/backtrace_aborted"); + printf(" %9u backtrace aborted\n", num); + num = read_num(OP_DRIVER_BASE"/backtrace_depth"); + printf(" %9u backtrace_depth\n", num); +#endif + } + } + else { + printf("oprofiled is not running\n"); + } +} + +void do_reset() +{ + int fd; + + fd = open(OP_DATA_DIR"/samples/current", O_RDONLY); + if (fd == -1) { + return; + } + close(fd); + system("rm -r "OP_DATA_DIR"/samples/current"); +} + +int main(int argc, char * const argv[]) +{ + int option_index; + char command[1024]; + + /* Initialize default strings */ + strcpy(vmlinux, "--no-vmlinux"); + strcpy(kernel_range, ""); + + while (1) { + int c = getopt_long(argc, argv, "", long_options, &option_index); + if (c == -1) { + break; + } + switch (c) { + case 0: + break; + /* --event */ + case 'e': + if (num_events == 3) { + fprintf(stderr, "More than 3 events specified\n"); + exit(1); + } + if (process_event(optarg)) { + exit(1); + } + break; + /* --vmlinux */ + case 'v': + sprintf(vmlinux, "-k %s", optarg); + break; + /* --kernel-range */ + case 'r': + sprintf(kernel_range, "-r %s", optarg); + break; + /* --shutdown */ + case 'h': { + int pid = read_num(OP_DATA_DIR"/lock"); + if (pid >= 0) { + kill(pid, SIGKILL); + } + setup_session_dir(); + break; + } + /* --status */ + case 't': + do_status(); + break; + default: + usage(); + exit(1); + } + } + verbose("list_events = %d\n", list_events); + verbose("setup = %d\n", setup); + + if (list_events) { + do_list_events(); + } + + if (quick) { + process_event("CPU_CYCLES"); + setup = 1; + } + + if (reset) { + do_reset(); + } + + if (show_usage) { + usage(); + } + + if (setup) { + if (do_setup()) { + fprintf(stderr, "do_setup failed"); + exit(1); + } + } + + if (num_events != 0) { + int i; + + strcpy(command, "oprofiled --session-dir="OP_DATA_DIR); + + /* Since counter #3 can only handle CPU_CYCLES, check and shuffle the + * order a bit so that the maximal number of events can be profiled + * simultaneously + */ + if (num_events == 3) { + for (i = 0; i < num_events; i++) { + int event_idx = selected_events[i]; + + if (event_info[event_idx].id == 0xff) { + break; + } + } + + /* No CPU_CYCLES is found */ + if (i == 3) { + fprintf(stderr, "You can only specify three events if one of " + "them is CPU_CYCLES\n"); + exit(1); + } + /* Swap CPU_CYCLES to counter #2 (starting from #0)*/ + else if (i != 2) { + int temp; + + temp = selected_events[2]; + selected_events[2] = selected_events[i]; + selected_events[i] = temp; + + temp = selected_counts[2]; + selected_counts[2] = selected_counts[i]; + selected_counts[i] = temp; + } + } + + + /* Configure the counters and enable them */ + for (i = 0; i < num_events; i++) { + int event_idx = selected_events[i]; + int setup_result = 0; + + if (i == 0) { + snprintf(command+strlen(command), 1024 - strlen(command), + " --events="); + } + else { + snprintf(command+strlen(command), 1024 - strlen(command), + ","); + } + /* Compose name:id:count:unit_mask:kernel:user, something like + * --events=CYCLES_DATA_STALL:2:0:200000:0:1:1,.... + */ + snprintf(command+strlen(command), 1024 - strlen(command), + "%s:%d:%d:%d:0:1:1", + event_info[event_idx].name, + event_info[event_idx].id, + i, + selected_counts[i]); + + setup_result |= echo_dev("1", 0, "user", i); + setup_result |= echo_dev("1", 0, "kernel", i); + setup_result |= echo_dev("0", 0, "unit_mask", i); + setup_result |= echo_dev("1", 0, "enabled", i); + setup_result |= echo_dev(NULL, selected_counts[i], "count", i); + setup_result |= echo_dev(NULL, event_info[event_idx].id, + "event", i); + if (setup_result) { + fprintf(stderr, "Counter configuration failed for %s\n", + event_info[event_idx].name); + fprintf(stderr, "Did you do \"opcontrol --setup\" first?\n"); + exit(1); + } + } + + /* Disable the unused counters */ + for (i = num_events; i < 3; i++) { + echo_dev("0", 0, "enabled", i); + } + + snprintf(command+strlen(command), 1024 - strlen(command), " %s", + vmlinux); + if (kernel_range[0]) { + snprintf(command+strlen(command), 1024 - strlen(command), " %s", + kernel_range); + } + verbose("command: %s\n", command); + system(command); + } + + if (start) { + echo_dev("1", 0, "enable", -1); + } + + if (stop) { + echo_dev("0", 0, "enable", -1); + } +} diff --git a/opimport_pull b/opimport_pull new file mode 100755 index 0000000..7dbac4a --- /dev/null +++ b/opimport_pull @@ -0,0 +1,70 @@ +#!/usr/bin/python2.4 -E + +import os +import re +import sys + +def PrintUsage(): + print "Usage:" + sys.argv[0] + " dir" + print " dir: directory on the host to store profile results" + +if (len(sys.argv) != 2): + PrintUsage() + sys.exit(1) + +try: + oprofile_event_dir = os.environ['OPROFILE_EVENTS_DIR'] +except: + print "OPROFILE_EVENTS_DIR not set. Run \". envsetup.sh\" first" + sys.exit(1) + +output_dir = sys.argv[1]; + +try: + os.makedirs(output_dir) +except: + if os.path.exists(output_dir): + print "Directory already exists:", output_dir + else: + print "Cannot create", output_dir + sys.exit(1) + +# get the samples off the phone +result = os.system("adb pull /data/oprofile/samples " + output_dir + \ + "/raw_samples > /dev/null 2>&1") +if result != 0: + print "adb pull failure, exiting" + sys.exit(1) + +# enter the destination directory +os.chdir(output_dir) +stream = os.popen("find raw_samples -type f -name \*all") + +# now all the sample files are on the host, we need to invoke opimport one at a +# time to convert the content from the ARM abi to x86 ABI + +# break the full filename into: +# 1: leading dir: "raw_samples" +# 2: intermediate dirs: "/blah/blah/blah" +# 3: filename: e.g. "CPU_CYCLES.150000.0.all.all.all" +pattern = re.compile("(^raw_samples)(.*)/(.*)$") +for line in stream: + match = pattern.search(line) + leading_dir = match.group(1) + middle_part = match.group(2) + file_name = match.group(3) + + dir = "samples" + middle_part + + # if multiple events are collected the directory could have been setup + if not os.path.exists(dir): + os.makedirs(dir) + + cmd = oprofile_event_dir + "/bin/opimport -a " + oprofile_event_dir + \ + "/abi/arm_abi -o samples" + middle_part + "/" + file_name + " " + line + os.system(cmd) + +stream.close() + +# short summary of profiling results +os.system(oprofile_event_dir + "/bin/opreport --session-dir=.") @@ -0,0 +1,564 @@ +/** \file popt/popt.h + * \ingroup popt + */ + +/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#ifndef H_POPT +#define H_POPT + +#include <stdio.h> /* for FILE * */ + +#define POPT_OPTION_DEPTH 10 + +/** \ingroup popt + * \name Arg type identifiers + */ +/*@{*/ +#define POPT_ARG_NONE 0 /*!< no arg */ +#define POPT_ARG_STRING 1 /*!< arg will be saved as string */ +#define POPT_ARG_INT 2 /*!< arg will be converted to int */ +#define POPT_ARG_LONG 3 /*!< arg will be converted to long */ +#define POPT_ARG_INCLUDE_TABLE 4 /*!< arg points to table */ +#define POPT_ARG_CALLBACK 5 /*!< table-wide callback... must be + set first in table; arg points + to callback, descrip points to + callback data to pass */ +#define POPT_ARG_INTL_DOMAIN 6 /*!< set the translation domain + for this table and any + included tables; arg points + to the domain string */ +#define POPT_ARG_VAL 7 /*!< arg should take value val */ +#define POPT_ARG_FLOAT 8 /*!< arg will be converted to float */ +#define POPT_ARG_DOUBLE 9 /*!< arg will be converted to double */ + +#define POPT_ARG_MASK 0x0000FFFF +/*@}*/ + +/** \ingroup popt + * \name Arg modifiers + */ +/*@{*/ +#define POPT_ARGFLAG_ONEDASH 0x80000000 /*!< allow -longoption */ +#define POPT_ARGFLAG_DOC_HIDDEN 0x40000000 /*!< don't show in help/usage */ +#define POPT_ARGFLAG_STRIP 0x20000000 /*!< strip this arg from argv(only applies to long args) */ +#define POPT_ARGFLAG_OPTIONAL 0x10000000 /*!< arg may be missing */ + +#define POPT_ARGFLAG_OR 0x08000000 /*!< arg will be or'ed */ +#define POPT_ARGFLAG_NOR 0x09000000 /*!< arg will be nor'ed */ +#define POPT_ARGFLAG_AND 0x04000000 /*!< arg will be and'ed */ +#define POPT_ARGFLAG_NAND 0x05000000 /*!< arg will be nand'ed */ +#define POPT_ARGFLAG_XOR 0x02000000 /*!< arg will be xor'ed */ +#define POPT_ARGFLAG_NOT 0x01000000 /*!< arg will be negated */ +#define POPT_ARGFLAG_LOGICALOPS \ + (POPT_ARGFLAG_OR|POPT_ARGFLAG_AND|POPT_ARGFLAG_XOR) + +#define POPT_BIT_SET (POPT_ARG_VAL|POPT_ARGFLAG_OR) + /*!< set arg bit(s) */ +#define POPT_BIT_CLR (POPT_ARG_VAL|POPT_ARGFLAG_NAND) + /*!< clear arg bit(s) */ + +#define POPT_ARGFLAG_SHOW_DEFAULT 0x00800000 /*!< show default value in --help */ + +/*@}*/ + +/** \ingroup popt + * \name Callback modifiers + */ +/*@{*/ +#define POPT_CBFLAG_PRE 0x80000000 /*!< call the callback before parse */ +#define POPT_CBFLAG_POST 0x40000000 /*!< call the callback after parse */ +#define POPT_CBFLAG_INC_DATA 0x20000000 /*!< use data from the include line, + not the subtable */ +#define POPT_CBFLAG_SKIPOPTION 0x10000000 /*!< don't callback with option */ +#define POPT_CBFLAG_CONTINUE 0x08000000 /*!< continue callbacks with option */ +/*@}*/ + +/** \ingroup popt + * \name Error return values + */ +/*@{*/ +#define POPT_ERROR_NOARG -10 /*!< missing argument */ +#define POPT_ERROR_BADOPT -11 /*!< unknown option */ +#define POPT_ERROR_OPTSTOODEEP -13 /*!< aliases nested too deeply */ +#define POPT_ERROR_BADQUOTE -15 /*!< error in paramter quoting */ +#define POPT_ERROR_ERRNO -16 /*!< errno set, use strerror(errno) */ +#define POPT_ERROR_BADNUMBER -17 /*!< invalid numeric value */ +#define POPT_ERROR_OVERFLOW -18 /*!< number too large or too small */ +#define POPT_ERROR_BADOPERATION -19 /*!< mutually exclusive logical operations requested */ +#define POPT_ERROR_NULLARG -20 /*!< opt->arg should not be NULL */ +#define POPT_ERROR_MALLOC -21 /*!< memory allocation failed */ +/*@}*/ + +/** \ingroup popt + * \name poptBadOption() flags + */ +/*@{*/ +#define POPT_BADOPTION_NOALIAS (1 << 0) /*!< don't go into an alias */ +/*@}*/ + +/** \ingroup popt + * \name poptGetContext() flags + */ +/*@{*/ +#define POPT_CONTEXT_NO_EXEC (1 << 0) /*!< ignore exec expansions */ +#define POPT_CONTEXT_KEEP_FIRST (1 << 1) /*!< pay attention to argv[0] */ +#define POPT_CONTEXT_POSIXMEHARDER (1 << 2) /*!< options can't follow args */ +#define POPT_CONTEXT_ARG_OPTS (1 << 4) /*!< return args as options with value 0 */ +/*@}*/ + +/** \ingroup popt + */ +struct poptOption { +/*@observer@*/ /*@null@*/ + const char * longName; /*!< may be NULL */ + char shortName; /*!< may be NUL */ + int argInfo; +/*@shared@*/ /*@null@*/ + void * arg; /*!< depends on argInfo */ + int val; /*!< 0 means don't return, just update flag */ +/*@observer@*/ /*@null@*/ + const char * descrip; /*!< description for autohelp -- may be NULL */ +/*@observer@*/ /*@null@*/ + const char * argDescrip; /*!< argument description for autohelp */ +}; + +/** \ingroup popt + * A popt alias argument for poptAddAlias(). + */ +struct poptAlias { +/*@owned@*/ /*@null@*/ + const char * longName; /*!< may be NULL */ + char shortName; /*!< may be NUL */ + int argc; +/*@owned@*/ + const char ** argv; /*!< must be free()able */ +}; + +/** \ingroup popt + * A popt alias or exec argument for poptAddItem(). + */ +/*@-exporttype@*/ +typedef struct poptItem_s { + struct poptOption option; /*!< alias/exec name(s) and description. */ + int argc; /*!< (alias) no. of args. */ +/*@owned@*/ + const char ** argv; /*!< (alias) args, must be free()able. */ +} * poptItem; +/*@=exporttype@*/ + +/** \ingroup popt + * \name Auto-generated help/usage + */ +/*@{*/ + +/** + * Empty table marker to enable displaying popt alias/exec options. + */ +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption poptAliasOptions[]; +/*@=exportvar@*/ +#define POPT_AUTOALIAS { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptAliasOptions, \ + 0, "Options implemented via popt alias/exec:", NULL }, + +/** + * Auto help table options. + */ +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption poptHelpOptions[]; +/*@=exportvar@*/ + +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption * poptHelpOptionsI18N; +/*@=exportvar@*/ + +#define POPT_AUTOHELP { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptHelpOptions, \ + 0, "Help options:", NULL }, + +#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL } +/*@}*/ + +/** \ingroup popt + */ +/*@-exporttype@*/ +typedef /*@abstract@*/ struct poptContext_s * poptContext; +/*@=exporttype@*/ + +/** \ingroup popt + */ +#ifndef __cplusplus +/*@-exporttype -typeuse@*/ +typedef struct poptOption * poptOption; +/*@=exporttype =typeuse@*/ +#endif + +/*@-exportconst@*/ +enum poptCallbackReason { + POPT_CALLBACK_REASON_PRE = 0, + POPT_CALLBACK_REASON_POST = 1, + POPT_CALLBACK_REASON_OPTION = 2 +}; +/*@=exportconst@*/ + +#ifdef __cplusplus +extern "C" { +#endif +/*@-type@*/ + +/** \ingroup popt + * Table callback prototype. + * @param con context + * @param reason reason for callback + * @param opt option that triggered callback + * @param arg @todo Document. + * @param data @todo Document. + */ +typedef void (*poptCallbackType) (poptContext con, + enum poptCallbackReason reason, + /*@null@*/ const struct poptOption * opt, + /*@null@*/ const char * arg, + /*@null@*/ const void * data) + /*@globals internalState @*/ + /*@modifies internalState @*/; + +/** \ingroup popt + * Initialize popt context. + * @param name context name (usually argv[0] program name) + * @param argc no. of arguments + * @param argv argument array + * @param options address of popt option table + * @param flags or'd POPT_CONTEXT_* bits + * @return initialized popt context + */ +/*@only@*/ /*@null@*/ +poptContext poptGetContext( + /*@dependent@*/ /*@keep@*/ const char * name, + int argc, /*@dependent@*/ /*@keep@*/ const char ** argv, + /*@dependent@*/ /*@keep@*/ const struct poptOption * options, + int flags) + /*@*/; + +/** \ingroup popt + * Reinitialize popt context. + * @param con context + */ +/*@unused@*/ +void poptResetContext(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return value of next option found. + * @param con context + * @return next option val, -1 on last item, POPT_ERROR_* on error + */ +int poptGetNextOpt(/*@null@*/poptContext con) + /*@globals fileSystem, internalState @*/ + /*@modifies con, fileSystem, internalState @*/; + +/** \ingroup popt + * Return next option argument (if any). + * @param con context + * @return option argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptGetOptArg(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return next argument. + * @param con context + * @return next argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptGetArg(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Peek at current argument. + * @param con context + * @return current argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptPeekArg(/*@null@*/poptContext con) + /*@*/; + +/** \ingroup popt + * Return remaining arguments. + * @param con context + * @return argument array, NULL terminated + */ +/*@observer@*/ /*@null@*/ +const char ** poptGetArgs(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return the option which caused the most recent error. + * @param con context + * @param flags + * @return offending option + */ +/*@observer@*/ +const char * poptBadOption(/*@null@*/poptContext con, int flags) + /*@*/; + +/** \ingroup popt + * Destroy context. + * @param con context + * @return NULL always + */ +/*@null@*/ +poptContext poptFreeContext( /*@only@*/ /*@null@*/ poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Add arguments to context. + * @param con context + * @param argv argument array, NULL terminated + * @return 0 on success, POPT_ERROR_OPTSTOODEEP on failure + */ +/*@unused@*/ +int poptStuffArgs(poptContext con, /*@keep@*/ const char ** argv) + /*@modifies con @*/; + +/** \ingroup popt + * Add alias to context. + * @todo Pass alias by reference, not value. + * @deprecated Use poptAddItem instead. + * @param con context + * @param alias alias to add + * @param flags (unused) + * @return 0 on success + */ +/*@unused@*/ +int poptAddAlias(poptContext con, struct poptAlias alias, int flags) + /*@modifies con @*/; + +/** \ingroup popt + * Add alias/exec item to context. + * @param con context + * @param newItem alias/exec item to add + * @param flags 0 for alias, 1 for exec + * @return 0 on success + */ +int poptAddItem(poptContext con, poptItem newItem, int flags) + /*@modifies con @*/; + +/** \ingroup popt + * Read configuration file. + * @param con context + * @param fn file name to read + * @return 0 on success, POPT_ERROR_ERRNO on failure + */ +int poptReadConfigFile(poptContext con, const char * fn) + /*@globals errno, fileSystem, internalState @*/ + /*@modifies con->execs, con->numExecs, + errno, fileSystem, internalState @*/; + +/** \ingroup popt + * Read default configuration from /etc/popt and $HOME/.popt. + * @param con context + * @param useEnv (unused) + * @return 0 on success, POPT_ERROR_ERRNO on failure + */ +/*@unused@*/ +int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv) + /*@globals fileSystem, internalState @*/ + /*@modifies con->execs, con->numExecs, + fileSystem, internalState @*/; + +/** \ingroup popt + * Duplicate an argument array. + * @note: The argument array is malloc'd as a single area, so only argv must + * be free'd. + * + * @param argc no. of arguments + * @param argv argument array + * @retval argcPtr address of returned no. of arguments + * @retval argvPtr address of returned argument array + * @return 0 on success, POPT_ERROR_NOARG on failure + */ +int poptDupArgv(int argc, /*@null@*/ const char **argv, + /*@null@*/ /*@out@*/ int * argcPtr, + /*@null@*/ /*@out@*/ const char *** argvPtr) + /*@modifies *argcPtr, *argvPtr @*/; + +/** \ingroup popt + * Parse a string into an argument array. + * The parse allows ', ", and \ quoting, but ' is treated the same as " and + * both may include \ quotes. + * @note: The argument array is malloc'd as a single area, so only argv must + * be free'd. + * + * @param s string to parse + * @retval argcPtr address of returned no. of arguments + * @retval argvPtr address of returned argument array + */ +int poptParseArgvString(const char * s, + /*@out@*/ int * argcPtr, /*@out@*/ const char *** argvPtr) + /*@modifies *argcPtr, *argvPtr @*/; + +/** \ingroup popt + * Parses an input configuration file and returns an string that is a + * command line. For use with popt. You must free the return value when done. + * + * Given the file: +\verbatim +# this line is ignored + # this one too +aaa + bbb + ccc +bla=bla + +this_is = fdsafdas + bad_line= + reall bad line + reall bad line = again +5555= 55555 + test = with lots of spaces +\endverbatim +* +* The result is: +\verbatim +--aaa --bbb --ccc --bla="bla" --this_is="fdsafdas" --5555="55555" --test="with lots of spaces" +\endverbatim +* +* Passing this to poptParseArgvString() yields an argv of: +\verbatim +'--aaa' +'--bbb' +'--ccc' +'--bla=bla' +'--this_is=fdsafdas' +'--5555=55555' +'--test=with lots of spaces' +\endverbatim + * + * @bug NULL is returned if file line is too long. + * @bug Silently ignores invalid lines. + * + * @param fp file handle to read + * @param *argstrp return string of options (malloc'd) + * @param flags unused + * @return 0 on success + * @see poptParseArgvString + */ +/*@-fcnuse@*/ +int poptConfigFileToString(FILE *fp, /*@out@*/ char ** argstrp, int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, *argstrp, fileSystem @*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Return formatted error string for popt failure. + * @param error popt error + * @return error string + */ +/*@observer@*/ +const char * poptStrerror(const int error) + /*@*/; + +/** \ingroup popt + * Limit search for executables. + * @param con context + * @param path single path to search for executables + * @param allowAbsolute absolute paths only? + */ +/*@unused@*/ +void poptSetExecPath(poptContext con, const char * path, int allowAbsolute) + /*@modifies con @*/; + +/** \ingroup popt + * Print detailed description of options. + * @param con context + * @param fp ouput file handle + * @param flags (unused) + */ +void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/; + +/** \ingroup popt + * Print terse description of options. + * @param con context + * @param fp ouput file handle + * @param flags (unused) + */ +void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/; + +/** \ingroup popt + * Provide text to replace default "[OPTION...]" in help/usage output. + * @param con context + * @param text replacement text + */ +/*@-fcnuse@*/ +void poptSetOtherOptionHelp(poptContext con, const char * text) + /*@modifies con @*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Return argv[0] from context. + * @param con context + * @return argv[0] + */ +/*@-fcnuse@*/ +/*@observer@*/ +const char * poptGetInvocationName(poptContext con) + /*@*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Shuffle argv pointers to remove stripped args, returns new argc. + * @param con context + * @param argc no. of args + * @param argv arg vector + * @return new argc + */ +/*@-fcnuse@*/ +int poptStrippedArgv(poptContext con, int argc, char ** argv) + /*@modifies *argv @*/; +/*@=fcnuse@*/ + +/** + * Save a long, performing logical operation with value. + * @warning Alignment check may be too strict on certain platorms. + * @param arg integer pointer, aligned on int boundary. + * @param argInfo logical operation (see POPT_ARGFLAG_*) + * @param aLong value to use + * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION + */ +/*@-incondefs@*/ +/*@unused@*/ +int poptSaveLong(/*@null@*/ long * arg, int argInfo, long aLong) + /*@modifies *arg @*/ + /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/; +/*@=incondefs@*/ + +/** + * Save an integer, performing logical operation with value. + * @warning Alignment check may be too strict on certain platorms. + * @param arg integer pointer, aligned on int boundary. + * @param argInfo logical operation (see POPT_ARGFLAG_*) + * @param aLong value to use + * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION + */ +/*@-incondefs@*/ +/*@unused@*/ +int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong) + /*@modifies *arg @*/ + /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/; +/*@=incondefs@*/ + +/*@=type@*/ +#ifdef __cplusplus +} +#endif + +#endif |