diff --git a/LICENSE b/LICENSE index 44e3868..8000a6f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,504 @@ -BSD 2-Clause License + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 -Copyright (c) 2018-2022, Technologic Systems, Inc. dba embeddedTS + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + Preamble -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/meson.build b/meson.build index da23db6..729b259 100644 --- a/meson.build +++ b/meson.build @@ -1,3 +1,9 @@ project('idleinject', 'c') -procpsdep = dependency('libprocps') -executable('idleinject', 'src/idleinject.c', install : true, dependencies : procpsdep) +idleinject_files = files( + 'src/idleinject.c', + 'src/alloc.c', + 'src/escape.c', + 'src/pwcache.c', + 'src/readproc.c', +) +executable('idleinject', idleinject_files, install : true) diff --git a/src/alloc.c b/src/alloc.c new file mode 100644 index 0000000..0efd5a7 --- /dev/null +++ b/src/alloc.c @@ -0,0 +1,96 @@ +/* + * alloc.c - memory allocation functions + * Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com + * Copyright 2002 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "alloc.h" + +static void xdefault_error(const char *restrict fmts, ...) __attribute__((format(printf,1,2))); +static void xdefault_error(const char *restrict fmts, ...) { + va_list va; + + va_start(va, fmts); + vfprintf(stderr, fmts, va); + va_end(va); +} + +message_fn xalloc_err_handler = xdefault_error; + + +void *xcalloc(size_t size) { + void * p; + + if (size == 0) + ++size; + p = calloc(1, size); + if (!p) { + xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size); + exit(EXIT_FAILURE); + } + return p; +} + +void *xmalloc(size_t size) { + void *p; + + if (size == 0) + ++size; + p = malloc(size); + if (!p) { + xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size); + exit(EXIT_FAILURE); + } + return(p); +} + +void *xrealloc(void *oldp, size_t size) { + void *p; + + if (size == 0) + ++size; + p = realloc(oldp, size); + if (!p) { + xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size); + exit(EXIT_FAILURE); + } + return(p); +} + +char *xstrdup(const char *str) { + char *p = NULL; + + if (str) { + size_t size = strlen(str) + 1; + if (size < 1) { + xalloc_err_handler("%s refused to allocate %zu bytes of memory", __func__, size); + exit(EXIT_FAILURE); + } + p = malloc(size); + if (!p) { + xalloc_err_handler("%s failed to allocate %zu bytes of memory", __func__, size); + exit(EXIT_FAILURE); + } + memcpy(p, str, size); + } + return(p); +} diff --git a/src/alloc.h b/src/alloc.h new file mode 100644 index 0000000..12abb84 --- /dev/null +++ b/src/alloc.h @@ -0,0 +1,20 @@ +#ifndef PROCPS_PROC_ALLOC_H +#define PROCPS_PROC_ALLOC_H + +#include "procps.h" + +EXTERN_C_BEGIN + +typedef void (*message_fn)(const char *__restrict, ...) __attribute__((format(printf,1,2))); + + /* change xalloc_err_handler to override the default fprintf(stderr... */ +extern message_fn xalloc_err_handler; + +extern void *xcalloc(size_t size) MALLOC; +extern void *xmalloc(size_t size) MALLOC; +extern void *xrealloc(void *oldp, size_t size) MALLOC; +extern char *xstrdup(const char *str) MALLOC; + +EXTERN_C_END + +#endif diff --git a/src/devname.h b/src/devname.h new file mode 100644 index 0000000..a725f15 --- /dev/null +++ b/src/devname.h @@ -0,0 +1,18 @@ +#ifndef PROC_DEVNAME_H +#define PROC_DEVNAME_H + +#include "procps.h" +#include "readproc.h" + +EXTERN_C_BEGIN + +#define ABBREV_DEV 1 /* remove /dev/ */ +#define ABBREV_TTY 2 /* remove tty */ +#define ABBREV_PTS 4 /* remove pts/ */ + +extern unsigned dev_to_tty(char *__restrict ret, unsigned chop, dev_t dev_t_dev, int pid, unsigned int flags); + +extern int tty_to_dev(const char *__restrict const name); + +EXTERN_C_END +#endif diff --git a/src/escape.c b/src/escape.c new file mode 100644 index 0000000..2e8fb7d --- /dev/null +++ b/src/escape.c @@ -0,0 +1,263 @@ +/* + * escape.c - printing handling + * Copyright 1998-2002 by Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include "procps.h" +#include "escape.h" +#include "readproc.h" + +#if (__GNU_LIBRARY__ >= 6) && (!defined(__UCLIBC__) || defined(__UCLIBC_HAS_WCHAR__)) +# include +# include +# include /* MB_CUR_MAX */ +# include +# include +#endif + +#define SECURE_ESCAPE_ARGS(dst, bytes, cells) do { \ + if ((bytes) <= 0) return 0; \ + *(dst) = '\0'; \ + if ((bytes) >= INT_MAX) return 0; \ + if ((cells) >= INT_MAX) return 0; \ + if ((cells) <= 0) return 0; \ +} while (0) + +static const unsigned char ESC_tab[] = { + "@..............................." + "||||||||||||||||||||||||||||||||" + "||||||||||||||||||||||||||||||||" + "|||||||||||||||||||||||||||||||." + "????????????????????????????????" + "????????????????????????????????" + "????????????????????????????????" + "????????????????????????????????" +}; + + +#if (__GNU_LIBRARY__ >= 6) && (!defined(__UCLIBC__) || defined(__UCLIBC_HAS_WCHAR__)) + +static int escape_str_utf8(char *restrict dst, const char *restrict src, int bufsize, int *maxcells){ + int my_cells = 0; + int my_bytes = 0; + mbstate_t s; + + SECURE_ESCAPE_ARGS(dst, bufsize, *maxcells); + + memset(&s, 0, sizeof (s)); + + for(;;) { + wchar_t wc; + int len = 0; + + if(my_cells >= *maxcells || my_bytes+1 >= bufsize) + break; + + if (!(len = mbrtowc (&wc, src, MB_CUR_MAX, &s))) + /* 'str' contains \0 */ + break; + + if (len < 0) { + /* invalid multibyte sequence -- zeroize state */ + memset (&s, 0, sizeof (s)); + *(dst++) = '?'; + src++; + my_cells++; + my_bytes++; + + } else if (len==1) { + /* non-multibyte */ + *(dst++) = isprint(*src) ? *src : '?'; + src++; + my_cells++; + my_bytes++; + + } else if (!iswprint(wc)) { + /* multibyte - no printable */ + *(dst++) = '?'; + src+=len; + my_cells++; + my_bytes++; + + } else { + /* multibyte - maybe, kinda "printable" */ + int wlen = wcwidth(wc); + // Got space? + if (wlen > *maxcells-my_cells || len >= bufsize-(my_bytes+1)) break; + // safe multibyte + memcpy(dst, src, len); + dst += len; + src += len; + my_bytes += len; + if (wlen > 0) my_cells += wlen; + } + //fprintf(stdout, "cells: %d\n", my_cells); + } + *dst = '\0'; + + // fprintf(stderr, "maxcells: %d, my_cells; %d\n", *maxcells, my_cells); + + *maxcells -= my_cells; + return my_bytes; // bytes of text, excluding the NUL +} +#endif /* __GNU_LIBRARY__ */ + + +/* sanitize a string via one-way mangle */ +int escape_str(char *restrict dst, const char *restrict src, int bufsize, int *maxcells){ + unsigned char c; + int my_cells = 0; + int my_bytes = 0; + +#if (__GNU_LIBRARY__ >= 6) && (!defined(__UCLIBC__) || defined(__UCLIBC_HAS_WCHAR__)) + static int utf_init=0; + + if(utf_init==0){ + /* first call -- check if UTF stuff is usable */ + char *enc = nl_langinfo(CODESET); + utf_init = enc && strcasecmp(enc, "UTF-8")==0 ? 1 : -1; + } + if (utf_init==1 && MB_CUR_MAX>1) { + /* UTF8 locales */ + return escape_str_utf8(dst, src, bufsize, maxcells); + } +#endif + + SECURE_ESCAPE_ARGS(dst, bufsize, *maxcells); + if(bufsize > *maxcells+1) bufsize=*maxcells+1; // FIXME: assumes 8-bit locale + + for(;;){ + if(my_cells >= *maxcells || my_bytes+1 >= bufsize) + break; + c = (unsigned char) *(src++); + if(!c) break; + if(ESC_tab[c]!='|') c=ESC_tab[c]; + my_cells++; + my_bytes++; + *(dst++) = c; + } + *dst = '\0'; + + *maxcells -= my_cells; + return my_bytes; // bytes of text, excluding the NUL +} + +///////////////////////////////////////////////// + +// escape an argv or environment string array +// +// bytes arg means sizeof(buf) +int escape_strlist(char *restrict dst, char *restrict const *restrict src, size_t bytes, int *cells){ + size_t i = 0; + + SECURE_ESCAPE_ARGS(dst, bytes, *cells); + + for(;;){ + i += escape_str(dst+i, *src, bytes-i, cells); + if(bytes-i < 3) break; // need room for space, a character, and the NUL + src++; + if(!*src) break; // need something to print + if (*cells<=1) break; // need room for printed size of text + dst[i++] = ' '; + --*cells; + } + return i; // bytes, excluding the NUL +} + +/////////////////////////////////////////////////// + +int escape_command(char *restrict const outbuf, const proc_t *restrict const pp, int bytes, int *cells, unsigned flags){ + int overhead = 0; + int end = 0; + + SECURE_ESCAPE_ARGS(outbuf, bytes, *cells); + + if(flags & ESC_ARGS){ + char **lc = (char**)pp->cmdline; + if(lc && *lc) return escape_strlist(outbuf, lc, bytes, cells); + } + if(flags & ESC_BRACKETS){ + overhead += 2; + } + if(flags & ESC_DEFUNCT){ + if(pp->state=='Z') overhead += 10; // chars in " " + else flags &= ~ESC_DEFUNCT; + } + if(overhead + 1 >= *cells || // if no room for even one byte of the command name + overhead + 1 >= bytes){ + outbuf[0] = '\0'; + return 0; + } + if(flags & ESC_BRACKETS){ + outbuf[end++] = '['; + } + *cells -= overhead; + end += escape_str(outbuf+end, pp->cmd, bytes-overhead, cells); + + // Hmmm, do we want "[foo] " or "[foo ]"? + if(flags & ESC_BRACKETS){ + outbuf[end++] = ']'; + } + if(flags & ESC_DEFUNCT){ + memcpy(outbuf+end, " ", 10); + end += 10; + } + outbuf[end] = '\0'; + return end; // bytes, not including the NUL +} + +///////////////////////////////////////////////// + +// copy a string, but 'escape' any control characters +// using the traditional escape.h calling conventions +int escaped_copy(char *restrict dst, const char *restrict src, int bufsize, int *maxroom){ + static int utf_sw = 0; + int i, n; + char c; + + if(utf_sw == 0){ + char *enc = nl_langinfo(CODESET); + utf_sw = enc && strcasecmp(enc, "UTF-8")==0 ? 1 : -1; + } + SECURE_ESCAPE_ARGS(dst, bufsize, *maxroom); + if (bufsize > *maxroom+1) bufsize = *maxroom+1; + + n = snprintf(dst, bufsize, "%s", src); + if (n < 0) { + *dst = '\0'; + return 0; + } + if (n >= bufsize) n = bufsize-1; + + if (utf_sw < 0) { + // if bad locale, replace the non-printing stuff + for (i = 0; i < n; i++) + if ((c = ESC_tab[(unsigned char)dst[i]]) != '|') + dst[i] = c; + } else { + // or eliminate those non-printing control chars + for (i = 0; i < n; i++) + if ((unsigned char)dst[i] < 0x20 || dst[i] == 0x7f) + dst[i] = '?'; + } + *maxroom -= n; + return n; +} diff --git a/src/escape.h b/src/escape.h new file mode 100644 index 0000000..80d16f1 --- /dev/null +++ b/src/escape.h @@ -0,0 +1,23 @@ +#ifndef PROCPS_PROC_ESCAPE_H +#define PROCPS_PROC_ESCAPE_H + +//#include +#include +#include "procps.h" +#include "readproc.h" + +EXTERN_C_BEGIN + +#define ESC_STRETCH 1 // since we mangle to '?' this is 1 (would be 4 for octal escapes) + +#define ESC_ARGS 0x1 // try to use cmdline instead of cmd +#define ESC_BRACKETS 0x2 // if using cmd, put '[' and ']' around it +#define ESC_DEFUNCT 0x4 // mark zombies with " " + +extern int escape_strlist(char *__restrict dst, char *__restrict const *__restrict src, size_t n, int *cells); +extern int escape_str(char *__restrict dst, const char *__restrict src, int bufsize, int *maxcells); +extern int escape_command(char *__restrict const outbuf, const proc_t *__restrict const pp, int bytes, int *cells, unsigned flags); +extern int escaped_copy(char *__restrict dst, const char *__restrict src, int bufsize, int *maxroom); + +EXTERN_C_END +#endif diff --git a/src/idleinject.c b/src/idleinject.c index f22bfee..71f0f1b 100644 --- a/src/idleinject.c +++ b/src/idleinject.c @@ -1,5 +1,24 @@ +/* + * Idleinject -- force process idle states to control CPU temperature + * Copyright (c) 2018-2022, Technologic Systems, Inc. dba embeddedTS + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + #include -#include +#include "readproc.h" #include #include #include @@ -70,6 +89,12 @@ static void recurse(pid_t pid) { if (!procs[i].flags & PROC_ALREADY_STOPPED) kill_list[nkills++] = pid; } +/* Parts needed from procps + * openproc() + * readproc() + * freeproc() + * closeproc() + */ static void idle_inject(void) { struct sched_param sched; PROCTAB *pt; diff --git a/src/procps.h b/src/procps.h new file mode 100644 index 0000000..ec74628 --- /dev/null +++ b/src/procps.h @@ -0,0 +1,90 @@ +#ifndef PROCPS_PROC_PROCPS_H +#define PROCPS_PROC_PROCPS_H + +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif + +// Some ports make the mistake of running a 32-bit userspace +// on a 64-bit kernel. Shame on them. It's not at all OK to +// make everything "long long", since that causes unneeded +// slowness on 32-bit hardware. +// +// SPARC: The 32-bit kernel was looking like an ex-penguin, +// but it lives! ("I'm not dead yet.") So, 64-bit users will +// just have to compile for 64-bit. Aw, the suffering. +// +// MIPS: Used 32-bit for embedded systems and obsolete hardware. +// The 64-bit systems use an n32 format executable, defining +// _ABIN32 to indicate this. Since n32 doesn't currently run on +// any 32-bit system, nobody get hurt if it's bloated. Not that +// this is sane of course, but it won't hurt the 32-bit users. +// __mips_eabi means eabi, which comes in both sizes, but isn't used. +// +// PowerPC: Big ugly problem! 32-bit Macs are still popular. :-/ +// +// x86-64: So far, nobody has been dumb enough to go 32-bit. +// +// Unknown: PA-RISC and zSeries +// +#if defined(k64test) || (defined(_ABIN32) && _MIPS_SIM == _ABIN32) +#define KLONG long long // not typedef; want "unsigned KLONG" to work +#define KLF "ll" +#define STRTOUKL strtoull +#else +#define KLONG long +#define KLF "l" +#define STRTOUKL strtoul +#endif + +// since gcc-2.5 +#define NORETURN __attribute__((__noreturn__)) +#define FUNCTION __attribute__((__const__)) // no access to global mem, even via ptr, and no side effect + +#if __GNUC__ > 2 || __GNUC_MINOR__ >= 96 +// won't alias anything, and aligned enough for anything +#define MALLOC __attribute__ ((__malloc__)) +// no side effect, may read globals +#define PURE __attribute__ ((__pure__)) +// tell gcc what to expect: if(unlikely(err)) die(err); +#define likely(x) __builtin_expect(!!(x),1) +#define unlikely(x) __builtin_expect(!!(x),0) +#define expected(x,y) __builtin_expect((x),(y)) +#else +#define MALLOC +#define PURE +#define likely(x) (x) +#define unlikely(x) (x) +#define expected(x,y) (x) +#endif + +#ifdef SHARED +# if SHARED==1 && (__GNUC__ > 2 || __GNUC_MINOR__ >= 96) +# define LABEL_OFFSET +# endif +#endif + +#define STRINGIFY_ARG(a) #a +#define STRINGIFY(a) STRINGIFY_ARG(a) + +// marks old junk, to warn non-procps-ng library users +#if ( __GNUC__ == 3 && __GNUC_MINOR__ > 0 ) || __GNUC__ > 3 +#define OBSOLETE __attribute__((deprecated)) +#else +#define OBSOLETE +#endif + +// Like HIDDEN, but for an alias that gets created. +// In gcc-3.2 there is an alias+hidden conflict. +// Many will have patched this bug, but oh well. +#if (( __GNUC__ == 3 && __GNUC_MINOR__ > 2 ) || __GNUC__ > 3) && !defined(__CYGWIN__) +#define HIDDEN_ALIAS(x) extern __typeof(x) x##_direct __attribute__((alias(#x),visibility("hidden"))) +#else +#define HIDDEN_ALIAS(x) extern __typeof(x) x##_direct __attribute__((alias(#x))) +#endif + +#endif diff --git a/src/pwcache.c b/src/pwcache.c new file mode 100644 index 0000000..5c2834e --- /dev/null +++ b/src/pwcache.c @@ -0,0 +1,91 @@ +/* + * pwcache.c - memory cache passwd file handling + * + * Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com + * Note: most likely none of his code remains + * + * Copyright 2002, Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include "alloc.h" +#include "pwcache.h" +#include + +// might as well fill cache lines... else we waste memory anyway + +#define HASHSIZE 64 /* power of 2 */ +#define HASH(x) ((x) & (HASHSIZE - 1)) + +static struct pwbuf { + struct pwbuf *next; + uid_t uid; + char name[P_G_SZ]; +} *pwhash[HASHSIZE]; + +char *pwcache_get_user(uid_t uid) { + struct pwbuf **p; + struct passwd *pw; + + p = &pwhash[HASH(uid)]; + while (*p) { + if ((*p)->uid == uid) + return((*p)->name); + p = &(*p)->next; + } + *p = (struct pwbuf *) xmalloc(sizeof(struct pwbuf)); + (*p)->uid = uid; + pw = getpwuid(uid); + if(!pw || strlen(pw->pw_name) >= P_G_SZ) + sprintf((*p)->name, "%u", uid); + else + strcpy((*p)->name, pw->pw_name); + + (*p)->next = NULL; + return((*p)->name); +} + +static struct grpbuf { + struct grpbuf *next; + gid_t gid; + char name[P_G_SZ]; +} *grphash[HASHSIZE]; + +char *pwcache_get_group(gid_t gid) { + struct grpbuf **g; + struct group *gr; + + g = &grphash[HASH(gid)]; + while (*g) { + if ((*g)->gid == gid) + return((*g)->name); + g = &(*g)->next; + } + *g = (struct grpbuf *) xmalloc(sizeof(struct grpbuf)); + (*g)->gid = gid; + gr = getgrgid(gid); + if (!gr || strlen(gr->gr_name) >= P_G_SZ) + sprintf((*g)->name, "%u", gid); + else + strcpy((*g)->name, gr->gr_name); + (*g)->next = NULL; + return((*g)->name); +} diff --git a/src/pwcache.h b/src/pwcache.h new file mode 100644 index 0000000..141f59f --- /dev/null +++ b/src/pwcache.h @@ -0,0 +1,17 @@ +#ifndef PROCPS_PROC_PWCACHE_H +#define PROCPS_PROC_PWCACHE_H + +#include +#include "procps.h" + +EXTERN_C_BEGIN + +// used in pwcache and in readproc to set size of username or groupname +#define P_G_SZ 33 + +char *pwcache_get_user(uid_t uid); +char *pwcache_get_group(gid_t gid); + +EXTERN_C_END + +#endif diff --git a/src/readproc.c b/src/readproc.c new file mode 100644 index 0000000..695c17b --- /dev/null +++ b/src/readproc.c @@ -0,0 +1,1698 @@ +/* + * New Interface to Process Table -- PROCTAB Stream (a la Directory streams) + * Copyright (C) 1996 Charles L. Blake. + * Copyright (C) 1998 Michael K. Johnson + * Copyright 1998-2003 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "readproc.h" +#include "alloc.h" +#include "escape.h" +#include "pwcache.h" +#include "devname.h" +#include "procps.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WITH_SYSTEMD +#include +#endif +#ifdef WITH_ELOGIND +#include +#endif + +// sometimes it's easier to do this manually, w/o gcc helping +#ifdef PROF +extern void __cyg_profile_func_enter(void*,void*); +#define ENTER(x) __cyg_profile_func_enter((void*)x,(void*)x) +#define LEAVE(x) __cyg_profile_func_exit((void*)x,(void*)x) +#else +#define ENTER(x) +#define LEAVE(x) +#endif + +#ifdef QUICK_THREADS +// used when multi-threaded and some memory must not be freed +#define MK_THREAD(q) q->pad_1 = '\xee' +#define IS_THREAD(q) ( q->pad_1 == '\xee' ) +#endif + +// utility buffers of MAX_BUFSZ bytes each, available to +// any function following an openproc() call +static char *src_buffer, + *dst_buffer; +#define MAX_BUFSZ 1024*64*2 + +// dynamic 'utility' buffer support for file2str() calls +struct utlbuf_s { + char *buf; // dynamically grown buffer + int siz; // current len of the above +} utlbuf_s; + +#ifndef SIGNAL_STRING +// convert hex string to unsigned long long +static unsigned long long unhex(const char *restrict cp){ + unsigned long long ull = 0; + for(;;){ + char c = *cp++; + if(!( (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f') )) break; + ull = (ull<<4) | (c - (c >= 'a' ? 'a'-10 : c >= 'A' ? 'A'-10 : '0')); + } + return ull; +} +#endif + +static int task_dir_missing; + +// free any additional dynamically acquired storage associated with a proc_t +// ( and if it's to be reused, refresh it otherwise destroy it ) +static inline void free_acquired (proc_t *p, int reuse) { +#ifdef QUICK_THREADS + if (!IS_THREAD(p)) { +#endif + if (p->environ) free((void*)*p->environ); + if (p->cmdline) free((void*)*p->cmdline); + if (p->cgroup) free((void*)*p->cgroup); + if (p->cgname) free(p->cgname); + if (p->supgid) free(p->supgid); + if (p->supgrp) free(p->supgrp); + if (p->sd_mach) free(p->sd_mach); + if (p->sd_ouid) free(p->sd_ouid); + if (p->sd_seat) free(p->sd_seat); + if (p->sd_sess) free(p->sd_sess); + if (p->sd_slice) free(p->sd_slice); + if (p->sd_unit) free(p->sd_unit); + if (p->sd_uunit) free(p->sd_uunit); +#ifdef QUICK_THREADS + } +#endif + memset(p, reuse ? '\0' : '\xff', sizeof(*p)); +} + +/////////////////////////////////////////////////////////////////////////// + +typedef struct status_table_struct { + unsigned char name[8]; // /proc/*/status field name + unsigned char len; // name length +#ifdef LABEL_OFFSET + long offset; // jump address offset +#else + void *addr; +#endif +} status_table_struct; + +#ifdef LABEL_OFFSET +#define F(x) {#x, sizeof(#x)-1, (long)(&&case_##x-&&base)}, +#else +#define F(x) {#x, sizeof(#x)-1, &&case_##x}, +#endif +#define NUL {"", 0, 0}, + +#define GPERF_TABLE_SIZE 128 + +// Derived from: +// gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c +// ( --key-positions verified by omission & reported "Computed positions" ) +// +// Suggested method: +// Grep this file for "case_", then strip those down to the name. +// Eliminate duplicates (due to #ifs), the ' case_' prefix and +// any c comments. Leave the colon and newline so that "Pid:\n", +// "Threads:\n", etc. would be lines, but no quote, no escape, etc. +// +// After a pipe through gperf, insert the resulting 'asso_values' +// into our 'asso' array. Then convert the gperf 'wordlist' array +// into our 'table' array by wrapping the string literals within +// the F macro and replacing empty strings with the NUL define. +// +// In the status_table_struct watch out for name size (grrr, expanding) +// and the number of entries. Currently, the table is padded to 128 +// entries and we therefore mask with 127. + +static void status2proc(char *S, proc_t *restrict P, int is_proc){ + long Threads = 0; + long Tgid = 0; + long Pid = 0; + + // 128 entries because we trust the kernel to use ASCII names + static const unsigned char asso[] = + { + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 6, 101, + 101, 101, 101, 101, 101, 45, 55, 25, 31, 50, + 50, 10, 0, 35, 101, 101, 21, 101, 30, 101, + 20, 36, 0, 5, 0, 40, 0, 0, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 30, 101, 15, + 0, 1, 101, 10, 101, 10, 101, 101, 101, 25, + 101, 40, 0, 101, 0, 50, 6, 40, 101, 1, + 35, 101, 101, 101, 101, 101, 101, 101 + }; + + static const status_table_struct table[GPERF_TABLE_SIZE] = { + F(VmHWM) + F(Threads) + NUL NUL NUL + F(VmRSS) + F(VmSwap) + NUL NUL NUL + F(Tgid) + F(VmStk) + NUL NUL NUL + F(VmSize) + F(Gid) + NUL NUL NUL + F(VmPTE) + F(VmPeak) + NUL NUL NUL + F(ShdPnd) + F(Pid) + NUL NUL NUL + F(PPid) + F(VmLib) + NUL NUL NUL + F(SigPnd) + F(VmLck) + NUL NUL NUL + F(SigCgt) + F(State) + NUL NUL NUL + F(CapPrm) + F(Uid) + NUL NUL NUL + F(SigIgn) + F(SigQ) + NUL NUL NUL + F(RssShmem) + F(Name) + NUL NUL NUL + F(CapInh) + F(VmData) + NUL NUL NUL + F(FDSize) + NUL NUL NUL NUL + F(SigBlk) + NUL NUL NUL NUL + F(CapEff) + NUL NUL NUL NUL + F(CapBnd) + NUL NUL NUL NUL + F(VmExe) + NUL NUL NUL NUL + F(Groups) + NUL NUL NUL NUL + F(RssAnon) + NUL NUL NUL NUL + F(RssFile) + }; + +#undef F +#undef NUL + +ENTER(0x220); + + goto base; + + for(;;){ + char *colon; + status_table_struct entry; + + // advance to next line + S = strchr(S, '\n'); + if(unlikely(!S)) break; // if no newline + S++; + + // examine a field name (hash and compare) + base: + if(unlikely(!S[0] || !S[1] || !S[2] || !S[3])) break; + entry = table[(GPERF_TABLE_SIZE -1) & (asso[S[3]&127] + asso[S[2]&127] + asso[S[0]&127])]; + colon = strchr(S, ':'); + if(unlikely(!colon)) break; + if(unlikely(colon[1]!='\t')) break; + if(unlikely(colon-S != entry.len)) continue; + if(unlikely(memcmp(entry.name,S,colon-S))) continue; + + S = colon+2; // past the '\t' + +#ifdef LABEL_OFFSET + goto *(&&base + entry.offset); +#else + goto *entry.addr; +#endif + + case_Name: + { unsigned u = 0; + while(u < sizeof P->cmd - 1u){ + int c = *S++; + if(unlikely(c=='\n')) break; + if(unlikely(c=='\0')) break; // should never happen + if(unlikely(c=='\\')){ + c = *S++; + if(c=='\n') break; // should never happen + if(!c) break; // should never happen + if(c=='n') c='\n'; // else we assume it is '\\' + } + P->cmd[u++] = c; + } + P->cmd[u] = '\0'; + S--; // put back the '\n' or '\0' + continue; + } +#ifdef SIGNAL_STRING + case_ShdPnd: + memcpy(P->signal, S, 16); + P->signal[16] = '\0'; + continue; + case_SigBlk: + memcpy(P->blocked, S, 16); + P->blocked[16] = '\0'; + continue; + case_SigCgt: + memcpy(P->sigcatch, S, 16); + P->sigcatch[16] = '\0'; + continue; + case_SigIgn: + memcpy(P->sigignore, S, 16); + P->sigignore[16] = '\0'; + continue; + case_SigPnd: + memcpy(P->_sigpnd, S, 16); + P->_sigpnd[16] = '\0'; + continue; +#else + case_ShdPnd: + P->signal = unhex(S); + continue; + case_SigBlk: + P->blocked = unhex(S); + continue; + case_SigCgt: + P->sigcatch = unhex(S); + continue; + case_SigIgn: + P->sigignore = unhex(S); + continue; + case_SigPnd: + P->_sigpnd = unhex(S); + continue; +#endif + case_State: + P->state = *S; + continue; + case_Tgid: + Tgid = strtol(S,&S,10); + continue; + case_Pid: + Pid = strtol(S,&S,10); + continue; + case_PPid: + P->ppid = strtol(S,&S,10); + continue; + case_Threads: + Threads = strtol(S,&S,10); + continue; + case_Uid: + P->ruid = strtol(S,&S,10); + P->euid = strtol(S,&S,10); + P->suid = strtol(S,&S,10); + P->fuid = strtol(S,&S,10); + continue; + case_Gid: + P->rgid = strtol(S,&S,10); + P->egid = strtol(S,&S,10); + P->sgid = strtol(S,&S,10); + P->fgid = strtol(S,&S,10); + continue; + case_VmData: + P->vm_data = strtol(S,&S,10); + continue; + case_VmExe: + P->vm_exe = strtol(S,&S,10); + continue; + case_VmLck: + P->vm_lock = strtol(S,&S,10); + continue; + case_VmLib: + P->vm_lib = strtol(S,&S,10); + continue; + case_VmRSS: + P->vm_rss = strtol(S,&S,10); + continue; + case_RssAnon: // subset of VmRSS, linux-4.5 + P->vm_rss_anon = strtol(S,&S,10); + continue; + case_RssFile: // subset of VmRSS, linux-4.5 + P->vm_rss_file = strtol(S,&S,10); + continue; + case_RssShmem: // subset of VmRSS, linux-4.5 + P->vm_rss_shared = strtol(S,&S,10); + continue; + case_VmSize: + P->vm_size = strtol(S,&S,10); + continue; + case_VmStk: + P->vm_stack = strtol(S,&S,10); + continue; + case_VmSwap: // Linux 2.6.34 + P->vm_swap = strtol(S,&S,10); + continue; + case_Groups: + { char *ss = S, *nl = strchr(S, '\n'); + size_t j; + + while (' ' == *ss || '\t' == *ss) ss++; + if (ss >= nl) continue; + j = nl ? (size_t)(nl - ss) : strlen(ss); + if (j > 0 && j < INT_MAX) { + P->supgid = xmalloc(j+1); // +1 in case space disappears + memcpy(P->supgid, ss, j); + if (unlikely(' ' != P->supgid[--j])) ++j; + P->supgid[j] = '\0'; // whack the space or the newline + for ( ; j; j--) + if (' ' == P->supgid[j]) + P->supgid[j] = ','; + } + continue; + } + case_CapBnd: + case_CapEff: + case_CapInh: + case_CapPrm: + case_FDSize: + case_SigQ: + case_VmHWM: // 2005, peak VmRSS unless VmRSS is bigger + case_VmPTE: + case_VmPeak: // 2005, peak VmSize unless VmSize is bigger + continue; + } + +#if 0 + // recent kernels supply per-tgid pending signals + if(is_proc && *ShdPnd){ + memcpy(P->signal, ShdPnd, 16); + P->signal[16] = '\0'; + } +#endif + + // recent kernels supply per-tgid pending signals +#ifdef SIGNAL_STRING + if(!is_proc || !P->signal[0]){ + memcpy(P->signal, P->_sigpnd, 16); + P->signal[16] = '\0'; + } +#else + if(!is_proc){ + P->signal = P->_sigpnd; + } +#endif + + // Linux 2.4.13-pre1 to max 2.4.xx have a useless "Tgid" + // that is not initialized for built-in kernel tasks. + // Only 2.6.0 and above have "Threads" (nlwp) info. + + if(Threads){ + P->nlwp = Threads; + P->tgid = Tgid; // the POSIX PID value + P->tid = Pid; // the thread ID + }else{ + P->nlwp = 1; + P->tgid = Pid; + P->tid = Pid; + } + + if (!P->supgid) + P->supgid = xstrdup("-"); + +LEAVE(0x220); +} +#undef GPERF_TABLE_SIZE + +static void supgrps_from_supgids (proc_t *p) { + char *g, *s; + int t; + + if (!p->supgid || '-' == *p->supgid) { + p->supgrp = xstrdup("-"); + return; + } + s = p->supgid; + t = 0; + do { + const int max = P_G_SZ+2; + char *end = NULL; + gid_t gid; + int len; + + while (',' == *s) ++s; + gid = strtol(s, &end, 10); + if (end <= s) break; + s = end; + g = pwcache_get_group(gid); + + if (t >= INT_MAX - max) break; + p->supgrp = xrealloc(p->supgrp, t + max); + + len = snprintf(p->supgrp+t, max, "%s%s", t ? "," : "", g); + if (len <= 0) (p->supgrp+t)[len = 0] = '\0'; + else if (len >= max) len = max-1; + t += len; + } while (*s); + + if (!p->supgrp) + p->supgrp = xstrdup("-"); +} + +/////////////////////////////////////////////////////////////////////// +static void oomscore2proc(const char* S, proc_t *restrict P) +{ + sscanf(S, "%d", &P->oom_score); +} + +static void oomadj2proc(const char* S, proc_t *restrict P) +{ + sscanf(S, "%d", &P->oom_adj); +} +/////////////////////////////////////////////////////////////////////// + +static const char *ns_names[] = { + [IPCNS] = "ipc", + [MNTNS] = "mnt", + [NETNS] = "net", + [PIDNS] = "pid", + [USERNS] = "user", + [UTSNS] = "uts", +}; + +const char *get_ns_name(int id) { + if (id < 0 || id >= NUM_NS) + return NULL; + return ns_names[id]; +} + +int get_ns_id(const char *name) { + int i; + + if (!name) + return -1; + for (i = 0; i < NUM_NS; i++) + if (!strcmp(ns_names[i], name)) + return i; + return -1; +} + +static void ns2proc(const char *directory, proc_t *restrict p) { + char path[PROCPATHLEN]; + struct stat sb; + int i; + + for (i = 0; i < NUM_NS; i++) { + snprintf(path, sizeof(path), "%s/ns/%s", directory, ns_names[i]); + if (0 == stat(path, &sb)) + p->ns[i] = (long)sb.st_ino; +#if 0 + else // this allows a caller to distinguish + p->ns[i] = -errno; // between the ENOENT or EACCES errors +#endif + } +} + +static void sd2proc(proc_t *restrict p) { +#if defined(WITH_SYSTEMD) || defined(WITH_ELOGIND) + char buf[64]; + uid_t uid; + + if (0 > sd_pid_get_machine_name(p->tid, &p->sd_mach)) + p->sd_mach = strdup("-"); + + if (0 > sd_pid_get_owner_uid(p->tid, &uid)) + p->sd_ouid = strdup("-"); + else { + snprintf(buf, sizeof(buf), "%d", (int)uid); + p->sd_ouid = strdup(buf); + } + if (0 > sd_pid_get_session(p->tid, &p->sd_sess)) { + p->sd_sess = strdup("-"); + p->sd_seat = strdup("-"); + } else { + if (0 > sd_session_get_seat(p->sd_sess, &p->sd_seat)) + p->sd_seat = strdup("-"); + } + if (0 > sd_pid_get_slice(p->tid, &p->sd_slice)) + p->sd_slice = strdup("-"); + if (0 > sd_pid_get_unit(p->tid, &p->sd_unit)) + p->sd_unit = strdup("-"); + if (0 > sd_pid_get_user_unit(p->tid, &p->sd_uunit)) + p->sd_uunit = strdup("-"); +#else + p->sd_mach = strdup("?"); + p->sd_ouid = strdup("?"); + p->sd_seat = strdup("?"); + p->sd_sess = strdup("?"); + p->sd_slice = strdup("?"); + p->sd_unit = strdup("?"); + p->sd_uunit = strdup("?"); +#endif +} +/////////////////////////////////////////////////////////////////////// + + +// Reads /proc/*/stat files, being careful not to trip over processes with +// names like ":-) 1 2 3 4 5 6". +static void stat2proc(const char* S, proc_t *restrict P) { + size_t num; + char* tmp; + +ENTER(0x160); + + /* fill in default values for older kernels */ + P->processor = 0; + P->rtprio = -1; + P->sched = -1; + P->nlwp = 0; + + S = strchr(S, '('); + if(unlikely(!S)) return; + S++; + tmp = strrchr(S, ')'); + if(unlikely(!tmp)) return; + if(unlikely(!tmp[1])) return; + num = tmp - S; + if(unlikely(num >= sizeof P->cmd)) num = sizeof P->cmd - 1; + memcpy(P->cmd, S, num); + P->cmd[num] = '\0'; + S = tmp + 2; // skip ") " + + sscanf(S, + "%c " + "%d %d %d %d %d " + "%lu %lu %lu %lu %lu " + "%llu %llu %llu %llu " /* utime stime cutime cstime */ + "%ld %ld " + "%d " + "%ld " + "%llu " /* start_time */ + "%lu " + "%ld " + "%lu %"KLF"u %"KLF"u %"KLF"u %"KLF"u %"KLF"u " + "%*s %*s %*s %*s " /* discard, no RT signals & Linux 2.1 used hex */ + "%"KLF"u %*u %*u " + "%d %d " + "%lu %lu", + &P->state, + &P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid, + &P->flags, &P->min_flt, &P->cmin_flt, &P->maj_flt, &P->cmaj_flt, + &P->utime, &P->stime, &P->cutime, &P->cstime, + &P->priority, &P->nice, + &P->nlwp, + &P->alarm, + &P->start_time, + &P->vsize, + &P->rss, + &P->rss_rlim, &P->start_code, &P->end_code, &P->start_stack, &P->kstk_esp, &P->kstk_eip, +/* P->signal, P->blocked, P->sigignore, P->sigcatch, */ /* can't use */ + &P->wchan, /* &P->nswap, &P->cnswap, */ /* nswap and cnswap dead for 2.4.xx and up */ +/* -- Linux 2.0.35 ends here -- */ + &P->exit_signal, &P->processor, /* 2.2.1 ends with "exit_signal" */ +/* -- Linux 2.2.8 to 2.5.17 end here -- */ + &P->rtprio, &P->sched /* both added to 2.5.18 */ + ); + + if(!P->nlwp){ + P->nlwp = 1; + } + +LEAVE(0x160); +} + +///////////////////////////////////////////////////////////////////////// + +static void statm2proc(const char* s, proc_t *restrict P) { + sscanf(s, "%ld %ld %ld %ld %ld %ld %ld", + &P->size, &P->resident, &P->share, + &P->trs, &P->lrs, &P->drs, &P->dt); +} + +static int file2str(const char *directory, const char *what, struct utlbuf_s *ub) { + #define buffGRW 1024 + char path[PROCPATHLEN]; + int fd, num, tot_read = 0, len; + + /* on first use we preallocate a buffer of minimum size to emulate + former 'local static' behavior -- even if this read fails, that + buffer will likely soon be used for another subdirectory anyway + ( besides, with this xcalloc we will never need to use memcpy ) */ + if (ub->buf) ub->buf[0] = '\0'; + else ub->buf = xcalloc((ub->siz = buffGRW)); + len = snprintf(path, sizeof path, "%s/%s", directory, what); + if (len <= 0 || (size_t)len >= sizeof path) return -1; + if (-1 == (fd = open(path, O_RDONLY, 0))) return -1; + while (0 < (num = read(fd, ub->buf + tot_read, ub->siz - tot_read))) { + tot_read += num; + if (tot_read < ub->siz) break; + if (ub->siz >= INT_MAX - buffGRW) { + tot_read--; + break; + } + ub->buf = xrealloc(ub->buf, (ub->siz += buffGRW)); + }; + ub->buf[tot_read] = '\0'; + close(fd); + if (unlikely(tot_read < 1)) return -1; + return tot_read; + #undef buffGRW +} + +static char** file2strvec(const char* directory, const char* what) { + char buf[2048]; /* read buf bytes at a time */ + char *p, *rbuf = 0, *endbuf, **q, **ret, *strp; + int fd, tot = 0, n, c, end_of_file = 0; + int align; + + const int len = snprintf(buf, sizeof buf, "%s/%s", directory, what); + if(len <= 0 || (size_t)len >= sizeof buf) return NULL; + fd = open(buf, O_RDONLY, 0); + if(fd==-1) return NULL; + + /* read whole file into a memory buffer, allocating as we go */ + while ((n = read(fd, buf, sizeof buf - 1)) >= 0) { + if (n < (int)(sizeof buf - 1)) + end_of_file = 1; + if (n <= 0 && tot <= 0) { /* nothing read now, nothing read before */ + break; /* process died between our open and read */ + } + /* ARG_LEN is our guesstimated median length of a command-line argument + or environment variable (the minimum is 1, the maximum is 131072) */ + #define ARG_LEN 64 + if (tot >= INT_MAX / (ARG_LEN + (int)sizeof(char*)) * ARG_LEN - n) { + end_of_file = 1; /* integer overflow: null-terminate and break */ + n = 0; /* but tot > 0 */ + } + #undef ARG_LEN + if (end_of_file && + ((n > 0 && buf[n-1] != '\0') || /* last read char not null */ + (n <= 0 && rbuf && rbuf[tot-1] != '\0'))) /* last read char not null */ + buf[n++] = '\0'; /* so append null-terminator */ + + if (n <= 0) break; /* unneeded (end_of_file = 1) but avoid realloc */ + rbuf = xrealloc(rbuf, tot + n); /* allocate more memory */ + memcpy(rbuf + tot, buf, n); /* copy buffer into it */ + tot += n; /* increment total byte ctr */ + if (end_of_file) + break; + } + close(fd); + if (n < 0 || tot <= 0) { /* error, or nothing read */ + if (rbuf) free(rbuf); + return NULL; /* read error */ + } + rbuf[tot-1] = '\0'; /* belt and suspenders (the while loop did it, too) */ + endbuf = rbuf + tot; /* count space for pointers */ + align = (sizeof(char*)-1) - ((tot + sizeof(char*)-1) & (sizeof(char*)-1)); + c = sizeof(char*); /* one extra for NULL term */ + for (p = rbuf; p < endbuf; p++) { + if (!*p || *p == '\n') { + if (c >= INT_MAX - (tot + (int)sizeof(char*) + align)) break; + c += sizeof(char*); + } + if (*p == '\n') + *p = 0; + } + + rbuf = xrealloc(rbuf, tot + c + align); /* make room for ptrs AT END */ + endbuf = rbuf + tot; /* addr just past data buf */ + q = ret = (char**) (endbuf+align); /* ==> free(*ret) to dealloc */ + for (strp = p = rbuf; p < endbuf; p++) { + if (!*p) { /* NUL char implies that */ + if (c < 2 * (int)sizeof(char*)) break; + c -= sizeof(char*); + *q++ = strp; /* point ptrs to the strings */ + strp = p+1; /* next string -> next char */ + } + } + *q = 0; /* null ptr list terminator */ + return ret; +} + + // this is the former under utilized 'read_cmdline', which has been + // generalized in support of these new libproc flags: + // PROC_EDITCGRPCVT, PROC_EDITCMDLCVT and PROC_EDITENVRCVT +static int read_unvectored(char *restrict const dst, unsigned sz, const char* whom, const char *what, char sep) { + char path[PROCPATHLEN]; + int fd, len; + unsigned n = 0; + + if(sz <= 0) return 0; + if(sz >= INT_MAX) sz = INT_MAX-1; + dst[0] = '\0'; + + len = snprintf(path, sizeof(path), "%s/%s", whom, what); + if(len <= 0 || (size_t)len >= sizeof(path)) return 0; + fd = open(path, O_RDONLY); + if(fd==-1) return 0; + + for(;;){ + ssize_t r = read(fd,dst+n,sz-n); + if(r==-1){ + if(errno==EINTR) continue; + break; + } + if(r<=0) break; // EOF + n += r; + if(n==sz) { // filled the buffer + --n; // make room for '\0' + break; + } + } + close(fd); + if(n){ + unsigned i = n; + while(i && dst[i-1]=='\0') --i; // skip trailing zeroes + while(i--) + if(dst[i]=='\n' || dst[i]=='\0') dst[i]=sep; + if(dst[n-1]==' ') dst[n-1]='\0'; + } + dst[n] = '\0'; + return n; +} + +static char** vectorize_this_str (const char* src) { + #define pSZ (sizeof(char*)) + char *cpy, **vec; + size_t adj, tot; + + tot = strlen(src) + 1; // prep for our vectors + if (tot < 1 || tot >= INT_MAX) tot = INT_MAX-1; // integer overflow? + adj = (pSZ-1) - ((tot + pSZ-1) & (pSZ-1)); // calc alignment bytes + cpy = xcalloc(tot + adj + (2 * pSZ)); // get new larger buffer + snprintf(cpy, tot, "%s", src); // duplicate their string + vec = (char**)(cpy + tot + adj); // prep pointer to pointers + *vec = cpy; // point 1st vector to string + *(vec+1) = NULL; // null ptr 'list' delimit + return vec; // ==> free(*vec) to dealloc + #undef pSZ +} + + // This routine reads a 'cgroup' for the designated proc_t. + // It is similar to file2strvec except we filter and concatenate + // the data into a single string represented as a single vector. +static void fill_cgroup_cvt (const char* directory, proc_t *restrict p) { + #define vMAX ( MAX_BUFSZ - (int)(dst - dst_buffer) ) + char *src, *dst, *grp, *eob, *name; + int tot, x, whackable_int = MAX_BUFSZ, len; + + *(dst = dst_buffer) = '\0'; // empty destination + tot = read_unvectored(src_buffer, MAX_BUFSZ, directory, "cgroup", '\0'); + for (src = src_buffer, eob = src_buffer + tot; src < eob; src += x) { + x = 1; // loop assist + if (!*src) continue; + x = strlen((grp = src)); + if ('/' == grp[x - 1]) continue; // skip empty root cgroups +#if 0 + grp += strspn(grp, "0123456789:"); // jump past group number +#endif + if (vMAX <= 1) break; + len = snprintf(dst, vMAX, "%s", (dst > dst_buffer) ? "," : ""); + if (len < 0 || len >= vMAX) break; + dst += len; + dst += escaped_copy(dst, grp, vMAX, &whackable_int); + } + p->cgroup = vectorize_this_str(dst_buffer[0] ? dst_buffer : "-"); + + name = strstr(p->cgroup[0], ":name="); + if (name && *(name+6)) name += 6; else name = p->cgroup[0]; + p->cgname = strdup(name); + #undef vMAX +} + + // This routine reads a 'cmdline' for the designated proc_t, "escapes" + // the result into a single string represented as a single vector + // and guarantees the caller a valid proc_t.cmdline pointer. +static void fill_cmdline_cvt (const char* directory, proc_t *restrict p) { + #define uFLG ( ESC_BRACKETS | ESC_DEFUNCT ) + int whackable_int = MAX_BUFSZ; + + if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "cmdline", ' ')) + escaped_copy(dst_buffer, src_buffer, MAX_BUFSZ, &whackable_int); + else + escape_command(dst_buffer, p, MAX_BUFSZ, &whackable_int, uFLG); + p->cmdline = vectorize_this_str(dst_buffer); + #undef uFLG +} + + // This routine reads an 'environ' for the designated proc_t and + // guarantees the caller a valid proc_t.environ pointer. +static void fill_environ_cvt (const char* directory, proc_t *restrict p) { + int whackable_int = MAX_BUFSZ; + + dst_buffer[0] = '\0'; + if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "environ", ' ')) + escaped_copy(dst_buffer, src_buffer, MAX_BUFSZ, &whackable_int); + p->environ = vectorize_this_str(dst_buffer[0] ? dst_buffer : "-"); +} + +// warning: interface may change +int read_cmdline(char *restrict const dst, unsigned sz, unsigned pid) { + char path[PROCPATHLEN]; + snprintf(path, sizeof(path), "/proc/%u", pid); + return read_unvectored(dst, sz, path, "cmdline", ' '); +} + + + // Provide the means to value proc_t.lxcname (perhaps only with "-") while + // tracking all names already seen thus avoiding the overhead of repeating + // malloc() and free() calls. +static const char *lxc_containers (const char *path) { + static struct utlbuf_s ub = { NULL, 0 }; // util buffer for whole cgroup + static char lxc_none[] = "-"; + /* + try to locate the lxc delimiter eyecatcher somewhere in a task's cgroup + directory -- the following are from nested privileged plus unprivileged + containers, where the '/lxc/' delimiter precedes the container name ... + 10:cpuset:/lxc/lxc-P/lxc/lxc-P-nested + 10:cpuset:/user.slice/user-1000.slice/session-c2.scope/lxc/lxc-U/lxc/lxc-U-nested + + ... some minor complications are the potential addition of more cgroups + for a controller displacing the lxc name (normally last on a line), and + environments with unexpected /proc/##/cgroup ordering/contents as with: + 10:cpuset:/lxc/lxc-P/lxc/lxc-P-nested/MY-NEW-CGROUP + or + 2:name=systemd:/ + 1:cpuset,cpu,cpuacct,devices,freezer,net_cls,blkio,perf_event,net_prio:/lxc/lxc-P + */ + if (file2str(path, "cgroup", &ub) > 0) { + /* ouch, the next defaults could be changed at lxc ./configure time + ( and a changed 'lxc.cgroup.pattern' is only available to root ) */ + static const char *lxc_delm1 = "lxc.payload."; // with lxc-4.0.0 + static const char *lxc_delm2 = "lxc.payload/"; // thru lxc-3.2.1 + static const char *lxc_delm3 = "lxc/"; // thru lxc-3.0.3 + const char *delim; + char *p1; + + if ((p1 = strstr(ub.buf, (delim = lxc_delm1))) + || ((p1 = strstr(ub.buf, (delim = lxc_delm2))) + || ((p1 = strstr(ub.buf, (delim = lxc_delm3)))))) { + static struct lxc_ele { + struct lxc_ele *next; + const char *name; + } *anchor = NULL; + struct lxc_ele *ele = anchor; + int delim_len = strlen(delim); + char *p2; + + if ((p2 = strchr(p1, '\n'))) // isolate a controller's line + *p2 = '\0'; + do { // deal with nested containers + p2 = p1 + delim_len; + p1 = strstr(p2, delim); + } while (p1); + if ((p1 = strchr(p2, '/'))) // isolate name only substring + *p1 = '\0'; + while (ele) { // have we already seen a name + if (!strcmp(ele->name, p2)) + return ele->name; // return just a recycled name + ele = ele->next; + } + ele = (struct lxc_ele *)xmalloc(sizeof(struct lxc_ele)); + ele->name = xstrdup(p2); + ele->next = anchor; // push the new container name + anchor = ele; + return ele->name; // return a new container name + } + } + return lxc_none; +} +/////////////////////////////////////////////////////////////////////// + + +/* These are some nice GNU C expression subscope "inline" functions. + * The can be used with arbitrary types and evaluate their arguments + * exactly once. + */ + +/* Test if item X of type T is present in the 0 terminated list L */ +# define XinL(T, X, L) ( { \ + T x = (X), *l = (L); \ + while (*l && *l != x) l++; \ + *l == x; \ + } ) + +/* Test if item X of type T is present in the list L of length N */ +# define XinLN(T, X, L, N) ( { \ + T x = (X), *l = (L); \ + int i = 0, n = (N); \ + while (i < n && l[i] != x) i++; \ + i < n && l[i] == x; \ + } ) + +////////////////////////////////////////////////////////////////////////////////// +// This reads process info from /proc in the traditional way, for one process. +// The pid (tgid? tid?) is already in p, and a path to it in path, with some +// room to spare. +static proc_t* simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) { + static struct utlbuf_s ub = { NULL, 0 }; // buf for stat,statm,status + static struct stat sb; // stat() buffer + char *restrict const path = PT->path; + unsigned flags = PT->flags; + + if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ + goto next_proc; + + if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) + goto next_proc; /* not one of the requested uids */ + + p->euid = sb.st_uid; /* need a way to get real uid */ + p->egid = sb.st_gid; /* need a way to get real gid */ + + if (flags & PROC_FILLSTAT) { // read /proc/#/stat + if (unlikely(file2str(path, "stat", &ub) == -1)) + goto next_proc; + stat2proc(ub.buf, p); + } + + if (flags & PROC_FILLMEM) { // read /proc/#/statm + if (likely(file2str(path, "statm", &ub) != -1)) + statm2proc(ub.buf, p); + } + + if (flags & PROC_FILLSTATUS) { // read /proc/#/status + if (likely(file2str(path, "status", &ub) != -1)){ + status2proc(ub.buf, p, 1); + if (flags & PROC_FILLSUPGRP) + supgrps_from_supgids(p); + } + } + + // if multithreaded, some values are crap + if(p->nlwp > 1){ + p->wchan = (KLONG)~0ull; + } + + /* some number->text resolving which is time consuming */ + if (flags & PROC_FILLUSR){ + memcpy(p->euser, pwcache_get_user(p->euid), sizeof p->euser); + if(flags & PROC_FILLSTATUS) { + memcpy(p->ruser, pwcache_get_user(p->ruid), sizeof p->ruser); + memcpy(p->suser, pwcache_get_user(p->suid), sizeof p->suser); + memcpy(p->fuser, pwcache_get_user(p->fuid), sizeof p->fuser); + } + } + + /* some number->text resolving which is time consuming */ + if (flags & PROC_FILLGRP){ + memcpy(p->egroup, pwcache_get_group(p->egid), sizeof p->egroup); + if(flags & PROC_FILLSTATUS) { + memcpy(p->rgroup, pwcache_get_group(p->rgid), sizeof p->rgroup); + memcpy(p->sgroup, pwcache_get_group(p->sgid), sizeof p->sgroup); + memcpy(p->fgroup, pwcache_get_group(p->fgid), sizeof p->fgroup); + } + } + + if (unlikely(flags & PROC_FILLENV)) { // read /proc/#/environ + if (flags & PROC_EDITENVRCVT) + fill_environ_cvt(path, p); + else + p->environ = file2strvec(path, "environ"); + } + + if (flags & (PROC_FILLCOM|PROC_FILLARG)) { // read /proc/#/cmdline + if (flags & PROC_EDITCMDLCVT) + fill_cmdline_cvt(path, p); + else + p->cmdline = file2strvec(path, "cmdline"); + } + + if ((flags & PROC_FILLCGROUP)) { // read /proc/#/cgroup + if (flags & PROC_EDITCGRPCVT) + fill_cgroup_cvt(path, p); + else + p->cgroup = file2strvec(path, "cgroup"); + } + + if (unlikely(flags & PROC_FILLOOM)) { + if (likely(file2str(path, "oom_score", &ub) != -1)) + oomscore2proc(ub.buf, p); + if (likely(file2str(path, "oom_adj", &ub) != -1)) + oomadj2proc(ub.buf, p); + } + + if (unlikely(flags & PROC_FILLNS)) // read /proc/#/ns/* + ns2proc(path, p); + + if (unlikely(flags & PROC_FILLSYSTEMD)) // get sd-login.h stuff + sd2proc(p); + + if (unlikely(flags & PROC_FILL_LXC)) // value the lxc name + p->lxcname = lxc_containers(path); + + return p; +next_proc: + return NULL; +} + +////////////////////////////////////////////////////////////////////////////////// +// This reads /proc/*/task/* data, for one task. +#ifdef QUICK_THREADS +// p is the POSIX process (task group summary) & source for some copies if !NULL +#else +// p is the POSIX process (task group summary) (not needed by THIS implementation) +#endif +// t is the POSIX thread (task group member, generally not the leader) +// path is a path to the task, with some room to spare. +static proc_t* simple_readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) { + static struct utlbuf_s ub = { NULL, 0 }; // buf for stat,statm,status + static struct stat sb; // stat() buffer + unsigned flags = PT->flags; + + if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ + goto next_task; + +// if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) +// goto next_task; /* not one of the requested uids */ + + t->euid = sb.st_uid; /* need a way to get real uid */ + t->egid = sb.st_gid; /* need a way to get real gid */ + + if (flags & PROC_FILLSTAT) { // read /proc/#/task/#/stat + if (unlikely(file2str(path, "stat", &ub) == -1)) + goto next_task; + stat2proc(ub.buf, t); + } + +#ifndef QUICK_THREADS + if (flags & PROC_FILLMEM) // read /proc/#/task/#statm + if (likely(file2str(path, "statm", &ub) != -1)) + statm2proc(ub.buf, t); +#endif + + if (flags & PROC_FILLSTATUS) { // read /proc/#/task/#/status + if (likely(file2str(path, "status", &ub) != -1)) { + status2proc(ub.buf, t, 0); +#ifndef QUICK_THREADS + if (flags & PROC_FILLSUPGRP) + supgrps_from_supgids(t); +#endif + } + } + + /* some number->text resolving which is time consuming */ + if (flags & PROC_FILLUSR){ + memcpy(t->euser, pwcache_get_user(t->euid), sizeof t->euser); + if(flags & PROC_FILLSTATUS) { + memcpy(t->ruser, pwcache_get_user(t->ruid), sizeof t->ruser); + memcpy(t->suser, pwcache_get_user(t->suid), sizeof t->suser); + memcpy(t->fuser, pwcache_get_user(t->fuid), sizeof t->fuser); + } + } + + /* some number->text resolving which is time consuming */ + if (flags & PROC_FILLGRP){ + memcpy(t->egroup, pwcache_get_group(t->egid), sizeof t->egroup); + if(flags & PROC_FILLSTATUS) { + memcpy(t->rgroup, pwcache_get_group(t->rgid), sizeof t->rgroup); + memcpy(t->sgroup, pwcache_get_group(t->sgid), sizeof t->sgroup); + memcpy(t->fgroup, pwcache_get_group(t->fgid), sizeof t->fgroup); + } + } + +#ifdef QUICK_THREADS + if (!p) { + if (flags & PROC_FILLMEM) + if (likely(file2str(path, "statm", &ub) != -1)) + statm2proc(ub.buf, t); + + if (flags & PROC_FILLSUPGRP) + supgrps_from_supgids(t); +#endif + if (unlikely(flags & PROC_FILLENV)) { // read /proc/#/task/#/environ + if (flags & PROC_EDITENVRCVT) + fill_environ_cvt(path, t); + else + t->environ = file2strvec(path, "environ"); + } + + if (flags & (PROC_FILLCOM|PROC_FILLARG)) { // read /proc/#/task/#/cmdline + if (flags & PROC_EDITCMDLCVT) + fill_cmdline_cvt(path, t); + else + t->cmdline = file2strvec(path, "cmdline"); + } + + if ((flags & PROC_FILLCGROUP)) { // read /proc/#/task/#/cgroup + if (flags & PROC_EDITCGRPCVT) + fill_cgroup_cvt(path, t); + else + t->cgroup = file2strvec(path, "cgroup"); + } + + if (unlikely(flags & PROC_FILLSYSTEMD)) // get sd-login.h stuff + sd2proc(t); + + if (unlikely(flags & PROC_FILL_LXC)) // value the lxc name + t->lxcname = lxc_containers(path); + +#ifdef QUICK_THREADS + } else { + t->size = p->size; + t->resident = p->resident; + t->share = p->share; + t->trs = p->trs; + t->lrs = p->lrs; + t->drs = p->drs; + t->dt = p->dt; + t->cmdline = p->cmdline; // better not free these until done with all threads! + t->environ = p->environ; + t->cgname = p->cgname; + t->cgroup = p->cgroup; + if (t->supgid) free(t->supgid); + t->supgid = p->supgid; + t->supgrp = p->supgrp; + t->sd_mach = p->sd_mach; + t->sd_ouid = p->sd_ouid; + t->sd_seat = p->sd_seat; + t->sd_sess = p->sd_sess; + t->sd_slice = p->sd_slice; + t->sd_unit = p->sd_unit; + t->sd_uunit = p->sd_uunit; + t->lxcname = p->lxcname; + MK_THREAD(t); + } +#endif + + if (unlikely(flags & PROC_FILLOOM)) { + if (likely(file2str(path, "oom_score", &ub) != -1)) + oomscore2proc(ub.buf, t); + if (likely(file2str(path, "oom_adj", &ub) != -1)) + oomadj2proc(ub.buf, t); + } + + if (unlikely(flags & PROC_FILLNS)) // read /proc/#/task/#/ns/* + ns2proc(path, t); + + return t; +next_task: + return NULL; +#ifndef QUICK_THREADS + (void)p; +#endif +} + +////////////////////////////////////////////////////////////////////////////////// +// This finds processes in /proc in the traditional way. +// Return non-zero on success. +static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) { + static struct dirent *ent; /* dirent handle */ + char *restrict const path = PT->path; + for (;;) { + ent = readdir(PT->procfs); + if(unlikely(unlikely(!ent) || unlikely(!ent->d_name[0]))) return 0; + if(likely(likely(*ent->d_name > '0') && likely(*ent->d_name <= '9'))) break; + } + p->tgid = strtoul(ent->d_name, NULL, 10); + p->tid = p->tgid; + snprintf(path, PROCPATHLEN, "/proc/%s", ent->d_name); + return 1; +} + +////////////////////////////////////////////////////////////////////////////////// +// This finds tasks in /proc/*/task/ in the traditional way. +// Return non-zero on success. +static int simple_nexttid(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) { + static struct dirent *ent; /* dirent handle */ + if(PT->taskdir_user != p->tgid){ + if(PT->taskdir){ + closedir(PT->taskdir); + } + // use "path" as some tmp space + snprintf(path, PROCPATHLEN, "/proc/%d/task", p->tgid); + PT->taskdir = opendir(path); + if(!PT->taskdir) return 0; + PT->taskdir_user = p->tgid; + } + for (;;) { + ent = readdir(PT->taskdir); + if(unlikely(unlikely(!ent) || unlikely(!ent->d_name[0]))) return 0; + if(likely(likely(*ent->d_name > '0') && likely(*ent->d_name <= '9'))) break; + } + t->tid = strtoul(ent->d_name, NULL, 10); + t->tgid = p->tgid; +//t->ppid = p->ppid; // cover for kernel behavior? we want both actually...? + snprintf(path, PROCPATHLEN, "/proc/%d/task/%s", p->tgid, ent->d_name); + return 1; +} + +////////////////////////////////////////////////////////////////////////////////// +// This "finds" processes in a list that was given to openproc(). +// Return non-zero on success. (tgid was handy) +static int listed_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) { + char *restrict const path = PT->path; + pid_t tgid = *(PT->pids)++; + if(likely(tgid)){ + snprintf(path, PROCPATHLEN, "/proc/%d", tgid); + p->tgid = tgid; + p->tid = tgid; // they match for leaders + } + return tgid; +} + +////////////////////////////////////////////////////////////////////////////////// +/* readproc: return a pointer to a proc_t filled with requested info about the + * next process available matching the restriction set. If no more such + * processes are available, return a null pointer (boolean false). Use the + * passed buffer instead of allocating space if it is non-NULL. */ + +/* This is optimized so that if a PID list is given, only those files are + * searched for in /proc. If other lists are given in addition to the PID list, + * the same logic can follow through as for the no-PID list case. This is + * fairly complex, but it does try to not to do any unnecessary work. + */ +proc_t* readproc(PROCTAB *restrict const PT, proc_t *restrict p) { + proc_t *ret; + proc_t *saved_p; + + PT->did_fake=0; +// if (PT->taskdir) { +// closedir(PT->taskdir); +// PT->taskdir = NULL; +// PT->taskdir_user = -1; +// } + + saved_p = p; + if(!p) p = xcalloc(sizeof *p); + else free_acquired(p, 1); + + for(;;){ + // fills in the path, plus p->tid and p->tgid + if (unlikely(!PT->finder(PT,p))) goto out; + + // go read the process data + ret = PT->reader(PT,p); + if(ret) return ret; + } + +out: + if(!saved_p) free(p); + // FIXME: maybe set tid to -1 here, for "-" in display? + return NULL; +} + +////////////////////////////////////////////////////////////////////////////////// +// readtask: return a pointer to a proc_t filled with requested info about the +// next task available. If no more such tasks are available, return a null +// pointer (boolean false). Use the passed buffer instead of allocating +// space if it is non-NULL. +proc_t* readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict t) { + char path[PROCPATHLEN]; // must hold /proc/2000222000/task/2000222000/cmdline + proc_t *ret; + proc_t *saved_t; + + saved_t = t; + if(!t) t = xcalloc(sizeof *t); + else free_acquired(t, 1); + + // 1. got to fake a thread for old kernels +#ifdef QUICK_THREADS + // 2. for single-threaded processes, this is faster (but must patch up stuff that differs!) + if(task_dir_missing || p->nlwp < 2){ +#else + if(task_dir_missing){ +#endif + if(PT->did_fake) goto out; + PT->did_fake=1; + memcpy(t,p,sizeof(proc_t)); + // use the per-task pending, not per-tgid pending +#ifdef SIGNAL_STRING + memcpy(&t->signal, &t->_sigpnd, sizeof t->signal); +#else + t->signal = t->_sigpnd; +#endif +#ifdef QUICK_THREADS + MK_THREAD(t); +#else + t->environ = NULL; + t->cmdline = vectorize_this_str("n/a"); + t->cgroup = NULL; + t->cgname = NULL; + t->supgid = NULL; + t->supgrp = NULL; + t->sd_mach = NULL; + t->sd_ouid = NULL; + t->sd_seat = NULL; + t->sd_sess = NULL; + t->sd_slice = NULL; + t->sd_unit = NULL; + t->sd_uunit = NULL; +#endif + return t; + } + + for(;;){ + // fills in the path, plus t->tid and t->tgid + if (unlikely(!PT->taskfinder(PT,p,t,path))) goto out; // simple_nexttid + + // go read the task data + ret = PT->taskreader(PT,p,t,path); // simple_readtask + if(ret) return ret; + } + +out: + if(!saved_t) free(t); + return NULL; +} + +////////////////////////////////////////////////////////////////////////////////// +// readeither: return a pointer to a proc_t filled with requested info about +// the next unique process or task available. If no more are available, +// return a null pointer (boolean false). Use the passed buffer instead +// of allocating space if it is non-NULL. +proc_t* readeither (PROCTAB *restrict const PT, proc_t *restrict x) { + static proc_t skel_p; // skeleton proc_t, only uses tid + tgid + static proc_t *new_p; // for process/task transitions + static int canary; + char path[PROCPATHLEN]; + proc_t *saved_x, *ret; + + saved_x = x; + if (!x) x = xcalloc(sizeof(*x)); + else free_acquired(x,1); + if (new_p) { + if (new_p->tid != canary) new_p = NULL; + goto next_task; + } + +next_proc: + new_p = NULL; + for (;;) { + // fills in the PT->path, plus skel_p.tid and skel_p.tgid + if (!PT->finder(PT,&skel_p)) goto end_procs; // simple_nextpid + if (!task_dir_missing) break; + if ((ret = PT->reader(PT,x))) return ret; // simple_readproc + } + +next_task: + // fills in our path, plus x->tid and x->tgid + if ((!(PT->taskfinder(PT,&skel_p,x,path))) // simple_nexttid + || (!(ret = PT->taskreader(PT,new_p,x,path)))) { // simple_readtask + goto next_proc; + } + if (!new_p) { + new_p = ret; + canary = new_p->tid; + } + return ret; + +end_procs: + if (!saved_x) free(x); + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////////// + +// initiate a process table scan +PROCTAB* openproc(int flags, ...) { + va_list ap; + struct stat sbuf; + static int did_stat; + PROCTAB* PT = xcalloc(sizeof(PROCTAB)); + + if (!did_stat){ + task_dir_missing = stat("/proc/self/task", &sbuf); + did_stat = 1; + } + PT->taskdir = NULL; + PT->taskdir_user = -1; + PT->taskfinder = simple_nexttid; + PT->taskreader = simple_readtask; + + PT->reader = simple_readproc; + if (flags & PROC_PID){ + PT->procfs = NULL; + PT->finder = listed_nextpid; + }else{ + PT->procfs = opendir("/proc"); + if (!PT->procfs) { free(PT); return NULL; } + PT->finder = simple_nextpid; + } + PT->flags = flags; + + va_start(ap, flags); + if (flags & PROC_PID) + PT->pids = va_arg(ap, pid_t*); + else if (flags & PROC_UID){ + PT->uids = va_arg(ap, uid_t*); + PT->nuid = va_arg(ap, int); + } + va_end(ap); + + if (!src_buffer){ + src_buffer = xmalloc(MAX_BUFSZ); + dst_buffer = xmalloc(MAX_BUFSZ); + } + return PT; +} + +// terminate a process table scan +void closeproc(PROCTAB* PT) { + if (PT){ + if (PT->procfs) closedir(PT->procfs); + if (PT->taskdir) closedir(PT->taskdir); + memset(PT,'#',sizeof(PROCTAB)); + free(PT); + } +} + +// deallocate space allocated by readproc +void freeproc(proc_t* p) { + if (p) { + free_acquired(p, 0); + free(p); + } +} + + +////////////////////////////////////////////////////////////////////////////////// +void look_up_our_self(proc_t *p) { + struct utlbuf_s ub = { NULL, 0 }; + + if(file2str("/proc/self", "stat", &ub) == -1){ + fprintf(stderr, "Error, do this: mount -t proc proc /proc\n"); + _exit(47); + } + stat2proc(ub.buf, p); // parse /proc/self/stat + free(ub.buf); +} + +HIDDEN_ALIAS(readproc); +HIDDEN_ALIAS(readtask); +HIDDEN_ALIAS(readeither); + +/* Convenient wrapper around openproc and readproc to slurp in the whole process + * table subset satisfying the constraints of flags and the optional PID list. + * Free allocated memory with exit(). Access via tab[N]->member. The pointer + * list is NULL terminated. + */ +proc_t** readproctab(int flags, ...) { + PROCTAB* PT = NULL; + proc_t** tab = NULL; + int n = 0; + va_list ap; + + va_start(ap, flags); /* pass through args to openproc */ + if (flags & PROC_UID) { + /* temporary variables to ensure that va_arg() instances + * are called in the right order + */ + uid_t* u; + int i; + + u = va_arg(ap, uid_t*); + i = va_arg(ap, int); + PT = openproc(flags, u, i); + } + else if (flags & PROC_PID) + PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */ + else + PT = openproc(flags); + va_end(ap); + if (!PT) + return 0; + do { /* read table: */ + if (n < 0 || (size_t)n >= INT_MAX / sizeof(proc_t*)) { + xalloc_err_handler("integer overflow in %s (%s=%zu)", __func__, "n", (size_t)n); + exit(EXIT_FAILURE); + } + tab = xrealloc(tab, (n+1)*sizeof(proc_t*));/* realloc as we go, using */ + tab[n] = readproc_direct(PT, NULL); /* final null to terminate */ + } while (tab[n++]); /* stop when NULL reached */ + closeproc(PT); + return tab; +} + +#define grow_by_size(ptr, nmemb, over, size) do { \ + if ((size_t)(nmemb) >= INT_MAX / 5) { \ + xalloc_err_handler("integer overflow in %s (%s=%zu)", __func__, #nmemb, (size_t)(nmemb)); \ + exit(EXIT_FAILURE); \ + } \ + (nmemb) = (nmemb) * 5 / 4 + (over); \ + if ((size_t)(nmemb) >= SSIZE_MAX / (size)) { \ + xalloc_err_handler("integer overflow in %s (%s=%zu)", __func__, #nmemb, (size_t)(nmemb)); \ + exit(EXIT_FAILURE); \ + } \ + (ptr) = xrealloc((ptr), (nmemb) * (size)); \ +} while (0) + +// Try again, this time with threads and selection. +proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(proc_t *buf), PROCTAB *restrict const PT) { + static proc_data_t pd; + proc_t** ptab = NULL; + size_t n_proc_alloc = 0; + size_t n_proc = 0; + + proc_t** ttab = NULL; + size_t n_task_alloc = 0; + size_t n_task = 0; + + proc_t* data = NULL; + size_t n_alloc = 0; + uintptr_t n_used = 0; + + for(;;){ + proc_t *tmp; + if(n_alloc == n_used){ + //proc_t *old = data; + grow_by_size(data, n_alloc, 30, sizeof(proc_t)); + memset(data+n_used, 0, sizeof(proc_t)*(n_alloc-n_used)); + } + if(n_proc_alloc == n_proc){ + //proc_t **old = ptab; + grow_by_size(ptab, n_proc_alloc, 30, sizeof(proc_t*)); + } + tmp = readproc_direct(PT, data+n_used); + if(!tmp) break; + if(!want_proc(tmp)) continue; + ptab[n_proc++] = (proc_t*)(n_used++); + if(!( PT->flags & PROC_LOOSE_TASKS )) continue; + for(;;){ + proc_t *t; + if(n_alloc == n_used){ + proc_t *old = data; + grow_by_size(data, n_alloc, 30, sizeof(proc_t)); + // have to move tmp too + tmp = data+(tmp-old); + memset(data+n_used, 0, sizeof(proc_t)*(n_alloc-n_used)); + } + if(n_task_alloc == n_task){ + //proc_t **old = ttab; + grow_by_size(ttab, n_task_alloc, 1, sizeof(proc_t*)); + } + t = readtask_direct(PT, tmp, data+n_used); + if(!t) break; + if(!want_task(t)) continue; + ttab[n_task++] = (proc_t*)(n_used++); + } + } + + pd.proc = ptab; + pd.task = ttab; + pd.nproc = n_proc; + pd.ntask = n_task; + if(PT->flags & PROC_LOOSE_TASKS){ + pd.tab = ttab; + pd.n = n_task; + }else{ + pd.tab = ptab; + pd.n = n_proc; + } + // change array indexes to pointers + while(n_proc--) ptab[n_proc] = data+(uintptr_t)(ptab[n_proc]); + while(n_task--) ttab[n_task] = data+(uintptr_t)(ttab[n_task]); + + return &pd; +} + +// Try try yet again, this time treating processes and threads the same... +proc_data_t *readproctab3 (int(*want_task)(proc_t *buf), PROCTAB *restrict const PT) { + static proc_data_t pd; + proc_t **tab = NULL; + size_t n_alloc = 0; + size_t n_used = 0; + proc_t *p = NULL; + + for (;;) { + if (n_alloc == n_used) { + grow_by_size(tab, n_alloc, 30, sizeof(proc_t*)); + } + // let this next guy allocate the necessary proc_t storage + // (or recycle it) since he can't tolerate realloc relocations + if (!(p = readeither_direct(PT,p))) break; + if (want_task(p)) { + tab[n_used++] = p; + p = NULL; + } + } + + pd.tab = tab; + pd.n = n_used; + return &pd; +} + +/* + * get_proc_stats - lookup a single tasks information and fill out a proc_t + * + * On failure, returns NULL. On success, returns 'p' and 'p' is a valid + * and filled out proc_t structure. + */ +proc_t * get_proc_stats(pid_t pid, proc_t *p) { + struct utlbuf_s ub = { NULL, 0 }; + static char path[32]; + struct stat statbuf; + + snprintf(path, sizeof path, "/proc/%d", pid); + if (stat(path, &statbuf)) { + perror("stat"); + return NULL; + } + + if (file2str(path, "stat", &ub) >= 0) + stat2proc(ub.buf, p); + if (file2str(path, "statm", &ub) >= 0) + statm2proc(ub.buf, p); + if (file2str(path, "status", &ub) >= 0) + status2proc(ub.buf, p, 0); + + free(ub.buf); + return p; +} + +#undef MK_THREAD +#undef IS_THREAD +#undef MAX_BUFSZ diff --git a/src/readproc.h b/src/readproc.h new file mode 100644 index 0000000..7905ea9 --- /dev/null +++ b/src/readproc.h @@ -0,0 +1,315 @@ +#ifndef PROCPS_PROC_READPROC_H +#define PROCPS_PROC_READPROC_H + +// New Interface to Process Table -- PROCTAB Stream (a la Directory streams) +// Copyright 1996 Charles L. Blake. +// Copyright 1998 Michael K. Johnson +// Copyright 1998-2003 Albert Cahalan +// May be distributed under the terms of the +// GNU Library General Public License, a copy of which is provided +// in the file COPYING + + +#include "procps.h" +#include "pwcache.h" + +#define SIGNAL_STRING +//#define QUICK_THREADS /* copy (vs. read) some thread info from parent proc_t */ + +EXTERN_C_BEGIN + +// ld cutime, cstime, priority, nice, timeout, alarm, rss, +// c state, +// d ppid, pgrp, session, tty, tpgid, +// s signal, blocked, sigignore, sigcatch, +// lu flags, min_flt, cmin_flt, maj_flt, cmaj_flt, utime, stime, +// lu rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip, +// lu start_time, vsize, wchan, + +// This is to help document a transition from pid to tgid/tid caused +// by the introduction of thread support. It is used in cases where +// neither tgid nor tid seemed correct. (in other words, FIXME) +#define XXXID tid + +enum ns_type { + IPCNS = 0, + MNTNS, + NETNS, + PIDNS, + USERNS, + UTSNS, + NUM_NS // total namespaces (fencepost) +}; +extern const char *get_ns_name(int id); +extern int get_ns_id(const char *name); + +// Basic data structure which holds all information we can get about a process. +// (unless otherwise specified, fields are read from /proc/#/stat) +// +// Most of it comes from task_struct in linux/sched.h +// +typedef struct proc_t { +// 1st 16 bytes + int + tid, // (special) task id, the POSIX thread ID (see also: tgid) + ppid; // stat,status pid of parent process + unsigned long // next 2 fields are NOT filled in by readproc + maj_delta, // stat (special) major page faults since last update + min_delta; // stat (special) minor page faults since last update + unsigned + pcpu; // stat (special) %CPU usage (is not filled in by readproc!!!) + char + state, // stat,status single-char code for process state (S=sleeping) +#ifdef QUICK_THREADS + pad_1, // n/a padding (psst, also used if multi-threaded) +#else + pad_1, // n/a padding +#endif + pad_2, // n/a padding + pad_3; // n/a padding +// 2nd 16 bytes + unsigned long long + utime, // stat user-mode CPU time accumulated by process + stime, // stat kernel-mode CPU time accumulated by process +// and so on... + cutime, // stat cumulative utime of process and reaped children + cstime, // stat cumulative stime of process and reaped children + start_time; // stat start time of process -- seconds since system boot +#ifdef SIGNAL_STRING + char + // Linux 2.1.7x and up have 64 signals. Allow 64, plus '\0' and padding. + signal[18], // status mask of pending signals, per-task for readtask() but per-proc for readproc() + blocked[18], // status mask of blocked signals + sigignore[18], // status mask of ignored signals + sigcatch[18], // status mask of caught signals + _sigpnd[18]; // status mask of PER TASK pending signals +#else + long long + // Linux 2.1.7x and up have 64 signals. + signal, // status mask of pending signals, per-task for readtask() but per-proc for readproc() + blocked, // status mask of blocked signals + sigignore, // status mask of ignored signals + sigcatch, // status mask of caught signals + _sigpnd; // status mask of PER TASK pending signals +#endif + unsigned KLONG + start_code, // stat address of beginning of code segment + end_code, // stat address of end of code segment + start_stack, // stat address of the bottom of stack for the process + kstk_esp, // stat kernel stack pointer + kstk_eip, // stat kernel instruction pointer + wchan; // stat (special) address of kernel wait channel proc is sleeping in + long + priority, // stat kernel scheduling priority + nice, // stat standard unix nice level of process + rss, // stat identical to 'resident' + alarm, // stat ? + // the next 7 members come from /proc/#/statm + size, // statm total virtual memory (as # pages) + resident, // statm resident non-swapped memory (as # pages) + share, // statm shared (mmap'd) memory (as # pages) + trs, // statm text (exe) resident set (as # pages) + lrs, // statm library resident set (always 0 w/ 2.6) + drs, // statm data+stack resident set (as # pages) + dt; // statm dirty pages (always 0 w/ 2.6) + unsigned long + vm_size, // status equals 'size' (as kb) + vm_lock, // status locked pages (as kb) + vm_rss, // status equals 'rss' and/or 'resident' (as kb) + vm_rss_anon, // status the 'anonymous' portion of vm_rss (as kb) + vm_rss_file, // status the 'file-backed' portion of vm_rss (as kb) + vm_rss_shared, // status the 'shared' portion of vm_rss (as kb) + vm_data, // status data only size (as kb) + vm_stack, // status stack only size (as kb) + vm_swap, // status based on linux-2.6.34 "swap ents" (as kb) + vm_exe, // status equals 'trs' (as kb) + vm_lib, // status total, not just used, library pages (as kb) + rtprio, // stat real-time priority + sched, // stat scheduling class + vsize, // stat number of pages of virtual memory ... + rss_rlim, // stat resident set size limit? + flags, // stat kernel flags for the process + min_flt, // stat number of minor page faults since process start + maj_flt, // stat number of major page faults since process start + cmin_flt, // stat cumulative min_flt of process and child processes + cmaj_flt; // stat cumulative maj_flt of process and child processes + char + **environ, // (special) environment string vector (/proc/#/environ) + **cmdline, // (special) command line string vector (/proc/#/cmdline) + **cgroup, // (special) cgroup string vector (/proc/#/cgroup) + *cgname, // (special) name portion of above (if possible) + *supgid, // status supplementary gids as comma delimited str + *supgrp; // supp grp names as comma delimited str, derived from supgid + char + // Be compatible: Digital allows 16 and NT allows 14 ??? + euser[P_G_SZ], // stat(),status effective user name + ruser[P_G_SZ], // status real user name + suser[P_G_SZ], // status saved user name + fuser[P_G_SZ], // status filesystem user name + rgroup[P_G_SZ], // status real group name + egroup[P_G_SZ], // status effective group name + sgroup[P_G_SZ], // status saved group name + fgroup[P_G_SZ], // status filesystem group name + cmd[64]; // stat,status basename of executable file in call to exec(2) + struct proc_t + *ring, // n/a thread group ring + *next; // n/a various library uses + int + pgrp, // stat process group id + session, // stat session id + nlwp, // stat,status number of threads, or 0 if no clue + tgid, // (special) thread group ID, the POSIX PID (see also: tid) + tty, // stat full device number of controlling terminal + /* FIXME: int uids & gids should be uid_t or gid_t from pwd.h */ + euid, egid, // stat(),status effective + ruid, rgid, // status real + suid, sgid, // status saved + fuid, fgid, // status fs (used for file access only) + tpgid, // stat terminal process group id + exit_signal, // stat might not be SIGCHLD + processor; // stat current (or most recent?) CPU + int + oom_score, // oom_score (badness for OOM killer) + oom_adj; // oom_adj (adjustment to OOM score) + long + ns[NUM_NS]; // (ns subdir) inode number of namespaces + char + *sd_mach, // n/a systemd vm/container name + *sd_ouid, // n/a systemd session owner uid + *sd_seat, // n/a systemd login session seat + *sd_sess, // n/a systemd login session id + *sd_slice, // n/a systemd slice unit + *sd_unit, // n/a systemd system unit id + *sd_uunit; // n/a systemd user unit id + const char + *lxcname; // n/a lxc container name +} proc_t; + +// PROCTAB: data structure holding the persistent information readproc needs +// from openproc(). The setup is intentionally similar to the dirent interface +// and other system table interfaces (utmp+wtmp come to mind). + +#include +#include +#include + +#define PROCPATHLEN 64 // must hold /proc/2000222000/task/2000222000/cmdline + +typedef struct PROCTAB { + DIR* procfs; +// char deBug0[64]; + DIR* taskdir; // for threads +// char deBug1[64]; + pid_t taskdir_user; // for threads + int did_fake; // used when taskdir is missing + int(*finder)(struct PROCTAB *__restrict const, proc_t *__restrict const); + proc_t*(*reader)(struct PROCTAB *__restrict const, proc_t *__restrict const); + int(*taskfinder)(struct PROCTAB *__restrict const, const proc_t *__restrict const, proc_t *__restrict const, char *__restrict const); + proc_t*(*taskreader)(struct PROCTAB *__restrict const, const proc_t *__restrict const, proc_t *__restrict const, char *__restrict const); + pid_t* pids; // pids of the procs + uid_t* uids; // uids of procs + int nuid; // cannot really sentinel-terminate unsigned short[] + int i; // generic + unsigned flags; + unsigned u; // generic + void * vp; // generic + char path[PROCPATHLEN]; // must hold /proc/2000222000/task/2000222000/cmdline + unsigned pathlen; // length of string in the above (w/o '\0') +} PROCTAB; + +// Initialize a PROCTAB structure holding needed call-to-call persistent data +extern PROCTAB* openproc(int flags, ... /* pid_t*|uid_t*|dev_t*|char* [, int n] */ ); + +typedef struct proc_data_t { // valued by: (else zero) + proc_t **tab; // readproctab2, readproctab3 + proc_t **proc; // readproctab2 + proc_t **task; // * readproctab2 + int n; // readproctab2, readproctab3 + int nproc; // readproctab2 + int ntask; // * readproctab2 +} proc_data_t; // * when PROC_LOOSE_TASKS set + +extern proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(proc_t *buf), PROCTAB *__restrict const PT); +extern proc_data_t *readproctab3(int(*want_task)(proc_t *buf), PROCTAB *__restrict const PT); + +// Convenient wrapper around openproc and readproc to slurp in the whole process +// table subset satisfying the constraints of flags and the optional PID list. +// Free allocated memory with exit(). Access via tab[N]->member. The pointer +// list is NULL terminated. +extern proc_t** readproctab(int flags, ... /* same as openproc */ ); + +// Clean-up open files, etc from the openproc() +extern void closeproc(PROCTAB* PT); + +// Retrieve the next process or task matching the criteria set by the openproc(). +// +// Note: When NULL is used as the readproc 'p', readtask 't' or readeither 'x' +// parameter, the library will allocate the necessary proc_t storage. +// +// Alternatively, you may provide your own reuseable buffer address +// in which case that buffer *MUST* be initialized to zero one time +// only before first use. Thereafter, the library will manage such +// a passed proc_t, freeing any additional acquired memory associated +// with the previous process or thread. +extern proc_t* readproc(PROCTAB *__restrict const PT, proc_t *__restrict p); +extern proc_t* readtask(PROCTAB *__restrict const PT, const proc_t *__restrict const p, proc_t *__restrict t); +extern proc_t* readeither(PROCTAB *__restrict const PT, proc_t *__restrict x); + +// warning: interface may change +extern int read_cmdline(char *__restrict const dst, unsigned sz, unsigned pid); + +extern void look_up_our_self(proc_t *p); + +// Deallocate space allocated by readproc +extern void freeproc(proc_t* p); + +// Fill out a proc_t for a single task +extern proc_t * get_proc_stats(pid_t pid, proc_t *p); + +// openproc/readproctab: +// +// Return PROCTAB* / *proc_t[] or NULL on error ((probably) "/proc" cannot be +// opened.) By default readproc will consider all processes as valid to parse +// and return, but not actually fill in the cmdline, environ, and /proc/#/statm +// derived memory fields. +// +// `flags' (a bitwise-or of PROC_* below) modifies the default behavior. The +// "fill" options will cause more of the proc_t to be filled in. The "filter" +// options all use the second argument as the pointer to a list of objects: +// process status', process id's, user id's. The third +// argument is the length of the list (currently only used for lists of user +// id's since uid_t supports no convenient termination sentinel.) + +#define PROC_FILLMEM 0x0001 // read statm +#define PROC_FILLCOM 0x0002 // alloc and fill in `cmdline' +#define PROC_FILLENV 0x0004 // alloc and fill in `environ' +#define PROC_FILLUSR 0x0008 // resolve user id number -> user name +#define PROC_FILLGRP 0x0010 // resolve group id number -> group name +#define PROC_FILLSTATUS 0x0020 // read status +#define PROC_FILLSTAT 0x0040 // read stat +#define PROC_FILLARG 0x0100 // alloc and fill in `cmdline' +#define PROC_FILLCGROUP 0x0200 // alloc and fill in `cgroup` +#define PROC_FILLSUPGRP 0x0400 // resolve supplementary group id -> group name +#define PROC_FILLOOM 0x0800 // fill in proc_t oom_score and oom_adj +#define PROC_FILLNS 0x8000 // fill in proc_t namespace information +#define PROC_FILLSYSTEMD 0x80000 // fill in proc_t systemd information +#define PROC_FILL_LXC 0x800000 // fill in proc_t lxcname, if possible + +#define PROC_LOOSE_TASKS 0x2000 // treat threads as if they were processes + +// consider only processes with one of the passed: +#define PROC_PID 0x1000 // process id numbers ( 0 terminated) +#define PROC_UID 0x4000 // user id numbers ( length needed ) + +#define PROC_EDITCGRPCVT 0x10000 // edit `cgroup' as single vector +#define PROC_EDITCMDLCVT 0x20000 // edit `cmdline' as single vector +#define PROC_EDITENVRCVT 0x40000 // edit `environ' as single vector + +// it helps to give app code a few spare bits +#define PROC_SPARE_1 0x01000000 +#define PROC_SPARE_2 0x02000000 +#define PROC_SPARE_3 0x04000000 +#define PROC_SPARE_4 0x08000000 + +EXTERN_C_END +#endif