├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── lib ├── list.c └── list.h ├── lua.c ├── scripts └── spank_demo.lua ├── slurm-spank-lua.spec └── spank-lua.8 /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | lua.so 3 | pkg/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default 2 | default: lua.so ; 3 | 4 | VERSION = $(shell git describe --tags --abbrev=0 | sed -r 's/^v//g') 5 | PKGDIR = $(shell pwd)/pkg 6 | package: 7 | mkdir -p $(PKGDIR) 8 | git ls-files | tar -c --transform 's,^,slurm-spank-lua-$(VERSION)/,' -T - | gzip > $(PKGDIR)/slurm-spank-lua-$(VERSION).tar.gz 9 | 10 | CFLAGS = -fPIC -g 11 | clean: 12 | rm -f lua.o lib/list.o lua.so 13 | lua.so: lua.o lib/list.o 14 | cc -shared -fPIC -o $@ $^ -llua 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > The Lua SPANK plugin for Slurm has been originally developed by [Mark 3 | Grondo](https://github.com/grondo) at [Lawrence Livermore National 4 | Laboratory](https://www.llnl.gov/). This is extracted from the [LLNL Slurm 5 | SPANK plugins](https://github.com/grondo/slurm-spank-plugins) project and 6 | re-packaged to only provide the LUA SPANK plugin, for easier installation. 7 | 8 | 9 | # Slurm SPANK Lua plugin 10 | 11 | The [Lua](https://www.lua.org/) [SPANK](https://slurm.schedmd.com/spank.html) 12 | plugin for [Slurm](https://slurm.schedmd.com/) allows Lua scripts to take the 13 | place of compiled C shared objects in the Slurm `spank(8)` framework. All the 14 | power of the C SPANK API is exported to Lua via this plugin, which loads one or 15 | more scripts and executes Lua functions during the appropriate Slurm phase (as 16 | described in the `spank(8)` manpage). 17 | 18 | 19 | 20 | 21 | ## Installation 22 | 23 | ### Assumptions 24 | 25 | To facilitate co-existence with other SPANK plugins, we'll assume that the file 26 | defined in `PlugStackConfig` (by default, `/etc/slurm/plugstack.conf`) contains 27 | something like: 28 | ``` 29 | include /etc/slurm/plugstack.conf.d/*.conf 30 | ``` 31 | so we can enable the Slurm SPANK Lua plugin by adding a `lua.conf` file in 32 | `/etc/slurm/plugstack.conf.d/` 33 | 34 | ### RPM-based installation (recommended) 35 | 36 | Get the source and build the RPM: 37 | 38 | ``` 39 | $ REPO=stanford-rc 40 | $ PROJ=slurm-spank-lua 41 | $ VER=$(curl -s https://api.github.com/repos/$REPO/$PROJ/releases/latest | awk '/tag_name/ {gsub(/"|,/,""); print $2}') 42 | $ wget https://github.com/$REPO/$PROJ/archive/${VER}.tar.gz -O slurm-spank-lua-${VER//v}.tar.gz 43 | $ rpmbuild -ta slurm-spank-lua-${VER//v}.tar.gz 44 | ``` 45 | 46 | Install the generated RPM. For instance (paths, arch and version may vary): 47 | 48 | ``` 49 | # rpm -Uvh ~/rpmbuild/RPMS/x86_64/slurm-spank-lua-0.38-1.x86_64.rpm 50 | ``` 51 | 52 | ### Manual installation 53 | 54 | The source can be compiled with: 55 | ``` 56 | $ cc -o lua.o -fPIC -c lua.c 57 | $ cc -o lib/list.o -fPIC -c lib/list.c 58 | $ cc -shared -fPIC -o lua.so lua.o lib/list.o -llua 59 | ``` 60 | 61 | or just: 62 | ``` 63 | $ make 64 | ``` 65 | 66 | Then, the generated `lua.so` should be placed in `/usr/lib/slurm`, and 67 | `lua.conf` in `/etc/slurm/plugstackconf.d/`. 68 | 69 | 70 | ## Configuration 71 | 72 | The RPM will install a `lua.conf` file in `/etc/slurm/plugstackconf.d/` that 73 | contains: 74 | ``` 75 | required lua.so /etc/slurm/lua.d/*.lua 76 | ``` 77 | Then, Lua scripts could simply be dropped in `/etc/slurm/lua.d/` to be 78 | automatically executed by Slurm. 79 | 80 | 81 | 82 | 83 | The RPM provides a demo script, `spank_demo.lua`, based on the [C SPANK demo 84 | plugin](https://github.com/yqin/slurm-plugins/blob/master/spank_demo.c) by 85 | [Yong Qin](https://github.com/yqin). Its goal is simply to demonstrate all the 86 | different SPANK functions and the context in which they're called. The plugin 87 | does nothing else than logging function calls. 88 | 89 | The demo script is installed `/etc/slurm/lua.d/disabled/`, so it won't be 90 | automatically executed. To enable it, one could simply move it to 91 | `/etc/slurm/lua.d/` 92 | 93 | 94 | > Note that both the Slurm SPANK Lua plugin (`lua.so`) and the Lua scripts will 95 | need to be present on both the submission host (the node where `srun`/`sbatch` 96 | commands are executed) and the execution host(s) (the compute nodes). 97 | 98 | 99 | ## Writing your own Lua scripts 100 | 101 | For more info about how the Slurm SPANK Lua plugin works, and how to write your 102 | own Lua scripts, see the man page (`man 8 spank-lua`). 103 | 104 | Other useful information sources: 105 | 106 | * Slurm SPANK documentation: https://slurm.schedmd.com/spank.html 107 | * SPANK functions and variables are defined in 108 | [spank.h](https://github.com/SchedMD/slurm/blob/master/slurm/spank.h) 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /lib/list.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * $Id$ 3 | ***************************************************************************** 4 | * $LSDId: list.c 3709 2006-11-29 00:51:22Z dun $ 5 | ***************************************************************************** 6 | * Copyright (C) 2001-2002 The Regents of the University of California. 7 | * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). 8 | * Written by Chris Dunlap . 9 | * 10 | * This file is from LSD-Tools, the LLNL Software Development Toolbox. 11 | * 12 | * LSD-Tools is free software; you can redistribute it and/or modify it under 13 | * the terms of the GNU General Public License as published by the Free 14 | * Software Foundation; either version 2 of the License, or (at your option) 15 | * any later version. 16 | * 17 | * LSD-Tools is distributed in the hope that it will be useful, but WITHOUT 18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | * more details. 21 | * 22 | * You should have received a copy of the GNU General Public License along 23 | * with LSD-Tools; if not, write to the Free Software Foundation, Inc., 24 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 25 | ***************************************************************************** 26 | * Refer to "list.h" for documentation on public functions. 27 | *****************************************************************************/ 28 | 29 | 30 | #ifdef HAVE_CONFIG_H 31 | # include "config.h" 32 | #endif /* HAVE_CONFIG_H */ 33 | 34 | #ifdef WITH_PTHREADS 35 | # include 36 | #endif /* WITH_PTHREADS */ 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include "list.h" 43 | 44 | 45 | /********************* 46 | * lsd_fatal_error * 47 | *********************/ 48 | 49 | #ifdef WITH_LSD_FATAL_ERROR_FUNC 50 | # undef lsd_fatal_error 51 | extern void lsd_fatal_error(char *file, int line, char *mesg); 52 | #else /* !WITH_LSD_FATAL_ERROR_FUNC */ 53 | # ifndef lsd_fatal_error 54 | # include 55 | # include 56 | # include 57 | # define lsd_fatal_error(file, line, mesg) \ 58 | do { \ 59 | fprintf(stderr, "ERROR: [%s:%d] %s: %s\n", \ 60 | file, line, mesg, strerror(errno)); \ 61 | } while (0) 62 | # endif /* !lsd_fatal_error */ 63 | #endif /* !WITH_LSD_FATAL_ERROR_FUNC */ 64 | 65 | 66 | /********************* 67 | * lsd_nomem_error * 68 | *********************/ 69 | 70 | #ifdef WITH_LSD_NOMEM_ERROR_FUNC 71 | # undef lsd_nomem_error 72 | extern void * lsd_nomem_error(char *file, int line, char *mesg); 73 | #else /* !WITH_LSD_NOMEM_ERROR_FUNC */ 74 | # ifndef lsd_nomem_error 75 | # define lsd_nomem_error(file, line, mesg) (NULL) 76 | # endif /* !lsd_nomem_error */ 77 | #endif /* !WITH_LSD_NOMEM_ERROR_FUNC */ 78 | 79 | 80 | /*************** 81 | * Constants * 82 | ***************/ 83 | 84 | #define LIST_ALLOC 32 85 | #define LIST_MAGIC 0xDEADBEEF 86 | 87 | 88 | /**************** 89 | * Data Types * 90 | ****************/ 91 | 92 | struct listNode { 93 | void *data; /* node's data */ 94 | struct listNode *next; /* next node in list */ 95 | }; 96 | 97 | struct listIterator { 98 | struct list *list; /* the list being iterated */ 99 | struct listNode *pos; /* the next node to be iterated */ 100 | struct listNode **prev; /* addr of 'next' ptr to prv It node */ 101 | struct listIterator *iNext; /* iterator chain for list_destroy() */ 102 | #ifndef NDEBUG 103 | unsigned int magic; /* sentinel for asserting validity */ 104 | #endif /* !NDEBUG */ 105 | }; 106 | 107 | struct list { 108 | struct listNode *head; /* head of the list */ 109 | struct listNode **tail; /* addr of last node's 'next' ptr */ 110 | struct listIterator *iNext; /* iterator chain for list_destroy() */ 111 | ListDelF fDel; /* function to delete node data */ 112 | int count; /* number of nodes in list */ 113 | #ifdef WITH_PTHREADS 114 | pthread_mutex_t mutex; /* mutex to protect access to list */ 115 | #endif /* WITH_PTHREADS */ 116 | #ifndef NDEBUG 117 | unsigned int magic; /* sentinel for asserting validity */ 118 | #endif /* !NDEBUG */ 119 | }; 120 | 121 | typedef struct listNode * ListNode; 122 | 123 | 124 | /**************** 125 | * Prototypes * 126 | ****************/ 127 | 128 | static void * list_node_create (List l, ListNode *pp, void *x); 129 | static void * list_node_destroy (List l, ListNode *pp); 130 | static List list_alloc (void); 131 | static void list_free (List l); 132 | static ListNode list_node_alloc (void); 133 | static void list_node_free (ListNode p); 134 | static ListIterator list_iterator_alloc (void); 135 | static void list_iterator_free (ListIterator i); 136 | static void * list_alloc_aux (int size, void *pfreelist); 137 | static void list_free_aux (void *x, void *pfreelist); 138 | 139 | 140 | /*************** 141 | * Variables * 142 | ***************/ 143 | 144 | static List list_free_lists = NULL; 145 | static ListNode list_free_nodes = NULL; 146 | static ListIterator list_free_iterators = NULL; 147 | 148 | #ifdef WITH_PTHREADS 149 | static pthread_mutex_t list_free_lock = PTHREAD_MUTEX_INITIALIZER; 150 | #endif /* WITH_PTHREADS */ 151 | 152 | 153 | /************ 154 | * Macros * 155 | ************/ 156 | 157 | #ifdef WITH_PTHREADS 158 | 159 | # define list_mutex_init(mutex) \ 160 | do { \ 161 | int e = pthread_mutex_init(mutex, NULL); \ 162 | if (e != 0) { \ 163 | errno = e; \ 164 | lsd_fatal_error(__FILE__, __LINE__, "list mutex init"); \ 165 | abort(); \ 166 | } \ 167 | } while (0) 168 | 169 | # define list_mutex_lock(mutex) \ 170 | do { \ 171 | int e = pthread_mutex_lock(mutex); \ 172 | if (e != 0) { \ 173 | errno = e; \ 174 | lsd_fatal_error(__FILE__, __LINE__, "list mutex lock"); \ 175 | abort(); \ 176 | } \ 177 | } while (0) 178 | 179 | # define list_mutex_unlock(mutex) \ 180 | do { \ 181 | int e = pthread_mutex_unlock(mutex); \ 182 | if (e != 0) { \ 183 | errno = e; \ 184 | lsd_fatal_error(__FILE__, __LINE__, "list mutex unlock"); \ 185 | abort(); \ 186 | } \ 187 | } while (0) 188 | 189 | # define list_mutex_destroy(mutex) \ 190 | do { \ 191 | int e = pthread_mutex_destroy(mutex); \ 192 | if (e != 0) { \ 193 | errno = e; \ 194 | lsd_fatal_error(__FILE__, __LINE__, "list mutex destroy"); \ 195 | abort(); \ 196 | } \ 197 | } while (0) 198 | 199 | # ifndef NDEBUG 200 | static int list_mutex_is_locked (pthread_mutex_t *mutex); 201 | # endif /* !NDEBUG */ 202 | 203 | #else /* !WITH_PTHREADS */ 204 | 205 | # define list_mutex_init(mutex) 206 | # define list_mutex_lock(mutex) 207 | # define list_mutex_unlock(mutex) 208 | # define list_mutex_destroy(mutex) 209 | # define list_mutex_is_locked(mutex) (1) 210 | 211 | #endif /* !WITH_PTHREADS */ 212 | 213 | 214 | /*************** 215 | * Functions * 216 | ***************/ 217 | 218 | List 219 | list_create (ListDelF f) 220 | { 221 | List l; 222 | 223 | if (!(l = list_alloc())) 224 | return(lsd_nomem_error(__FILE__, __LINE__, "list create")); 225 | l->head = NULL; 226 | l->tail = &l->head; 227 | l->iNext = NULL; 228 | l->fDel = f; 229 | l->count = 0; 230 | list_mutex_init(&l->mutex); 231 | assert(l->magic = LIST_MAGIC); /* set magic via assert abuse */ 232 | return(l); 233 | } 234 | 235 | 236 | void 237 | list_destroy (List l) 238 | { 239 | ListIterator i, iTmp; 240 | ListNode p, pTmp; 241 | 242 | assert(l != NULL); 243 | list_mutex_lock(&l->mutex); 244 | assert(l->magic == LIST_MAGIC); 245 | i = l->iNext; 246 | while (i) { 247 | assert(i->magic == LIST_MAGIC); 248 | iTmp = i->iNext; 249 | assert(i->magic = ~LIST_MAGIC); /* clear magic via assert abuse */ 250 | list_iterator_free(i); 251 | i = iTmp; 252 | } 253 | p = l->head; 254 | while (p) { 255 | pTmp = p->next; 256 | if (p->data && l->fDel) 257 | l->fDel(p->data); 258 | list_node_free(p); 259 | p = pTmp; 260 | } 261 | assert(l->magic = ~LIST_MAGIC); /* clear magic via assert abuse */ 262 | list_mutex_unlock(&l->mutex); 263 | list_mutex_destroy(&l->mutex); 264 | list_free(l); 265 | return; 266 | } 267 | 268 | 269 | int 270 | list_is_empty (List l) 271 | { 272 | int n; 273 | 274 | assert(l != NULL); 275 | list_mutex_lock(&l->mutex); 276 | assert(l->magic == LIST_MAGIC); 277 | n = l->count; 278 | list_mutex_unlock(&l->mutex); 279 | return(n == 0); 280 | } 281 | 282 | 283 | int 284 | list_count (List l) 285 | { 286 | int n; 287 | 288 | assert(l != NULL); 289 | list_mutex_lock(&l->mutex); 290 | assert(l->magic == LIST_MAGIC); 291 | n = l->count; 292 | list_mutex_unlock(&l->mutex); 293 | return(n); 294 | } 295 | 296 | 297 | void * 298 | list_append (List l, void *x) 299 | { 300 | void *v; 301 | 302 | assert(l != NULL); 303 | assert(x != NULL); 304 | list_mutex_lock(&l->mutex); 305 | assert(l->magic == LIST_MAGIC); 306 | v = list_node_create(l, l->tail, x); 307 | list_mutex_unlock(&l->mutex); 308 | return(v); 309 | } 310 | 311 | 312 | void * 313 | list_prepend (List l, void *x) 314 | { 315 | void *v; 316 | 317 | assert(l != NULL); 318 | assert(x != NULL); 319 | list_mutex_lock(&l->mutex); 320 | assert(l->magic == LIST_MAGIC); 321 | v = list_node_create(l, &l->head, x); 322 | list_mutex_unlock(&l->mutex); 323 | return(v); 324 | } 325 | 326 | 327 | void * 328 | list_find_first (List l, ListFindF f, void *key) 329 | { 330 | ListNode p; 331 | void *v = NULL; 332 | 333 | assert(l != NULL); 334 | assert(f != NULL); 335 | list_mutex_lock(&l->mutex); 336 | assert(l->magic == LIST_MAGIC); 337 | for (p=l->head; p; p=p->next) { 338 | if (f(p->data, key)) { 339 | v = p->data; 340 | break; 341 | } 342 | } 343 | list_mutex_unlock(&l->mutex); 344 | return(v); 345 | } 346 | 347 | 348 | int 349 | list_delete_all (List l, ListFindF f, void *key) 350 | { 351 | ListNode *pp; 352 | void *v; 353 | int n = 0; 354 | 355 | assert(l != NULL); 356 | assert(f != NULL); 357 | list_mutex_lock(&l->mutex); 358 | assert(l->magic == LIST_MAGIC); 359 | pp = &l->head; 360 | while (*pp) { 361 | if (f((*pp)->data, key)) { 362 | if ((v = list_node_destroy(l, pp))) { 363 | if (l->fDel) 364 | l->fDel(v); 365 | n++; 366 | } 367 | } 368 | else { 369 | pp = &(*pp)->next; 370 | } 371 | } 372 | list_mutex_unlock(&l->mutex); 373 | return(n); 374 | } 375 | 376 | 377 | int 378 | list_for_each (List l, ListForF f, void *arg) 379 | { 380 | ListNode p; 381 | int n = 0; 382 | 383 | assert(l != NULL); 384 | assert(f != NULL); 385 | list_mutex_lock(&l->mutex); 386 | assert(l->magic == LIST_MAGIC); 387 | for (p=l->head; p; p=p->next) { 388 | n++; 389 | if (f(p->data, arg) < 0) { 390 | n = -n; 391 | break; 392 | } 393 | } 394 | list_mutex_unlock(&l->mutex); 395 | return(n); 396 | } 397 | 398 | 399 | void 400 | list_sort (List l, ListCmpF f) 401 | { 402 | /* Note: Time complexity O(n^2). 403 | */ 404 | ListNode *pp, *ppPrev, *ppPos, pTmp; 405 | ListIterator i; 406 | 407 | assert(l != NULL); 408 | assert(f != NULL); 409 | list_mutex_lock(&l->mutex); 410 | assert(l->magic == LIST_MAGIC); 411 | if (l->count > 1) { 412 | ppPrev = &l->head; 413 | pp = &(*ppPrev)->next; 414 | while (*pp) { 415 | if (f((*pp)->data, (*ppPrev)->data) < 0) { 416 | ppPos = &l->head; 417 | while (f((*pp)->data, (*ppPos)->data) >= 0) 418 | ppPos = &(*ppPos)->next; 419 | pTmp = (*pp)->next; 420 | (*pp)->next = *ppPos; 421 | *ppPos = *pp; 422 | *pp = pTmp; 423 | if (ppPrev == ppPos) 424 | ppPrev = &(*ppPrev)->next; 425 | } 426 | else { 427 | ppPrev = pp; 428 | pp = &(*pp)->next; 429 | } 430 | } 431 | l->tail = pp; 432 | 433 | for (i=l->iNext; i; i=i->iNext) { 434 | assert(i->magic == LIST_MAGIC); 435 | i->pos = i->list->head; 436 | i->prev = &i->list->head; 437 | } 438 | } 439 | list_mutex_unlock(&l->mutex); 440 | return; 441 | } 442 | 443 | 444 | void * 445 | list_push (List l, void *x) 446 | { 447 | void *v; 448 | 449 | assert(l != NULL); 450 | assert(x != NULL); 451 | list_mutex_lock(&l->mutex); 452 | assert(l->magic == LIST_MAGIC); 453 | v = list_node_create(l, &l->head, x); 454 | list_mutex_unlock(&l->mutex); 455 | return(v); 456 | } 457 | 458 | 459 | void * 460 | list_pop (List l) 461 | { 462 | void *v; 463 | 464 | assert(l != NULL); 465 | list_mutex_lock(&l->mutex); 466 | assert(l->magic == LIST_MAGIC); 467 | v = list_node_destroy(l, &l->head); 468 | list_mutex_unlock(&l->mutex); 469 | return(v); 470 | } 471 | 472 | 473 | void * 474 | list_peek (List l) 475 | { 476 | void *v; 477 | 478 | assert(l != NULL); 479 | list_mutex_lock(&l->mutex); 480 | assert(l->magic == LIST_MAGIC); 481 | v = (l->head) ? l->head->data : NULL; 482 | list_mutex_unlock(&l->mutex); 483 | return(v); 484 | } 485 | 486 | 487 | void * 488 | list_enqueue (List l, void *x) 489 | { 490 | void *v; 491 | 492 | assert(l != NULL); 493 | assert(x != NULL); 494 | list_mutex_lock(&l->mutex); 495 | assert(l->magic == LIST_MAGIC); 496 | v = list_node_create(l, l->tail, x); 497 | list_mutex_unlock(&l->mutex); 498 | return(v); 499 | } 500 | 501 | 502 | void * 503 | list_dequeue (List l) 504 | { 505 | void *v; 506 | 507 | assert(l != NULL); 508 | list_mutex_lock(&l->mutex); 509 | assert(l->magic == LIST_MAGIC); 510 | v = list_node_destroy(l, &l->head); 511 | list_mutex_unlock(&l->mutex); 512 | return(v); 513 | } 514 | 515 | 516 | ListIterator 517 | list_iterator_create (List l) 518 | { 519 | ListIterator i; 520 | 521 | assert(l != NULL); 522 | if (!(i = list_iterator_alloc())) 523 | return(lsd_nomem_error(__FILE__, __LINE__, "list iterator create")); 524 | i->list = l; 525 | list_mutex_lock(&l->mutex); 526 | assert(l->magic == LIST_MAGIC); 527 | i->pos = l->head; 528 | i->prev = &l->head; 529 | i->iNext = l->iNext; 530 | l->iNext = i; 531 | assert(i->magic = LIST_MAGIC); /* set magic via assert abuse */ 532 | list_mutex_unlock(&l->mutex); 533 | return(i); 534 | } 535 | 536 | 537 | void 538 | list_iterator_reset (ListIterator i) 539 | { 540 | assert(i != NULL); 541 | assert(i->magic == LIST_MAGIC); 542 | list_mutex_lock(&i->list->mutex); 543 | assert(i->list->magic == LIST_MAGIC); 544 | i->pos = i->list->head; 545 | i->prev = &i->list->head; 546 | list_mutex_unlock(&i->list->mutex); 547 | return; 548 | } 549 | 550 | 551 | void 552 | list_iterator_destroy (ListIterator i) 553 | { 554 | ListIterator *pi; 555 | 556 | assert(i != NULL); 557 | assert(i->magic == LIST_MAGIC); 558 | list_mutex_lock(&i->list->mutex); 559 | assert(i->list->magic == LIST_MAGIC); 560 | for (pi=&i->list->iNext; *pi; pi=&(*pi)->iNext) { 561 | assert((*pi)->magic == LIST_MAGIC); 562 | if (*pi == i) { 563 | *pi = (*pi)->iNext; 564 | break; 565 | } 566 | } 567 | list_mutex_unlock(&i->list->mutex); 568 | assert(i->magic = ~LIST_MAGIC); /* clear magic via assert abuse */ 569 | list_iterator_free(i); 570 | return; 571 | } 572 | 573 | 574 | void * 575 | list_next (ListIterator i) 576 | { 577 | ListNode p; 578 | 579 | assert(i != NULL); 580 | assert(i->magic == LIST_MAGIC); 581 | list_mutex_lock(&i->list->mutex); 582 | assert(i->list->magic == LIST_MAGIC); 583 | if ((p = i->pos)) 584 | i->pos = p->next; 585 | if (*i->prev != p) 586 | i->prev = &(*i->prev)->next; 587 | list_mutex_unlock(&i->list->mutex); 588 | return(p ? p->data : NULL); 589 | } 590 | 591 | 592 | void * 593 | list_insert (ListIterator i, void *x) 594 | { 595 | void *v; 596 | 597 | assert(i != NULL); 598 | assert(x != NULL); 599 | assert(i->magic == LIST_MAGIC); 600 | list_mutex_lock(&i->list->mutex); 601 | assert(i->list->magic == LIST_MAGIC); 602 | v = list_node_create(i->list, i->prev, x); 603 | list_mutex_unlock(&i->list->mutex); 604 | return(v); 605 | } 606 | 607 | 608 | void * 609 | list_find (ListIterator i, ListFindF f, void *key) 610 | { 611 | void *v; 612 | 613 | assert(i != NULL); 614 | assert(f != NULL); 615 | assert(i->magic == LIST_MAGIC); 616 | while ((v=list_next(i)) && !f(v,key)) {;} 617 | return(v); 618 | } 619 | 620 | 621 | void * 622 | list_remove (ListIterator i) 623 | { 624 | void *v = NULL; 625 | 626 | assert(i != NULL); 627 | assert(i->magic == LIST_MAGIC); 628 | list_mutex_lock(&i->list->mutex); 629 | assert(i->list->magic == LIST_MAGIC); 630 | if (*i->prev != i->pos) 631 | v = list_node_destroy(i->list, i->prev); 632 | list_mutex_unlock(&i->list->mutex); 633 | return(v); 634 | } 635 | 636 | 637 | int 638 | list_delete (ListIterator i) 639 | { 640 | void *v; 641 | 642 | assert(i != NULL); 643 | assert(i->magic == LIST_MAGIC); 644 | if ((v = list_remove(i))) { 645 | if (i->list->fDel) 646 | i->list->fDel(v); 647 | return(1); 648 | } 649 | return(0); 650 | } 651 | 652 | 653 | static void * 654 | list_node_create (List l, ListNode *pp, void *x) 655 | { 656 | /* Inserts data pointed to by [x] into list [l] after [pp], 657 | * the address of the previous node's "next" ptr. 658 | * Returns a ptr to data [x], or NULL if insertion fails. 659 | * This routine assumes the list is already locked upon entry. 660 | */ 661 | ListNode p; 662 | ListIterator i; 663 | 664 | assert(l != NULL); 665 | assert(l->magic == LIST_MAGIC); 666 | assert(list_mutex_is_locked(&l->mutex)); 667 | assert(pp != NULL); 668 | assert(x != NULL); 669 | if (!(p = list_node_alloc())) 670 | return(lsd_nomem_error(__FILE__, __LINE__, "list node create")); 671 | p->data = x; 672 | if (!(p->next = *pp)) 673 | l->tail = &p->next; 674 | *pp = p; 675 | l->count++; 676 | for (i=l->iNext; i; i=i->iNext) { 677 | assert(i->magic == LIST_MAGIC); 678 | if (i->prev == pp) 679 | i->prev = &p->next; 680 | else if (i->pos == p->next) 681 | i->pos = p; 682 | assert((i->pos == *i->prev) || (i->pos == (*i->prev)->next)); 683 | } 684 | return(x); 685 | } 686 | 687 | 688 | static void * 689 | list_node_destroy (List l, ListNode *pp) 690 | { 691 | /* Removes the node pointed to by [*pp] from from list [l], 692 | * where [pp] is the address of the previous node's "next" ptr. 693 | * Returns the data ptr associated with list item being removed, 694 | * or NULL if [*pp] points to the NULL element. 695 | * This routine assumes the list is already locked upon entry. 696 | */ 697 | void *v; 698 | ListNode p; 699 | ListIterator i; 700 | 701 | assert(l != NULL); 702 | assert(l->magic == LIST_MAGIC); 703 | assert(list_mutex_is_locked(&l->mutex)); 704 | assert(pp != NULL); 705 | if (!(p = *pp)) 706 | return(NULL); 707 | v = p->data; 708 | if (!(*pp = p->next)) 709 | l->tail = pp; 710 | l->count--; 711 | for (i=l->iNext; i; i=i->iNext) { 712 | assert(i->magic == LIST_MAGIC); 713 | if (i->pos == p) 714 | i->pos = p->next, i->prev = pp; 715 | else if (i->prev == &p->next) 716 | i->prev = pp; 717 | assert((i->pos == *i->prev) || (i->pos == (*i->prev)->next)); 718 | } 719 | list_node_free(p); 720 | return(v); 721 | } 722 | 723 | 724 | static List 725 | list_alloc (void) 726 | { 727 | return(list_alloc_aux(sizeof(struct list), &list_free_lists)); 728 | } 729 | 730 | 731 | static void 732 | list_free (List l) 733 | { 734 | list_free_aux(l, &list_free_lists); 735 | return; 736 | } 737 | 738 | 739 | static ListNode 740 | list_node_alloc (void) 741 | { 742 | return(list_alloc_aux(sizeof(struct listNode), &list_free_nodes)); 743 | } 744 | 745 | 746 | static void 747 | list_node_free (ListNode p) 748 | { 749 | list_free_aux(p, &list_free_nodes); 750 | return; 751 | } 752 | 753 | 754 | static ListIterator 755 | list_iterator_alloc (void) 756 | { 757 | return(list_alloc_aux(sizeof(struct listIterator), &list_free_iterators)); 758 | } 759 | 760 | 761 | static void 762 | list_iterator_free (ListIterator i) 763 | { 764 | list_free_aux(i, &list_free_iterators); 765 | return; 766 | } 767 | 768 | 769 | static void * 770 | list_alloc_aux (int size, void *pfreelist) 771 | { 772 | /* Allocates an object of [size] bytes from the freelist [*pfreelist]. 773 | * Memory is added to the freelist in chunks of size LIST_ALLOC. 774 | * Returns a ptr to the object, or NULL if the memory request fails. 775 | */ 776 | void **px; 777 | void **pfree = pfreelist; 778 | void **plast; 779 | 780 | assert(sizeof(char) == 1); 781 | assert(size >= sizeof(void *)); 782 | assert(pfreelist != NULL); 783 | assert(LIST_ALLOC > 0); 784 | list_mutex_lock(&list_free_lock); 785 | if (!*pfree) { 786 | if ((*pfree = malloc(LIST_ALLOC * size))) { 787 | px = *pfree; 788 | plast = (void **) ((char *) *pfree + ((LIST_ALLOC - 1) * size)); 789 | while (px < plast) 790 | *px = (char *) px + size, px = *px; 791 | *plast = NULL; 792 | } 793 | } 794 | if ((px = *pfree)) 795 | *pfree = *px; 796 | else 797 | errno = ENOMEM; 798 | list_mutex_unlock(&list_free_lock); 799 | return(px); 800 | } 801 | 802 | 803 | static void 804 | list_free_aux (void *x, void *pfreelist) 805 | { 806 | /* Frees the object [x], returning it to the freelist [*pfreelist]. 807 | */ 808 | void **px = x; 809 | void **pfree = pfreelist; 810 | 811 | assert(x != NULL); 812 | assert(pfreelist != NULL); 813 | list_mutex_lock(&list_free_lock); 814 | *px = *pfree; 815 | *pfree = px; 816 | list_mutex_unlock(&list_free_lock); 817 | return; 818 | } 819 | 820 | 821 | #ifndef NDEBUG 822 | #ifdef WITH_PTHREADS 823 | static int 824 | list_mutex_is_locked (pthread_mutex_t *mutex) 825 | { 826 | /* Returns true if the mutex is locked; o/w, returns false. 827 | */ 828 | int rc; 829 | 830 | assert(mutex != NULL); 831 | rc = pthread_mutex_trylock(mutex); 832 | return(rc == EBUSY ? 1 : 0); 833 | } 834 | #endif /* WITH_PTHREADS */ 835 | #endif /* !NDEBUG */ 836 | -------------------------------------------------------------------------------- /lib/list.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * $Id: list.h,v 1.14 2002/12/11 19:00:36 dun Exp $ 3 | ***************************************************************************** 4 | * Copyright (C) 2001-2002 The Regents of the University of California. 5 | * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). 6 | * Written by Chris Dunlap . 7 | * 8 | * This file is from LSD-Tools, the LLNL Software Development Toolbox. 9 | * 10 | * LSD-Tools is free software; you can redistribute it and/or modify it under 11 | * the terms of the GNU General Public License as published by the Free 12 | * Software Foundation; either version 2 of the License, or (at your option) 13 | * any later version. 14 | * 15 | * LSD-Tools is distributed in the hope that it will be useful, but WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 18 | * more details. 19 | * 20 | * You should have received a copy of the GNU General Public License along 21 | * with LSD-Tools; if not, write to the Free Software Foundation, Inc., 22 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 23 | *****************************************************************************/ 24 | 25 | 26 | #ifndef LSD_LIST_H 27 | #define LSD_LIST_H 28 | 29 | 30 | /*********** 31 | * Notes * 32 | ***********/ 33 | /* 34 | * If NDEBUG is not defined, internal debug code will be enabled. This is 35 | * intended for development use only and production code should define NDEBUG. 36 | * 37 | * If WITH_LSD_FATAL_ERROR_FUNC is defined, the linker will expect to 38 | * find an external lsd_fatal_error(file,line,mesg) function. By default, 39 | * lsd_fatal_error(file,line,mesg) is a macro definition that outputs an 40 | * error message to stderr. This macro may be redefined to invoke another 41 | * routine instead. 42 | * 43 | * If WITH_LSD_NOMEM_ERROR_FUNC is defined, the linker will expect to 44 | * find an external lsd_nomem_error(file,line,mesg) function. By default, 45 | * lsd_nomem_error(file,line,mesg) is a macro definition that returns NULL. 46 | * This macro may be redefined to invoke another routine instead. 47 | * 48 | * If WITH_PTHREADS is defined, these routines will be thread-safe. 49 | */ 50 | 51 | 52 | /**************** 53 | * Data Types * 54 | ****************/ 55 | 56 | typedef struct list * List; 57 | /* 58 | * List opaque data type. 59 | */ 60 | 61 | typedef struct listIterator * ListIterator; 62 | /* 63 | * List Iterator opaque data type. 64 | */ 65 | 66 | typedef void (*ListDelF) (void *x); 67 | /* 68 | * Function prototype to deallocate data stored in a list. 69 | * This function is responsible for freeing all memory associated 70 | * with an item, including all subordinate items (if applicable). 71 | */ 72 | 73 | typedef int (*ListCmpF) (void *x, void *y); 74 | /* 75 | * Function prototype for comparing two items in a list. 76 | * Returns less-than-zero if (xy). 78 | */ 79 | 80 | typedef int (*ListFindF) (void *x, void *key); 81 | /* 82 | * Function prototype for matching items in a list. 83 | * Returns non-zero if (x==key); o/w returns zero. 84 | */ 85 | 86 | typedef int (*ListForF) (void *x, void *arg); 87 | /* 88 | * Function prototype for operating on each item in a list. 89 | * Returns less-than-zero on error. 90 | */ 91 | 92 | 93 | /******************************* 94 | * General-Purpose Functions * 95 | *******************************/ 96 | 97 | List list_create (ListDelF f); 98 | /* 99 | * Creates and returns a new empty list, or lsd_nomem_error() on failure. 100 | * The deletion function [f] is used to deallocate memory used by items 101 | * in the list; if this is NULL, memory associated with these items 102 | * will not be freed when the list is destroyed. 103 | * Note: Abandoning a list without calling list_destroy() will result 104 | * in a memory leak. 105 | */ 106 | 107 | void list_destroy (List l); 108 | /* 109 | * Destroys list [l], freeing memory used for list iterators and the 110 | * list itself; if a deletion function was specified when the list 111 | * was created, it will be called for each item in the list. 112 | */ 113 | 114 | int list_is_empty (List l); 115 | /* 116 | * Returns non-zero if list [l] is empty; o/w returns zero. 117 | */ 118 | 119 | int list_count (List l); 120 | /* 121 | * Returns the number of items in list [l]. 122 | */ 123 | 124 | 125 | /*************************** 126 | * List Access Functions * 127 | ***************************/ 128 | 129 | void * list_append (List l, void *x); 130 | /* 131 | * Inserts data [x] at the end of list [l]. 132 | * Returns the data's ptr, or lsd_nomem_error() if insertion failed. 133 | */ 134 | 135 | void * list_prepend (List l, void *x); 136 | /* 137 | * Inserts data [x] at the beginning of list [l]. 138 | * Returns the data's ptr, or lsd_nomem_error() if insertion failed. 139 | */ 140 | 141 | void * list_find_first (List l, ListFindF f, void *key); 142 | /* 143 | * Traverses list [l] using [f] to match each item with [key]. 144 | * Returns a ptr to the first item for which the function [f] 145 | * returns non-zero, or NULL if no such item is found. 146 | * Note: This function differs from list_find() in that it does not require 147 | * a list iterator; it should only be used when all list items are known 148 | * to be unique (according to the function [f]). 149 | */ 150 | 151 | int list_delete_all (List l, ListFindF f, void *key); 152 | /* 153 | * Traverses list [l] using [f] to match each item with [key]. 154 | * Removes all items from the list for which the function [f] returns 155 | * non-zero; if a deletion function was specified when the list was 156 | * created, it will be called to deallocate each item being removed. 157 | * Returns a count of the number of items removed from the list. 158 | */ 159 | 160 | int list_for_each (List l, ListForF f, void *arg); 161 | /* 162 | * For each item in list [l], invokes the function [f] with [arg]. 163 | * Returns a count of the number of items on which [f] was invoked. 164 | * If [f] returns <0 for a given item, the iteration is aborted and the 165 | * function returns the negative of that item's position in the list. 166 | */ 167 | 168 | void list_sort (List l, ListCmpF f); 169 | /* 170 | * Sorts list [l] into ascending order according to the function [f]. 171 | * Note: Sorting a list resets all iterators associated with the list. 172 | * Note: The sort algorithm is stable. 173 | */ 174 | 175 | 176 | /**************************** 177 | * Stack Access Functions * 178 | ****************************/ 179 | 180 | void * list_push (List l, void *x); 181 | /* 182 | * Pushes data [x] onto the top of stack [l]. 183 | * Returns the data's ptr, or lsd_nomem_error() if insertion failed. 184 | */ 185 | 186 | void * list_pop (List l); 187 | /* 188 | * Pops the data item at the top of the stack [l]. 189 | * Returns the data's ptr, or NULL if the stack is empty. 190 | */ 191 | 192 | void * list_peek (List l); 193 | /* 194 | * Peeks at the data item at the top of the stack (or head of the queue) [l]. 195 | * Returns the data's ptr, or NULL if the stack (or queue) is empty. 196 | * Note: The item is not removed from the list. 197 | */ 198 | 199 | 200 | /**************************** 201 | * Queue Access Functions * 202 | ****************************/ 203 | 204 | void * list_enqueue (List l, void *x); 205 | /* 206 | * Enqueues data [x] at the tail of queue [l]. 207 | * Returns the data's ptr, or lsd_nomem_error() if insertion failed. 208 | */ 209 | 210 | void * list_dequeue (List l); 211 | /* 212 | * Dequeues the data item at the head of the queue [l]. 213 | * Returns the data's ptr, or NULL if the queue is empty. 214 | */ 215 | 216 | 217 | /***************************** 218 | * List Iterator Functions * 219 | *****************************/ 220 | 221 | ListIterator list_iterator_create (List l); 222 | /* 223 | * Creates and returns a list iterator for non-destructively traversing 224 | * list [l], or lsd_nomem_error() on failure. 225 | */ 226 | 227 | void list_iterator_reset (ListIterator i); 228 | /* 229 | * Resets the list iterator [i] to start traversal at the beginning 230 | * of the list. 231 | */ 232 | 233 | void list_iterator_destroy (ListIterator i); 234 | /* 235 | * Destroys the list iterator [i]; list iterators not explicitly destroyed 236 | * in this manner will be destroyed when the list is deallocated via 237 | * list_destroy(). 238 | */ 239 | 240 | void * list_next (ListIterator i); 241 | /* 242 | * Returns a ptr to the next item's data, 243 | * or NULL once the end of the list is reached. 244 | * Example: i=list_iterator_create(i); while ((x=list_next(i))) {...} 245 | */ 246 | 247 | void * list_insert (ListIterator i, void *x); 248 | /* 249 | * Inserts data [x] immediately before the last item returned via list 250 | * iterator [i]; once the list iterator reaches the end of the list, 251 | * insertion is made at the list's end. 252 | * Returns the data's ptr, or lsd_nomem_error() if insertion failed. 253 | */ 254 | 255 | void * list_find (ListIterator i, ListFindF f, void *key); 256 | /* 257 | * Traverses the list from the point of the list iterator [i] 258 | * using [f] to match each item with [key]. 259 | * Returns a ptr to the next item for which the function [f] 260 | * returns non-zero, or NULL once the end of the list is reached. 261 | * Example: i=list_iterator_reset(i); while ((x=list_find(i,f,k))) {...} 262 | */ 263 | 264 | void * list_remove (ListIterator i); 265 | /* 266 | * Removes from the list the last item returned via list iterator [i] 267 | * and returns the data's ptr. 268 | * Note: The client is responsible for freeing the returned data. 269 | */ 270 | 271 | int list_delete (ListIterator i); 272 | /* 273 | * Removes from the list the last item returned via list iterator [i]; 274 | * if a deletion function was specified when the list was created, 275 | * it will be called to deallocate the item being removed. 276 | * Returns a count of the number of items removed from the list 277 | * (ie, '1' if the item was removed, and '0' otherwise). 278 | */ 279 | 280 | 281 | #endif /* !LSD_LIST_H */ 282 | -------------------------------------------------------------------------------- /lua.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * Copyright (C) 2007-2008 Lawrence Livermore National Security, LLC. 4 | * Produced at Lawrence Livermore National Laboratory. 5 | * Written by Mark Grondona . 6 | * 7 | * UCRL-CODE-235358 8 | * 9 | * This file is part of chaos-spankings, a set of spank plugins for SLURM. 10 | * 11 | * This is free software; you can redistribute it and/or modify it 12 | * under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This is distributed in the hope that it will be useful, but WITHOUT 17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 18 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 19 | * for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program. If not, see . 23 | ****************************************************************************/ 24 | 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include /* need longjmp for lua_atpanic */ 31 | #include /* basename(3) */ 32 | 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "lib/list.h" 42 | 43 | SPANK_PLUGIN (lua, 1) 44 | 45 | /* Name of spank_t lightuserdata reference in 46 | * spank table passed to lua spank functions. 47 | */ 48 | #define SPANK_REFNAME "spank" 49 | 50 | /* 51 | * This module keeps a list of options provided by the lua 52 | * script so that it can easily map the option val (s_opt.val) 53 | * back to the lua fucntion and val (l_function and l_val) 54 | * 55 | * This is only necessary because SLURM's spank option callbacks 56 | * do not provide any state or context besides a plugin-specific 57 | * option value. 58 | * 59 | */ 60 | struct lua_script_option { 61 | struct lua_script * script; 62 | int l_val; 63 | char * l_function; 64 | struct spank_option s_opt; 65 | }; 66 | 67 | /* 68 | * Global script_option_list declaration: 69 | */ 70 | static List script_option_list = NULL; 71 | 72 | /* 73 | * Structure describing an individual lua script 74 | * and a list of such scripts 75 | */ 76 | struct lua_script { 77 | char *path; 78 | lua_State *L; 79 | int fail_on_error; 80 | }; 81 | List lua_script_list = NULL; 82 | 83 | /* 84 | * Tell lua_atpanic where to longjmp on exceptions: 85 | */ 86 | static jmp_buf panicbuf; 87 | static int spank_atpanic (lua_State *L) { longjmp (panicbuf, 0); } 88 | 89 | /* 90 | * Lua scripts pass string versions of spank_item_t to get/set_time. 91 | * This table maps the name to item and vice versa. 92 | */ 93 | #define SPANK_ITEM(x) { (x), #x } 94 | #define SPANK_ITEM_END { 0, NULL } 95 | static struct s_item_name { 96 | spank_item_t item; 97 | const char *name; 98 | } spank_item_table [] = { 99 | SPANK_ITEM(S_JOB_UID), 100 | SPANK_ITEM(S_JOB_GID), 101 | SPANK_ITEM(S_JOB_ID), 102 | SPANK_ITEM(S_JOB_STEPID), 103 | SPANK_ITEM(S_JOB_NNODES), 104 | SPANK_ITEM(S_JOB_NODEID), 105 | SPANK_ITEM(S_JOB_LOCAL_TASK_COUNT), 106 | SPANK_ITEM(S_JOB_TOTAL_TASK_COUNT), 107 | SPANK_ITEM(S_JOB_NCPUS), 108 | SPANK_ITEM(S_JOB_ARGV), 109 | SPANK_ITEM(S_JOB_ENV), 110 | SPANK_ITEM(S_TASK_ID), 111 | SPANK_ITEM(S_TASK_GLOBAL_ID), 112 | SPANK_ITEM(S_TASK_EXIT_STATUS), 113 | SPANK_ITEM(S_TASK_PID), 114 | SPANK_ITEM(S_JOB_PID_TO_GLOBAL_ID), 115 | SPANK_ITEM(S_JOB_PID_TO_LOCAL_ID), 116 | SPANK_ITEM(S_JOB_LOCAL_TO_GLOBAL_ID), 117 | SPANK_ITEM(S_JOB_GLOBAL_TO_LOCAL_ID), 118 | SPANK_ITEM(S_JOB_SUPPLEMENTARY_GIDS), 119 | SPANK_ITEM(S_SLURM_VERSION), 120 | SPANK_ITEM(S_SLURM_VERSION_MAJOR), 121 | SPANK_ITEM(S_SLURM_VERSION_MINOR), 122 | SPANK_ITEM(S_SLURM_VERSION_MICRO), 123 | SPANK_ITEM(S_STEP_CPUS_PER_TASK), 124 | SPANK_ITEM(S_JOB_ALLOC_CORES), 125 | SPANK_ITEM(S_JOB_ALLOC_MEM), 126 | SPANK_ITEM(S_STEP_ALLOC_CORES), 127 | SPANK_ITEM(S_STEP_ALLOC_MEM), 128 | SPANK_ITEM_END 129 | }; 130 | 131 | 132 | /***************************************************************************** 133 | * 134 | * Lua script interface functions: 135 | * 136 | ****************************************************************************/ 137 | 138 | static int l_spank_error_msg (lua_State *L, const char *msg) 139 | { 140 | lua_pushnil (L); 141 | lua_pushstring (L, msg); 142 | return (2); 143 | } 144 | 145 | static int l_spank_error (lua_State *L, spank_err_t e) 146 | { 147 | return l_spank_error_msg (L, spank_strerror (e)); 148 | } 149 | 150 | 151 | /* 152 | * Get lua function return code. 153 | * Functions must return -1 for failure, anything else 154 | * indicates success (Including no return code, which 155 | * would be nil at top of stack created by lua_pcall()). 156 | * 157 | */ 158 | static int lua_script_rc (lua_State *L) 159 | { 160 | int rc = 0; 161 | if (lua_isnumber (L, -1)) 162 | rc = lua_tonumber (L, -1); 163 | 164 | /* Clean up the stack */ 165 | lua_pop (L, 0); 166 | return (rc); 167 | } 168 | 169 | /* 170 | * Return spank_t handle as lightuserdata from the spank table 171 | * at index [index] on the Lua stack. 172 | */ 173 | static spank_t lua_getspank (lua_State *L, int index) 174 | { 175 | spank_t sp; 176 | 177 | if (!lua_istable (L, 1)) 178 | luaL_error (L, "Invalid argument expected table got %s", 179 | luaL_typename (L, 1)); 180 | 181 | lua_getfield (L, index, SPANK_REFNAME); 182 | if (!lua_islightuserdata (L, -1)) 183 | return (NULL); 184 | 185 | sp = lua_touserdata (L, -1); 186 | 187 | /* Pop lightuserdata off the stack */ 188 | lua_pop (L, 1); 189 | return (sp); 190 | } 191 | 192 | /* 193 | * Convert a spank item by name to a spank_item_t enum (as integer). 194 | */ 195 | static int name_to_item (const char *name) 196 | { 197 | struct s_item_name *s = spank_item_table; 198 | 199 | while (s->name != NULL) { 200 | if (strcmp (s->name, name) == 0) { 201 | return (s->item); 202 | } 203 | s++; 204 | } 205 | return (-1); 206 | } 207 | 208 | /* 209 | * Generic function to push a spank item with a numeric (int) representation 210 | * on to the lua stack. 211 | */ 212 | static int l_spank_get_item_int (lua_State *L, spank_t sp, spank_item_t item) 213 | { 214 | spank_err_t err; 215 | int val; 216 | 217 | err = spank_get_item (sp, item, &val); 218 | if (err != ESPANK_SUCCESS) 219 | return l_spank_error (L, err); 220 | 221 | lua_pushnumber (L, val); 222 | return (1); 223 | } 224 | 225 | /* 226 | * Generic function to push a spank item with a numeric (long) representation 227 | * on to the lua stack. 228 | */ 229 | static int l_spank_get_item_long (lua_State *L, spank_t sp, spank_item_t item) 230 | { 231 | spank_err_t err; 232 | long val; 233 | 234 | err = spank_get_item (sp, item, &val); 235 | if (err != ESPANK_SUCCESS) 236 | return l_spank_error (L, err); 237 | 238 | lua_pushnumber (L, val); 239 | return (1); 240 | } 241 | 242 | 243 | /* 244 | * Generic function to push a spank item with a string representation 245 | * on to the lua stack. 246 | */ 247 | static int 248 | l_spank_get_item_string (lua_State *L, spank_t sp, spank_item_t item) 249 | { 250 | spank_err_t err; 251 | const char *s; 252 | 253 | err = spank_get_item (sp, item, &s); 254 | if (err != ESPANK_SUCCESS) 255 | return l_spank_error (L, err); 256 | lua_pushstring (L, s); 257 | return (1); 258 | 259 | } 260 | 261 | /* 262 | * Return S_JOB_ARGV as an array on the lua stack 263 | */ 264 | static int l_spank_get_item_argv (lua_State *L, spank_t sp) 265 | { 266 | spank_err_t err; 267 | const char **av; 268 | int i, ac; 269 | 270 | err = spank_get_item (sp, S_JOB_ARGV, &ac, &av); 271 | if (err != ESPANK_SUCCESS) 272 | return l_spank_error (L, err); 273 | 274 | lua_newtable (L); 275 | for (i = 0; i < ac; i++) { 276 | lua_pushstring (L, av[i]); 277 | lua_rawseti (L, -2, i+1); 278 | } 279 | return (1); 280 | } 281 | 282 | /* 283 | * Set a single environment entry as an item in the table 284 | * at the top of the stack such that t[NAME] = VALUE. 285 | */ 286 | static void set_env_table_entry (lua_State *L, int i, const char *entry) 287 | { 288 | const char *val = strchr (entry, '='); 289 | if (val == NULL) { 290 | lua_pushstring (L, entry); 291 | lua_pushstring (L, ""); 292 | } 293 | else { 294 | lua_pushlstring (L, entry, val - entry); 295 | lua_pushstring (L, val+1); 296 | } 297 | lua_settable (L, i); 298 | } 299 | 300 | /* 301 | * Copy S_JOB_ENV to a table on the Lua stack. 302 | */ 303 | static int l_spank_get_item_env (lua_State *L, spank_t sp) 304 | { 305 | spank_err_t err; 306 | const char **env; 307 | const char **p; 308 | int t; 309 | 310 | err = spank_get_item (sp, S_JOB_ENV, &env); 311 | if (err != ESPANK_SUCCESS) 312 | return l_spank_error (L, err); 313 | 314 | lua_newtable (L); 315 | t = lua_gettop (L); 316 | 317 | for (p = env; *p != NULL; p++) 318 | set_env_table_entry (L, t, *p); 319 | 320 | return (1); 321 | } 322 | 323 | /* 324 | * Copy GID list as array on the Lua stack. 325 | */ 326 | static int l_spank_get_item_gids (lua_State *L, spank_t sp) 327 | { 328 | spank_err_t err; 329 | gid_t *gids; 330 | int i, ngids; 331 | 332 | err = spank_get_item (sp, S_JOB_SUPPLEMENTARY_GIDS, &gids, &ngids); 333 | if (err != ESPANK_SUCCESS) 334 | return l_spank_error (L, err); 335 | 336 | lua_newtable (L); 337 | for (i = 0; i < ngids; i++) { 338 | lua_pushnumber (L, gids[i]); 339 | lua_rawseti (L, -2, i+1); 340 | } 341 | return (1); 342 | } 343 | 344 | static int 345 | l_spank_id_query (lua_State *L, spank_t sp, spank_item_t item) 346 | { 347 | spank_err_t err; 348 | long rv, id; 349 | 350 | id = luaL_checknumber (L, -1); 351 | lua_pop (L, 1); 352 | 353 | err = spank_get_item (sp, item, id, &rv); 354 | if (err != ESPANK_SUCCESS) 355 | return l_spank_error (L, err); 356 | 357 | lua_pushnumber (L, rv); 358 | return (1); 359 | } 360 | 361 | static int l_spank_get_exit_status (lua_State *L, spank_t sp) 362 | { 363 | spank_err_t err; 364 | int status; 365 | 366 | err = spank_get_item (sp, S_TASK_EXIT_STATUS, &status); 367 | if (err != ESPANK_SUCCESS) 368 | return l_spank_error (L, err); 369 | 370 | lua_pushnumber (L, status); 371 | /* 372 | * Now push WEXITSTATUS or nil if !WIFEXITED and 373 | * WTERMSIG, WCOREDUMP or nil if !WIFSIGNALED 374 | */ 375 | if (WIFEXITED (status)) 376 | lua_pushnumber (L, WEXITSTATUS (status)); 377 | else 378 | lua_pushnil (L); 379 | 380 | if (WIFSIGNALED (status)) { 381 | lua_pushnumber (L, WTERMSIG (status)); 382 | lua_pushnumber (L, WCOREDUMP (status)); 383 | } 384 | else { 385 | lua_pushnil (L); 386 | lua_pushnil (L); 387 | } 388 | /* Returns 4 values: status, exitcode, termsig, coredumped */ 389 | return (4); 390 | } 391 | 392 | static int l_spank_get_item (lua_State *L) 393 | { 394 | spank_t sp; 395 | int item; 396 | 397 | sp = lua_getspank (L, 1); 398 | item = name_to_item (lua_tostring (L, 2)); 399 | if (item < 0) 400 | return luaL_error (L,"Invalid spank item %s", lua_tostring (L, 2)); 401 | 402 | switch (item) { 403 | case S_JOB_UID: 404 | case S_JOB_GID: 405 | case S_JOB_ID: 406 | case S_JOB_STEPID: 407 | case S_JOB_NNODES: 408 | case S_JOB_NODEID: 409 | case S_JOB_LOCAL_TASK_COUNT: 410 | case S_JOB_TOTAL_TASK_COUNT: 411 | case S_JOB_NCPUS: 412 | case S_TASK_ID: 413 | case S_TASK_GLOBAL_ID: 414 | case S_TASK_PID: 415 | case S_STEP_CPUS_PER_TASK: 416 | return l_spank_get_item_int (L, sp, item); 417 | case S_JOB_ALLOC_MEM: 418 | case S_STEP_ALLOC_MEM: 419 | return l_spank_get_item_long (L, sp, item); 420 | case S_JOB_ALLOC_CORES: 421 | case S_STEP_ALLOC_CORES: 422 | case S_SLURM_VERSION: 423 | case S_SLURM_VERSION_MAJOR: 424 | case S_SLURM_VERSION_MINOR: 425 | case S_SLURM_VERSION_MICRO: 426 | return l_spank_get_item_string (L, sp, item); 427 | case S_JOB_ARGV: 428 | return l_spank_get_item_argv (L, sp); 429 | case S_JOB_ENV: 430 | return l_spank_get_item_env (L, sp); 431 | case S_JOB_SUPPLEMENTARY_GIDS: 432 | return l_spank_get_item_gids (L, sp); 433 | case S_JOB_PID_TO_GLOBAL_ID: 434 | case S_JOB_PID_TO_LOCAL_ID: 435 | case S_JOB_LOCAL_TO_GLOBAL_ID: 436 | case S_JOB_GLOBAL_TO_LOCAL_ID: 437 | return l_spank_id_query (L, sp, item); 438 | case S_TASK_EXIT_STATUS: 439 | return l_spank_get_exit_status (L, sp); 440 | } 441 | 442 | return luaL_error (L, "Unhandled spank item: %s", lua_tostring (L, 2)); 443 | } 444 | 445 | typedef spank_err_t (*setenv_f) (spank_t, const char *, const char *, int); 446 | typedef spank_err_t (*getenv_f) (spank_t, const char *, char *, int); 447 | typedef spank_err_t (*unsetenv_f) (spank_t, const char *); 448 | 449 | 450 | static int l_do_getenv (lua_State *L, getenv_f fn) 451 | { 452 | spank_err_t err; 453 | spank_t sp; 454 | const char *var; 455 | char buf[4096]; 456 | 457 | sp = lua_getspank (L, 1); 458 | var = luaL_checkstring (L, 2); 459 | 460 | err = (*fn) (sp, var, buf, sizeof (buf)); 461 | lua_pop (L, 0); 462 | 463 | if (err != ESPANK_SUCCESS) 464 | return l_spank_error (L, err); 465 | 466 | lua_pushstring (L, buf); 467 | return (1); 468 | } 469 | 470 | static int l_do_setenv (lua_State *L, setenv_f fn) 471 | { 472 | spank_t sp; 473 | const char *var; 474 | const char *val; 475 | int overwrite; 476 | int err; 477 | 478 | sp = lua_getspank (L, 1); 479 | var = luaL_checkstring (L, 2); 480 | val = luaL_checkstring (L, 3); 481 | overwrite = lua_tonumber (L, 4); /* 0 by default */ 482 | 483 | err = (*fn) (sp, var, val, overwrite); 484 | lua_pop (L, 0); 485 | 486 | if (err != ESPANK_SUCCESS) 487 | return l_spank_error (L, err); 488 | 489 | lua_pushboolean (L, 1); 490 | return (1); 491 | } 492 | 493 | static int l_do_unsetenv (lua_State *L, unsetenv_f fn) 494 | { 495 | int err = (*fn) (lua_getspank (L, 1), luaL_checkstring (L, 2)); 496 | lua_pop (L, 2); 497 | 498 | if (err != ESPANK_SUCCESS) 499 | return l_spank_error (L, err); 500 | 501 | lua_pushboolean (L, 1); 502 | return (1); 503 | } 504 | 505 | static int l_spank_setenv (lua_State *L) 506 | { 507 | return l_do_setenv (L, spank_setenv); 508 | } 509 | 510 | static int l_spank_getenv (lua_State *L) 511 | { 512 | return l_do_getenv (L, spank_getenv); 513 | } 514 | 515 | static int l_spank_unsetenv (lua_State *L) 516 | { 517 | return l_do_unsetenv (L, spank_unsetenv); 518 | } 519 | 520 | static void * sym_lookup (const char *name) 521 | { 522 | static void *h = NULL; 523 | if (h == NULL) 524 | h = dlopen (NULL, 0); 525 | return dlsym (h, name); 526 | } 527 | 528 | static int l_spank_job_control_setenv (lua_State *L) 529 | { 530 | setenv_f f = sym_lookup ("spank_job_control_setenv"); 531 | 532 | if (f == NULL) 533 | return l_spank_error_msg (L, 534 | "spank_job_control_setenv not implemented in this version"); 535 | 536 | return l_do_setenv (L, f); 537 | } 538 | static int l_spank_job_control_getenv (lua_State *L) 539 | { 540 | getenv_f f = sym_lookup ("spank_job_control_getenv"); 541 | 542 | if (f == NULL) 543 | return l_spank_error_msg (L, 544 | "spank_job_control_getenv not implemented in this version"); 545 | 546 | return l_do_getenv (L, f); 547 | } 548 | static int l_spank_job_control_unsetenv (lua_State *L) 549 | { 550 | unsetenv_f f = sym_lookup ("spank_job_control_unsetenv"); 551 | 552 | if (f == NULL) 553 | return l_spank_error_msg (L, 554 | "spank_job_control_unsetenv not implemented in this version"); 555 | 556 | return l_do_unsetenv (L, f); 557 | } 558 | 559 | static int s_opt_find (struct lua_script_option *o, int *pval) 560 | { 561 | return (o->s_opt.val == *pval); 562 | } 563 | 564 | static int lua_spank_option_callback (int val, const char *optarg, int remote) 565 | { 566 | lua_State *L; 567 | struct lua_script_option *o; 568 | 569 | o = list_find_first ( 570 | script_option_list, 571 | (ListFindF) s_opt_find, 572 | &val); 573 | 574 | if (o == NULL) 575 | return (-1); 576 | 577 | if (o->l_function == NULL) 578 | return (0); 579 | 580 | L = o->script->L; 581 | 582 | lua_getglobal (L, o->l_function); 583 | lua_pushnumber (L, o->l_val); 584 | lua_pushstring (L, optarg); 585 | lua_pushboolean (L, remote); 586 | 587 | slurm_debug ("spank/lua: %s: callback %s for option %s optarg=%s", 588 | o->script->path, o->l_function, o->s_opt.name, 589 | optarg ? optarg : "nil"); 590 | 591 | if (lua_pcall (L, 3, 1, 0) != 0) { 592 | slurm_error ("Failed to call lua callback function %s: %s", 593 | o->l_function, lua_tostring (L, -1)); 594 | lua_pop (L, 1); 595 | return (-1); 596 | } 597 | 598 | return lua_script_rc (L); 599 | } 600 | 601 | static struct lua_script_option * 602 | lua_script_option_create (struct lua_script *script, int i) 603 | { 604 | struct lua_script_option *o = malloc (sizeof (*o)); 605 | struct lua_State *L = script->L; 606 | 607 | if (o == NULL) 608 | luaL_error (L, "Unable to create lua script option: Out of memory"); 609 | 610 | o->s_opt.cb = (spank_opt_cb_f) lua_spank_option_callback; 611 | o->script = script; 612 | 613 | /* 614 | * Option name: 615 | */ 616 | lua_getfield (L, i, "name"); 617 | if (lua_isnil (L, -1)) 618 | luaL_error (L, "Required field \"name\" missing from spank option table"); 619 | o->s_opt.name = strdup (lua_tostring (L, -1)); 620 | lua_pop (L, 1); 621 | 622 | /* 623 | * Option arginfo (optional): 624 | */ 625 | lua_getfield (L, i, "arginfo"); 626 | if (lua_isnil (L, -1)) 627 | o->s_opt.arginfo = NULL; 628 | else 629 | o->s_opt.arginfo = strdup (lua_tostring (L, -1)); 630 | lua_pop (L, 1); 631 | 632 | /* 633 | * Option usage (required): 634 | */ 635 | lua_getfield (L, i, "usage"); 636 | if (lua_isnil (L, -1)) 637 | luaL_error (L, "Required field \"usage\" missing from spank option table"); 638 | o->s_opt.usage = strdup (lua_tostring (L, -1)); 639 | lua_pop (L, 1); 640 | 641 | /* 642 | * Option has_arg (optional): 643 | */ 644 | lua_getfield (L, i, "has_arg"); 645 | if (lua_isnil (L, -1)) 646 | o->s_opt.has_arg = 0; 647 | else 648 | o->s_opt.has_arg = lua_tonumber (L, -1); 649 | lua_pop (L, 1); 650 | 651 | /* 652 | * Option val (optional): 653 | */ 654 | lua_getfield (L, i, "val"); 655 | if (lua_isnil (L, -1)) 656 | o->l_val = 0; 657 | else 658 | o->l_val = lua_tonumber (L, -1); 659 | lua_pop (L, 1); 660 | 661 | /* 662 | * Option callback function name: 663 | */ 664 | lua_getfield (L, i, "cb"); 665 | if (!lua_isnil (L, -1)) { 666 | o->l_function = strdup (lua_tostring (L, -1)); 667 | 668 | /* 669 | * Check for existence of callback function 670 | */ 671 | lua_getglobal (L, o->l_function); 672 | if (!lua_isfunction (L, -1)) 673 | luaL_error (L, "Unable to find spank option cb function %s", 674 | o->l_function); 675 | lua_pop (L, 1); 676 | } 677 | else 678 | o->l_function = NULL; 679 | lua_pop (L, 1); 680 | return (o); 681 | } 682 | 683 | static void lua_script_option_destroy (struct lua_script_option *o) 684 | { 685 | if (o->s_opt.name) 686 | free (o->s_opt.name); 687 | if (o->s_opt.arginfo) 688 | free (o->s_opt.arginfo); 689 | if (o->s_opt.usage) 690 | free (o->s_opt.usage); 691 | if (o->l_function) 692 | free (o->l_function); 693 | free (o); 694 | } 695 | 696 | 697 | static int lua_script_option_register (struct lua_script *script, 698 | spank_t sp, int index) 699 | { 700 | spank_err_t err; 701 | struct lua_State *L = script->L; 702 | struct lua_script_option *opt = lua_script_option_create (script, index); 703 | 704 | 705 | if (!script_option_list) 706 | script_option_list = list_create ((ListDelF)lua_script_option_destroy); 707 | 708 | opt->s_opt.val = list_count (script_option_list); 709 | list_push (script_option_list, opt); 710 | 711 | err = spank_option_register (sp, &opt->s_opt); 712 | if (err != ESPANK_SUCCESS) 713 | return l_spank_error (L, err); 714 | 715 | lua_pushboolean (L, 1); 716 | return (1); 717 | } 718 | 719 | static int find_script_by_state (struct lua_script *s, lua_State *L) 720 | { 721 | return (s->L == L); 722 | } 723 | 724 | static int l_spank_option_register (lua_State *L) 725 | { 726 | int rc; 727 | spank_t sp; 728 | struct lua_script *script; 729 | 730 | sp = lua_getspank (L, 1); 731 | if (!lua_istable (L, 2)) 732 | return luaL_error (L, 733 | "Expected table argument to spank_option_register"); 734 | 735 | script = list_find_first (lua_script_list, 736 | (ListFindF) find_script_by_state, 737 | (void *) L); 738 | 739 | rc = lua_script_option_register (script, sp, 2); 740 | lua_pop (L, 2); 741 | 742 | return (rc); 743 | } 744 | 745 | static int l_spank_option_getopt (lua_State *L) 746 | { 747 | spank_t sp; 748 | spank_err_t err; 749 | char *optarg; 750 | struct lua_script *script; 751 | struct lua_script_option *opt; 752 | 753 | sp = lua_getspank (L, 1); 754 | if (!lua_istable (L, 2)) 755 | return luaL_error (L, 756 | "Expected table argument to spank_option_getopt"); 757 | 758 | script = list_find_first (lua_script_list, 759 | (ListFindF) find_script_by_state, 760 | (void *) L); 761 | if (!script) 762 | return luaL_error (L, 763 | "Unable to determine script from lua state!"); 764 | 765 | opt = lua_script_option_create (script, 2); 766 | err = spank_option_getopt (sp, &opt->s_opt, &optarg); 767 | lua_script_option_destroy (opt); 768 | 769 | lua_pop (L, 2); 770 | if (err != ESPANK_SUCCESS) { 771 | if (err == ESPANK_ERROR) 772 | return l_spank_error_msg (L, "Option unused"); 773 | else 774 | return l_spank_error (L, err); 775 | } 776 | 777 | if (optarg == NULL) 778 | lua_pushboolean (L, 1); 779 | else 780 | lua_pushstring (L, optarg); 781 | 782 | return (1); 783 | } 784 | 785 | static int l_spank_remote (lua_State *L) 786 | { 787 | spank_t sp; 788 | 789 | int val; 790 | 791 | sp = lua_getspank (L, 1); 792 | switch (spank_remote (sp)) { 793 | case 0: 794 | lua_pushstring (L, "no"); 795 | break; 796 | case 1: 797 | lua_pushstring (L, "yes"); 798 | break; 799 | default: 800 | lua_pushstring (L, "error"); 801 | break; 802 | } 803 | 804 | return (1); 805 | } 806 | 807 | 808 | static int l_spank_context (lua_State *L) 809 | { 810 | switch (spank_context ()) { 811 | case S_CTX_LOCAL: 812 | lua_pushstring (L, "local"); 813 | break; 814 | case S_CTX_REMOTE: 815 | lua_pushstring (L, "remote"); 816 | break; 817 | case S_CTX_ALLOCATOR: 818 | lua_pushstring (L, "allocator"); 819 | break; 820 | #if HAVE_S_CTX_SLURMD 821 | case S_CTX_SLURMD: 822 | lua_pushstring (L, "slurmd"); 823 | break; 824 | #endif 825 | #if HAVE_S_CTX_JOB_SCRIPT 826 | case S_CTX_JOB_SCRIPT: 827 | lua_pushstring (L, "job_script"); 828 | break; 829 | #endif 830 | case S_CTX_ERROR: 831 | default: 832 | lua_pushstring (L, "error"); 833 | break; 834 | } 835 | 836 | return (1); 837 | } 838 | 839 | /***************************************************************************** 840 | * SPANK table 841 | ****************************************************************************/ 842 | 843 | static const struct luaL_Reg spank_functions [] = { 844 | { "remote", l_spank_remote }, 845 | { "register_option", l_spank_option_register }, 846 | { "get_item", l_spank_get_item }, 847 | { "getopt", l_spank_option_getopt }, 848 | { "getenv", l_spank_getenv }, 849 | { "setenv", l_spank_setenv }, 850 | { "unsetenv", l_spank_unsetenv }, 851 | { "job_control_setenv", l_spank_job_control_setenv }, 852 | { "job_control_getenv", l_spank_job_control_getenv }, 853 | { "job_control_unsetenv", l_spank_job_control_unsetenv }, 854 | { NULL, NULL }, 855 | }; 856 | 857 | 858 | static int lua_spank_table_create (lua_State *L, spank_t sp, int ac, char **av) 859 | { 860 | const char *str; 861 | int i; 862 | 863 | lua_newtable (L); 864 | #if LUA_VERSION_NUM >= 502 865 | luaL_setfuncs(L, spank_functions, 0); 866 | #else 867 | luaL_register (L, NULL, spank_functions); 868 | #endif 869 | 870 | /* Register spank handle as light userdata inside spank table: 871 | */ 872 | lua_pushlightuserdata (L, sp); 873 | lua_setfield (L, -2, SPANK_REFNAME); 874 | 875 | l_spank_context (L); 876 | lua_setfield (L, -2, "context"); 877 | 878 | if (spank_get_item (sp, S_SLURM_VERSION, &str) == ESPANK_SUCCESS) { 879 | lua_pushstring (L, str); 880 | lua_setfield (L, -2, "slurm_version"); 881 | } 882 | 883 | lua_newtable (L); 884 | for (i = 1; i < ac; i++) { 885 | lua_pushstring (L, av[i]); 886 | lua_rawseti (L, -2, i); 887 | } 888 | lua_setfield (L, -2, "args"); 889 | 890 | return (0); 891 | } 892 | 893 | static int lua_spank_call (struct lua_script *s, spank_t sp, const char *fn, 894 | int ac, char **av) 895 | { 896 | struct lua_State *L = s->L; 897 | /* 898 | * Missing functions are not an error 899 | */ 900 | lua_getglobal (L, fn); 901 | if (lua_isnil (L, -1)) { 902 | lua_pop (L, 1); 903 | return (0); 904 | } 905 | 906 | /* 907 | * Create spank object to pass to spank functions 908 | */ 909 | lua_spank_table_create (L, sp, ac, av); 910 | 911 | if (lua_pcall (L, 1, 1, 0)) { 912 | slurm_error ("spank/lua: %s: %s", fn, lua_tostring (L, -1)); 913 | return (s->fail_on_error ? -1 : 0); 914 | } 915 | 916 | return lua_script_rc (L); 917 | } 918 | 919 | /* 920 | * Convert '%' to '%%" on the msg string on top of the stack. 921 | */ 922 | static const char *l_string_sanitized (lua_State *L) 923 | { 924 | int err; 925 | const char fn[] = "local s = ... return string.gsub(s, '%%', '%%%%')"; 926 | 927 | err = luaL_loadstring (L, fn); 928 | if (err) { 929 | slurm_error ("spank/lua: loadstring (%s): %s", fn, lua_tostring(L, -1)); 930 | return (NULL); 931 | } 932 | 933 | /* 934 | * Move string to top of stack: 935 | */ 936 | lua_pushvalue (L, 2); 937 | lua_remove (L, 2); 938 | 939 | /* 940 | * Call gsub, throwing away 2nd return value (number of matches), 941 | * leaving modified string on top of stack: 942 | */ 943 | err = lua_pcall (L, 1, 1, 0); 944 | if (err) { 945 | slurm_error ("spank/lua: sanitize msg: %s", lua_tostring (L, -1)); 946 | return (NULL); 947 | } 948 | 949 | return (lua_tostring (L, -1)); 950 | } 951 | 952 | static int l_spank_log_msg (lua_State *L) 953 | { 954 | int level = luaL_checknumber (L, 1); 955 | const char *msg; 956 | 957 | msg = l_string_sanitized (L); 958 | if (!msg) 959 | return (0); 960 | 961 | if (level == -1) { 962 | slurm_error ("%s", msg); 963 | lua_pushnumber (L, -1); 964 | return (1); 965 | } 966 | 967 | if (level == 0) 968 | slurm_info ("%s", msg); 969 | else if (level == 1) 970 | slurm_verbose ("%s", msg); 971 | else if (level == 2) 972 | slurm_debug ("%s", msg); 973 | else if (level == 3) 974 | slurm_debug2 ("%s", msg); 975 | else if (level == 4) 976 | slurm_debug3 ("%s", msg); 977 | #if SLURM_VERSION_NUMBER > SLURM_VERSION_NUM(20, 2, 0) 978 | else if (level == 5) 979 | slurm_spank_log ("%s", msg); 980 | #endif 981 | return (0); 982 | } 983 | 984 | static int SPANK_table_create (lua_State *L) 985 | { 986 | lua_newtable (L); 987 | lua_pushcfunction (L, l_spank_log_msg); 988 | lua_setfield (L, -2, "_log_msg"); 989 | 990 | /* 991 | * Create more user-friendly lua versions of SLURM log functions 992 | * with lua. 993 | */ 994 | char log_levels[][16] = { "log_error", "log_info", "log_verbose", 995 | "log_debug", "log_debug2", "log_debug3", 996 | #if SLURM_VERSION_NUMBER > SLURM_VERSION_NUM(20, 2, 0) 997 | "log_user", 998 | #endif 999 | }; 1000 | size_t i; 1001 | for (i = 0; i < sizeof(log_levels) / sizeof(log_levels[0]); i++) { 1002 | char loadstr[64]; 1003 | #if LUA_VERSION_NUM >= 502 1004 | char loadstr_table[7] = "table."; 1005 | #else 1006 | char loadstr_table[7] = ""; 1007 | #endif 1008 | snprintf(loadstr, 64, "SPANK._log_msg (%d, string.format(%sunpack({...})))", i-1, loadstr_table); 1009 | luaL_loadstring (L, loadstr); 1010 | lua_setfield (L, -2, log_levels[i]); 1011 | } 1012 | 1013 | /* 1014 | * SPANK.SUCCESS and SPANK.FAILURE 1015 | */ 1016 | lua_pushnumber (L, -1); 1017 | lua_setfield (L, -2, "FAILURE"); 1018 | 1019 | lua_pushnumber (L, 0); 1020 | lua_setfield (L, -2, "SUCCESS"); 1021 | 1022 | lua_setglobal (L, "SPANK"); 1023 | return (0); 1024 | } 1025 | 1026 | int load_spank_options_table (struct lua_script *script, spank_t sp) 1027 | { 1028 | int t; 1029 | lua_State *L = script->L; 1030 | 1031 | lua_getglobal (L, "spank_options"); 1032 | if (lua_isnil (L, -1)) { 1033 | lua_pop (L, 1); 1034 | return (0); 1035 | } 1036 | 1037 | /* 1038 | * Iterate through spank_options table, which should 1039 | * be a table of spank_option entries 1040 | */ 1041 | t = lua_gettop (L); 1042 | lua_pushnil (L); /* push starting "key" on stack */ 1043 | while (lua_next (L, t) != 0) { 1044 | /* 1045 | * If lua_script_option_register() returns 2, then it has 1046 | * pushed 2 args on the stack and thus has failed. We 1047 | * don't need to return the failure message back to SLURM, 1048 | * that has been printed by lua, but we pop the stack and 1049 | * return < 0 so that SLURM can detect failure. 1050 | */ 1051 | if (lua_script_option_register (script, sp, -1) > 1) { 1052 | slurm_error ("lua_script_option_register: %s", lua_tostring(L, -1)); 1053 | lua_pop (L, -1); /* pop everything */ 1054 | return (-1); 1055 | } 1056 | 1057 | /* On success, lua_script_option_register pushes a boolean onto 1058 | * the stack, so we need to pop 2 items: the boolean and 1059 | * the spank_option table itself. This leaves the 'key' on 1060 | * top for lua_next(). 1061 | */ 1062 | lua_pop (L, 2); 1063 | } 1064 | lua_pop (L, 1); /* pop 'spank_options' table */ 1065 | 1066 | return (0); 1067 | } 1068 | 1069 | static lua_State *lua_script_newstate(const char *path) { 1070 | lua_State *L = luaL_newstate (); 1071 | luaL_openlibs (L); 1072 | 1073 | /* 1074 | * Create the global SPANK table 1075 | */ 1076 | SPANK_table_create (L); 1077 | 1078 | /* 1079 | * Set up handler for lua_atpanic() so lua doesn't exit() on us. 1080 | * This handles errors from outside of protected mode -- 1081 | * for example when this plugin is processing the global 1082 | * spank_options table. The spank_atpanic() function will 1083 | * return to the setjmp() point below (thus avoiding Lua's 1084 | * call to exit() from its own panic handler). This is basically 1085 | * here so that we can use luaL_error() everwhere without 1086 | * worrying about the context of the call. 1087 | */ 1088 | lua_atpanic (L, spank_atpanic); 1089 | if (setjmp (panicbuf)) { 1090 | slurm_error ("spank/lua: PANIC: %s: %s", 1091 | path, lua_tostring (L, -1)); 1092 | return NULL; 1093 | } 1094 | return L; 1095 | } 1096 | 1097 | static struct lua_script * lua_script_create (const char *path) 1098 | { 1099 | lua_State *L = lua_script_newstate(path); 1100 | if (!L) 1101 | return NULL; 1102 | 1103 | struct lua_script *script = malloc (sizeof (*script)); 1104 | 1105 | script->path = strdup (path); 1106 | script->L = L; 1107 | script->fail_on_error = 0; 1108 | 1109 | return script; 1110 | } 1111 | 1112 | static void lua_script_destroy (struct lua_script *s) 1113 | { 1114 | free (s->path); 1115 | lua_close(s->L); 1116 | /* Only call lua_close() on the main lua state */ 1117 | free (s); 1118 | } 1119 | 1120 | static int ef (const char *p, int eerrno) 1121 | { 1122 | slurm_error ("spank/lua: glob: %s: %s", p, strerror (eerrno)); 1123 | return (-1); 1124 | } 1125 | 1126 | List lua_script_list_create (const char *pattern) 1127 | { 1128 | glob_t gl; 1129 | size_t i; 1130 | List l = NULL; 1131 | 1132 | if (pattern == NULL) 1133 | return (NULL); 1134 | 1135 | int rc = glob (pattern, GLOB_ERR, ef, &gl); 1136 | switch (rc) { 1137 | case 0: 1138 | l = list_create ((ListDelF) lua_script_destroy); 1139 | for (i = 0; i < gl.gl_pathc; i++) { 1140 | struct lua_script * s; 1141 | s = lua_script_create (gl.gl_pathv[i]); 1142 | if (s == NULL) { 1143 | slurm_error ("lua_script_create failed for %s.", 1144 | gl.gl_pathv[i]); 1145 | continue; 1146 | } 1147 | list_push (l, s); 1148 | } 1149 | break; 1150 | case GLOB_NOMATCH: 1151 | break; 1152 | case GLOB_NOSPACE: 1153 | slurm_error ("spank/lua: glob(3): Out of memory"); 1154 | case GLOB_ABORTED: 1155 | slurm_verbose ("spank/lua: cannot read dir %s: %m", pattern); 1156 | break; 1157 | default: 1158 | slurm_error ("Unknown glob(3) return code = %d", rc); 1159 | break; 1160 | } 1161 | 1162 | globfree (&gl); 1163 | 1164 | return l; 1165 | } 1166 | 1167 | static int spank_lua_options_table_register (List script_list, spank_t sp) 1168 | { 1169 | struct lua_script *script; 1170 | ListIterator i = list_iterator_create (script_list); 1171 | 1172 | if (i == NULL) { 1173 | slurm_error ("spank/lua: spank_lua_opts_register: Out of memory"); 1174 | return (-1); 1175 | } 1176 | 1177 | while ((script = list_next (i))) { 1178 | /* 1179 | * Load any options exported via a global spank_options table 1180 | */ 1181 | if (load_spank_options_table (script, sp) < 0) { 1182 | slurm_error ("spank/lua: %s: load_spank_options: %s", 1183 | basename (script->path), lua_tostring (script->L, -1)); 1184 | if (script->fail_on_error) 1185 | return (-1); 1186 | list_remove(i); 1187 | lua_script_destroy (script); 1188 | continue; 1189 | } 1190 | } 1191 | list_iterator_destroy (i); 1192 | return (0); 1193 | } 1194 | 1195 | 1196 | struct spank_lua_options { 1197 | unsigned fail_on_error:1; 1198 | }; 1199 | 1200 | static int spank_lua_process_args (int *ac, char **argvp[], 1201 | struct spank_lua_options *opt) 1202 | { 1203 | /* 1204 | * Advance argv past any spank/lua options. The rest of the 1205 | * args are the script/glob and script arguments. 1206 | * 1207 | * For now, the only supported spank/lua arg is 'failonerror' 1208 | * so this must appear in argv[0] 1209 | */ 1210 | if (strcmp ((*argvp)[0], "failonerror") == 0) { 1211 | opt->fail_on_error = 1; 1212 | (*ac)--; 1213 | (*argvp)++; 1214 | } 1215 | return 0; 1216 | } 1217 | 1218 | static void print_lua_script_error (struct lua_script *script) 1219 | { 1220 | const char *s = basename (script->path); 1221 | const char *err = lua_tostring (script->L, -1); 1222 | if (script->fail_on_error) 1223 | slurm_error ("spank/lua: Fatal: %s", err); 1224 | else 1225 | slurm_info ("spank/lua: Disabling %s: %s", s, err); 1226 | } 1227 | 1228 | static int lua_script_valid_in_context (spank_t sp, struct lua_script *script) 1229 | { 1230 | int valid = 1; 1231 | 1232 | #if HAVE_S_CTX_SLURMD 1233 | if (spank_context() == S_CTX_SLURMD) { 1234 | lua_getglobal (script->L, "slurm_spank_slurmd_init"); 1235 | lua_getglobal (script->L, "slurm_spank_slurmd_exit"); 1236 | if (lua_isnil (script->L, -1) && lua_isnil (script->L, -2)) 1237 | valid = 0; 1238 | lua_pop (script->L, 2); 1239 | } 1240 | #endif 1241 | #if HAVE_S_CTX_JOB_SCRIPT 1242 | if (spank_context() == S_CTX_JOB_SCRIPT) { 1243 | lua_getglobal (script->L, "slurm_spank_job_prolog"); 1244 | lua_getglobal (script->L, "slurm_spank_job_epilog"); 1245 | if (lua_isnil (script->L, -1) && lua_isnil (script->L, -2)) 1246 | valid = 0; 1247 | lua_pop (script->L, 2); 1248 | } 1249 | #endif 1250 | 1251 | return (valid); 1252 | } 1253 | 1254 | 1255 | int spank_lua_init (spank_t sp, int ac, char *av[]) 1256 | { 1257 | struct spank_lua_options opt; 1258 | ListIterator i; 1259 | struct lua_script *script; 1260 | int rc = 0; 1261 | int j = 0; 1262 | void *lua_handle = NULL; 1263 | 1264 | char *const lua_libs[] = { 1265 | #if LUA_VERSION_NUM == 504 1266 | "liblua-5.4.so", 1267 | "liblua5.4.so", 1268 | "liblua5.4.so.0", 1269 | "liblua.so.5.4", 1270 | #elif LUA_VERSION_NUM == 503 1271 | "liblua-5.3.so", 1272 | "liblua5.3.so", 1273 | "liblua5.3.so.0", 1274 | "liblua.so.5.3", 1275 | #elif LUA_VERSION_NUM == 502 1276 | "liblua-5.2.so", 1277 | "liblua5.2.so", 1278 | "liblua5.2.so.0", 1279 | "liblua.so.5.2", 1280 | #else 1281 | "liblua-5.1.so", 1282 | "liblua5.1.so", 1283 | "liblua5.1.so.0", 1284 | "liblua.so.5.1", 1285 | #endif 1286 | "liblua.so", 1287 | NULL 1288 | }; 1289 | 1290 | if (ac == 0) { 1291 | slurm_error ("spank/lua: Requires at least 1 arg"); 1292 | return (-1); 1293 | } 1294 | /* 1295 | * Check for spank/lua options in argv 1296 | */ 1297 | spank_lua_process_args (&ac, &av, &opt); 1298 | 1299 | /* 1300 | * dlopen liblua to ensure that symbols from that lib are 1301 | * available globally (so lua doesn't fail to dlopen its 1302 | * DSOs 1303 | */ 1304 | while (lua_libs[j] && !(lua_handle = dlopen(lua_libs[j], RTLD_NOW | RTLD_GLOBAL))) 1305 | j++; 1306 | 1307 | if (!lua_handle) { 1308 | slurm_error ("spank/lua: Failed to find a 'lua.so' version"); 1309 | return (-1); 1310 | } 1311 | 1312 | lua_script_list = lua_script_list_create (av[0]); 1313 | if (lua_script_list == NULL) { 1314 | slurm_verbose ("spank/lua: No files found in %s", av[0]); 1315 | return (0); 1316 | } 1317 | 1318 | 1319 | i = list_iterator_create (lua_script_list); 1320 | while ((script = list_next (i))) { 1321 | script->fail_on_error = opt.fail_on_error; 1322 | 1323 | /* 1324 | * Load script (luaL_loadfile) and compile it (lua_pcall). 1325 | */ 1326 | if (luaL_loadfile (script->L, script->path) || 1327 | lua_pcall (script->L, 0, 0, 0)) { 1328 | print_lua_script_error (script); 1329 | if (opt.fail_on_error) 1330 | return (-1); 1331 | list_remove(i); 1332 | lua_script_destroy (script); 1333 | continue; 1334 | } 1335 | 1336 | /* 1337 | * Don't keep script loaded if the script doesn't have any 1338 | * callbacks in the current context. 1339 | */ 1340 | if (!lua_script_valid_in_context (sp, script)) { 1341 | slurm_debug ("%s: no callbacks in this context", 1342 | basename (script->path)); 1343 | list_remove (i); 1344 | lua_script_destroy (script); 1345 | } 1346 | } 1347 | list_iterator_destroy (i); 1348 | slurm_verbose ("spank/lua: Loaded %d plugins in this context", 1349 | list_count (lua_script_list)); 1350 | return rc; 1351 | } 1352 | 1353 | static int call_foreach (List l, spank_t sp, const char *name, 1354 | int ac, char *av[]) 1355 | { 1356 | int rc = 0; 1357 | struct lua_script *script; 1358 | struct spank_lua_options opt; 1359 | ListIterator i; 1360 | 1361 | if (l == NULL) 1362 | return (0); 1363 | 1364 | /* 1365 | * Advance argv past any spank/lua (non-script) options: 1366 | */ 1367 | spank_lua_process_args (&ac, &av, &opt); 1368 | 1369 | i = list_iterator_create (l); 1370 | while ((script = list_next (i))) { 1371 | if (lua_spank_call (script, sp, name, ac, av) < 0) 1372 | rc = -1; 1373 | } 1374 | 1375 | list_iterator_destroy (i); 1376 | return (rc); 1377 | } 1378 | 1379 | /***************************************************************************** 1380 | * 1381 | * SPANK interface: 1382 | * 1383 | ****************************************************************************/ 1384 | int slurm_spank_init (spank_t sp, int ac, char *av[]) 1385 | { 1386 | if (spank_lua_init (sp, ac, av) < 0) 1387 | return (-1); 1388 | 1389 | if (lua_script_list == NULL) 1390 | return (0); 1391 | /* 1392 | * Register options in global 'spank_options' table 1393 | */ 1394 | if (spank_lua_options_table_register (lua_script_list, sp) < 0) 1395 | return (-1); 1396 | return call_foreach (lua_script_list, sp, "slurm_spank_init", ac, av); 1397 | } 1398 | 1399 | int slurm_spank_slurmd_init (spank_t sp, int ac, char *av[]) 1400 | { 1401 | if (spank_lua_init (sp, ac, av) < 0) 1402 | return (-1); 1403 | return call_foreach (lua_script_list, sp, 1404 | "slurm_spank_slurmd_init", ac, av); 1405 | } 1406 | 1407 | int slurm_spank_job_prolog (spank_t sp, int ac, char *av[]) 1408 | { 1409 | if (spank_lua_init (sp, ac, av) < 0) 1410 | return (-1); 1411 | return call_foreach (lua_script_list, sp, 1412 | "slurm_spank_job_prolog", ac, av); 1413 | } 1414 | 1415 | int slurm_spank_init_post_opt (spank_t sp, int ac, char *av[]) 1416 | { 1417 | return call_foreach (lua_script_list, sp, 1418 | "slurm_spank_init_post_opt", ac, av); 1419 | } 1420 | 1421 | int slurm_spank_local_user_init (spank_t sp, int ac, char *av[]) 1422 | { 1423 | return call_foreach (lua_script_list, sp, 1424 | "slurm_spank_local_user_init", ac, av); 1425 | } 1426 | 1427 | int slurm_spank_user_init (spank_t sp, int ac, char *av[]) 1428 | { 1429 | return call_foreach (lua_script_list, sp, 1430 | "slurm_spank_user_init", ac, av); 1431 | } 1432 | 1433 | int slurm_spank_task_init_privileged (spank_t sp, int ac, char *av[]) 1434 | { 1435 | return call_foreach (lua_script_list, sp, 1436 | "slurm_spank_task_init_privileged", ac, av); 1437 | } 1438 | 1439 | int slurm_spank_task_init (spank_t sp, int ac, char *av[]) 1440 | { 1441 | return call_foreach (lua_script_list, sp, 1442 | "slurm_spank_task_init", ac, av); 1443 | } 1444 | 1445 | int slurm_spank_task_post_fork (spank_t sp, int ac, char *av[]) 1446 | { 1447 | return call_foreach (lua_script_list, sp, 1448 | "slurm_spank_task_post_fork", ac, av); 1449 | } 1450 | 1451 | int slurm_spank_task_exit (spank_t sp, int ac, char *av[]) 1452 | { 1453 | return call_foreach (lua_script_list, sp, "slurm_spank_task_exit", ac, av); 1454 | } 1455 | 1456 | int slurm_spank_job_epilog (spank_t sp, int ac, char *av[]) 1457 | { 1458 | if (spank_lua_init (sp, ac, av) < 0) 1459 | return (-1); 1460 | return call_foreach (lua_script_list, sp, 1461 | "slurm_spank_job_epilog", ac, av); 1462 | } 1463 | 1464 | int slurm_spank_exit (spank_t sp, int ac, char *av[]) 1465 | { 1466 | int rc = call_foreach (lua_script_list, sp, "slurm_spank_exit", ac, av); 1467 | 1468 | if (lua_script_list) 1469 | list_destroy (lua_script_list); 1470 | if (script_option_list) 1471 | list_destroy (script_option_list); 1472 | return (rc); 1473 | } 1474 | 1475 | int slurm_spank_slurmd_exit (spank_t sp, int ac, char *av[]) 1476 | { 1477 | int rc = call_foreach (lua_script_list, sp, 1478 | "slurm_spank_slurmd_exit", ac, av); 1479 | 1480 | if (lua_script_list) 1481 | list_destroy (lua_script_list); 1482 | if (script_option_list) 1483 | list_destroy (script_option_list); 1484 | return (rc); 1485 | } 1486 | 1487 | 1488 | /* 1489 | * vi: ts=4 sw=4 expandtab 1490 | */ 1491 | -------------------------------------------------------------------------------- /scripts/spank_demo.lua: -------------------------------------------------------------------------------- 1 | -- =========================================================================== 2 | -- SPANK plugin to demonstrate when and where and by whom the SPANK functions 3 | -- are called. 4 | -- =========================================================================== 5 | 6 | -- 7 | -- includes 8 | -- 9 | 10 | local posix = require("posix") 11 | 12 | 13 | -- 14 | -- constants 15 | -- 16 | 17 | myname = "spank_demo" 18 | 19 | 20 | -- 21 | -- functions 22 | -- 23 | 24 | function gethostname() 25 | local f = io.popen ("/bin/hostname") 26 | local hostname = f:read("*a") or "" 27 | f:close() 28 | hostname = string.gsub(hostname, "\n$", "") 29 | return hostname 30 | end 31 | 32 | function getuid() 33 | local f = io.popen ("/bin/id -un") 34 | local uid = f:read("*a") or "" 35 | f:close() 36 | uid = string.gsub(uid, "\n$", "") 37 | return uid 38 | end 39 | 40 | function getgid() 41 | local f = io.popen ("/bin/id -gn") 42 | local gid = f:read("*a") or "" 43 | f:close() 44 | gid = string.gsub(gid, "\n$", "") 45 | return gid 46 | end 47 | 48 | function getdevicecgroup() 49 | local f = io.popen ("grep devices /proc/$$/cgroup | cut -d: -f3") 50 | local d_cg = f:read("*a") or "" 51 | f:close() 52 | d_cg = string.gsub(d_cg, "\n$", "") 53 | return d_cg 54 | end 55 | 56 | function display_msg(spank, caller) 57 | local context = spank.context 58 | local hostname = gethostname() 59 | local uid = getuid() 60 | local gid = getgid() 61 | local device_cgroup = getdevicecgroup() 62 | 63 | SPANK.log_info("%s: ctx:%s host:%s caller:%s uid:%s gid:%s device_cgroup:%s" , myname, context, hostname, caller, uid, gid, device_cgroup) 64 | return 0 65 | end 66 | 67 | 68 | -- 69 | -- SPANK functions 70 | -- cf. https://slurm.schedmd.com/spank.html 71 | -- 72 | 73 | function slurm_spank_init (spank) 74 | display_msg(spank, "slurm_spank_init") 75 | return 0 76 | end 77 | 78 | function slurm_spank_slurmd_init (spank) 79 | display_msg(spank, "slurm_spank_slurmd_init") 80 | return 0 81 | end 82 | 83 | function slurm_spank_init_post_opt (spank) 84 | display_msg(spank, "slurm_spank_init_post_opt") 85 | return 0 86 | end 87 | 88 | function slurm_spank_local_user_init (spank) 89 | display_msg(spank, "slurm_spank_local_user_init") 90 | return 0 91 | end 92 | 93 | function slurm_spank_user_init (spank) 94 | display_msg(spank, "slurm_spank_user_init") 95 | return 0 96 | end 97 | 98 | function slurm_spank_task_init_privileged (spank) 99 | display_msg(spank, "slurm_spank_task_init_privileged") 100 | return 0 101 | end 102 | 103 | function slurm_spank_task_init (spank) 104 | display_msg(spank, "slurm_spank_task_init") 105 | return 0 106 | end 107 | 108 | function slurm_spank_task_post_fork (spank) 109 | display_msg(spank, "slurm_spank_task_post_fork") 110 | return 0 111 | end 112 | 113 | function slurm_spank_task_exit (spank) 114 | display_msg(spank, "slurm_spank_task_exit") 115 | return 0 116 | end 117 | 118 | function slurm_spank_exit (spank) 119 | display_msg(spank, "slurm_spank_exit") 120 | return 0 121 | end 122 | 123 | function slurm_spank_job_epilog (spank) 124 | display_msg(spank, "slurm_spank_job_epilog") 125 | return 0 126 | end 127 | 128 | function slurm_spank_slurmd_exit (spank) 129 | display_msg(spank, "slurm_spank_slurmd_exit") 130 | return 0 131 | end 132 | -------------------------------------------------------------------------------- /slurm-spank-lua.spec: -------------------------------------------------------------------------------- 1 | %global slurm_version %(rpm -q slurm-devel --qf "%{VERSION}" 2>/dev/null) 2 | %define _use_internal_dependency_generator 0 3 | %define __find_requires %{_builddir}/find-requires 4 | 5 | Summary: Slurm Lua SPANK plugin 6 | Name: slurm-spank-lua 7 | Version: 0.47 8 | Release: %{slurm_version}.1%{?dist} 9 | License: GPL 10 | Group: System Environment/Base 11 | Source0: %{name}-%{version}.tar.gz 12 | BuildRoot: %{_tmppath}/%{name}-%{version} 13 | 14 | BuildRequires: slurm-devel bison flex pkgconfig(pkg-config) 15 | BuildRequires: pkgconfig(lua) >= 5.1 16 | Requires: slurm 17 | 18 | %description 19 | The lua.so spank plugin for Slurm allows lua scripts to take the place of 20 | compiled C shared objects in the Slurm spank(8) framework. All the power of the 21 | C SPANK API is exported to lua via this plugin, which loads one or scripts and 22 | executes lua functions during the appropriate Slurm phase (as described in the 23 | spank(8) manpage). 24 | 25 | 26 | %prep 27 | %setup -q 28 | # Dummy file used to get a RPM dependency on libslurm.so 29 | echo 'int main(){}' > %{_builddir}/libslurm_dummy.c 30 | cat < %{_builddir}/find-requires 31 | #!/bin/sh 32 | # Add dummy to list of files sent to the regular find-requires 33 | { echo %{_builddir}/libslurm_dummy; cat; } | \ 34 | %{_rpmconfigdir}/find-requires 35 | EOF 36 | chmod +x %{_builddir}/find-requires 37 | 38 | %build 39 | %{__cc} -g -o lua.o -fPIC -c lua.c $(pkg-config --cflags lua) 40 | %{__cc} -g -o lib/list.o -fPIC -c lib/list.c 41 | %{__cc} -g -shared -fPIC -o lua.so lua.o lib/list.o -llua 42 | # Dummy file to get a dependency on libslurm 43 | %{__cc} -lslurm -o %{_builddir}/libslurm_dummy %{_builddir}/libslurm_dummy.c 44 | 45 | 46 | %install 47 | rm -rf $RPM_BUILD_ROOT 48 | mkdir -p $RPM_BUILD_ROOT%{_libdir}/slurm 49 | mkdir -p $RPM_BUILD_ROOT%{_mandir}/man8 50 | mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/slurm/plugstack.conf.d 51 | mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/slurm/lua.d/disabled 52 | install -m 755 lua.so $RPM_BUILD_ROOT%{_libdir}/slurm 53 | install -m 644 scripts/spank_demo.lua \ 54 | $RPM_BUILD_ROOT%{_sysconfdir}/slurm/lua.d/disabled 55 | echo "required lua.so /etc/slurm/lua.d/*.lua" > \ 56 | $RPM_BUILD_ROOT/%{_sysconfdir}/slurm/plugstack.conf.d/lua.conf 57 | install -D -m0644 spank-lua.8 $RPM_BUILD_ROOT/%{_mandir}/man8/spank-lua.8 58 | 59 | 60 | %clean 61 | rm -rf "$RPM_BUILD_ROOT" 62 | 63 | 64 | %files 65 | %defattr(-,root,root,-) 66 | %dir %attr(0755,root,root) %{_sysconfdir}/slurm/lua.d 67 | %{_libdir}/slurm/lua.so 68 | %{_sysconfdir}/slurm/plugstack.conf.d/lua.conf 69 | %{_sysconfdir}/slurm/lua.d/disabled/spank_demo.lua 70 | %{_mandir}/man8/spank-lua* 71 | 72 | 73 | %changelog 74 | * Fri Oct 14 2022 Matt Ezell - 0.47-1 75 | - Use pkg-config to find lua headers (SLES support) 76 | - Always build with support for spank:getopt() 77 | - Ensure loadstr_table is null-terminated 78 | * Mon Aug 15 2022 Trey Dockendorf - 0.46-1 79 | - Fix compilation issue with Lua >5.2 80 | * Tue May 17 2022 Kilian Cavalotti - 0.45-1 81 | - Add support for slurm_debug2(), slurm_debug3() and slurm_spank_log() 82 | * Fri Feb 11 2022 Kilian Cavalotti - 0.44-1 83 | - Fix issue with multiple scripts and Lua > 5.1 84 | - Add support for Lua 5.4 85 | * Tue May 11 2021 Trey Dockendorf - 0.43-1 86 | - Add support for Lua 5.3 87 | * Tue Mar 02 2021 Trey Dockendorf - 0.42-1 88 | - Force a dependency on versioned libslurm.so 89 | * Tue Aug 11 2020 Trey Dockendorf - 0.41-1 90 | - Keep dist in RPM release 91 | * Tue Aug 11 2020 Trey Dockendorf - 0.40-1 92 | - Add Slurm version in RPM release 93 | - Add "package" target in Makefile 94 | * Thu Nov 29 2018 Kilian Cavalotti - 0.39-1 95 | - Added missing SPEC %files entry for spank_demo.lua 96 | * Mon Jan 22 2018 Kilian Cavalotti - 0.38-2 97 | - Provides spank_demo.lua script 98 | * Tue Jan 16 2018 Kilian Cavalotti - 0.38-1 99 | - Fix segfault if no Lua scripts are present 100 | - Include the slurm/lua.d directory 101 | * Tue Jan 16 2018 Kilian Cavalotti - 0.37-2 102 | - Requires lua-devel, as the liblua.so symlink, which is required by the 103 | plugin, is only provided in the -devel package. 104 | * Tue Jan 16 2018 Kilian Cavalotti - 0.37-1 105 | - Initial build, extracted and repackaged from LLNL's slurm-spank-plugins. 106 | -------------------------------------------------------------------------------- /spank-lua.8: -------------------------------------------------------------------------------- 1 | .TH "spank-lua" "8" "2012-02-18" "spank-lua" "lua bindings for Slurm spank framework" 2 | 3 | .SH "NAME" 4 | \fBspank-lua\fR \- Lua bindings for Slurm in a SPANK plugin 5 | 6 | .SH "DESCRIPTION" 7 | 8 | This manual page describes the \fBspank-lua\fR lua bindings for 9 | Slurm, which allow lua scripts to be embedded in Slurm job launch 10 | code via the \fBspank\fR(8) interface. 11 | 12 | The \fBlua.so\fR spank plugin for Slurm allows lua scripts to take the 13 | place of compiled C shared objects in the Slurm \fBspank\fR(8) framework. 14 | All the functionality of the C SPANK API is exported to lua via this plugin, 15 | which loads one or more scripts and executes lua functions during the appropriate 16 | Slurm phase (as described in the \fBspank\fR(8) manpage). 17 | 18 | This plugin provides Slurm administrators with the ability to easily 19 | expose new options to the Slurm user commands \fBsrun\fR(1), 20 | \fBsalloc\fR(1), and \fBsbatch\fR(1) commands, modify the Slurm job launch 21 | environment, and execute custom code from within the context of Slurm 22 | all with the power of a fast interpreted language. For one example of 23 | \fBspank-lua\fR usage scenario, see the \fIEXAMPLES\fR section below. 24 | 25 | .SH "CONFIGURATION" 26 | 27 | The lua SPANK plugin is configured like other SPANK plugins by adding 28 | a line to the plugstack.conf like: 29 | .nf 30 | 31 | required lua.so [\fIOPTIONS\fR] [[\fIGLOB\fR] [\fISCRIPT OPTIONS\fR]... 32 | 33 | .fi 34 | 35 | where \fIGLOB\fR is a path to a lua script to load or a glob which 36 | may load multiple lua scripts, \fIOPTIONS\fR is a list of options for 37 | \fBspank-lua\fR itself, and \fISCRIPT OPTIONS\fR is a space-separated list 38 | of extra options to pass to all lua scripts loaded by this line. These 39 | options are passed to lua scripts in the \fBargs\fR member of the 40 | \fBspank\fR handle. For example: 41 | .nf 42 | 43 | required lua.so /etc/slurm/lua.d/* verbose=1 44 | 45 | .fi 46 | 47 | Would load all files in /etc/slurm/lua.d and would pass an 48 | \fBargs\fR table in the \fBspank\fR handle passed to lua scripts 49 | such that 50 | .nf 51 | 52 | \fBspank.args\fR = { "verbose=1" } 53 | 54 | .fi 55 | 56 | At this time, the only supported \fIOPTIONS\fR for \fBspank-lua\fR are 57 | \fBfailonerror\fR, which enabled fatal errors for script loading and 58 | parsing errors, instead of just skipping the current lua script. 59 | 60 | .SH "SPANK LUA API" 61 | 62 | As with compiled spank plugins, spank lua scripts may be called 63 | from one or more of many contexts during the allocation, launch, 64 | and exit of a Slurm job. Which of these contexts a spank-lua 65 | script is called from depends on which of the callbacks listed 66 | in the \fBspank\fR(8) manpage are defined by the loaded script. 67 | These callbacks are listed here by name for reference, but please 68 | see \fBspank\fR(8) manpage for more information on the context 69 | of each hook listed: 70 | .TP 8 71 | .B slurm_spank_init 72 | Called at initialization in all contexts (except slurmd). 73 | .TP 74 | .B slurm_spank_slurmd_init 75 | Called at slurmd initialization. 76 | .TP 77 | .B slurm_spank_job_prolog 78 | Called just before job prolog. 79 | .TP 80 | .B slurm_spank_init_post_opt 81 | Called post option processing in all contexts. 82 | .TP 83 | .B slurm_spank_local_user_init 84 | Called in local context post-job-allocation but before job launch. 85 | .TP 86 | .B slurm_spank_user_init 87 | Called after privileges have been temporarily dropped on remote 88 | nodes (called once per node). 89 | .TP 90 | .B slurm_spank_task_init_privileged 91 | Called for each task just after fork, but before elevated privileges 92 | are dropped permanently. 93 | .TP 94 | .B slurm_spank_task_init 95 | Called once for each task just before execve(2). 96 | .TP 97 | .B slurm_spank_task_post_fork 98 | Called for each task from parent process after fork(2) is complete. 99 | Due to the fact that \fBslurmd\fR does not exec any tasks until all 100 | tasks have completed fork(2), this call is guaranteed to run before 101 | the user task is executed. (remote context only) 102 | .TP 103 | .B slurm_spank_task_exit 104 | Called for each task as its exit status is collected by Slurm. 105 | (remote context only) 106 | .TP 107 | .B slurm_spank_job_epilog 108 | Called just before the job epilog. 109 | .TP 110 | .B slurm_spank_slurmd_exit 111 | Called in slurmd just before the daemon exits. 112 | .TP 113 | .B slurm_spank_exit 114 | Called once just before \fBslurmstepd\fR exits in remote context. 115 | In local context, called before \fBsrun\fR exits. 116 | .LP 117 | One or more of these hooks may exist in each lua script 118 | loaded by the \fBspank-lua\fR plugin. Each hook is run at the 119 | appropriate time, and is passed a single argument, the \fBspank\fR 120 | handle which is described below. 121 | .LP 122 | These scripts are allowed to return a single error code back 123 | to Slurm. A value of -1 indicates failure (See \fBSPANK.FAILURE\fR 124 | below), and anything else is considered to indicate success. No 125 | return value is accepted is considered to indicate success. 126 | 127 | .SH "SPANK-LUA HANDLE" 128 | 129 | .LP 130 | Each time one of the spank functions is called from a lua 131 | script, the spank-lua plugin sets up a \fBspank\fR table to pass 132 | as the first and only argument to that function. This table 133 | serves as the spank handle for the duration of the call, and 134 | exports several methods that may be used by the script, including 135 | .TP 8 136 | .B context 137 | The value of \fBspank.context\fR indicates the current context in which 138 | the spank plugin is running, and may have the string value 139 | "\fIlocal\fR", "\fIallocator\fR", or "\fIremote\fR". 140 | .TP 141 | .B args 142 | If any extra arguments were included in the \fBplugstack.conf\fR entry 143 | for \fBlua.so\fR, these are collected and included in \fBspank.args\fR 144 | array. (Remember that lua arrays are indexed starting at 1, not 0). 145 | .TP 146 | .B slurm_version 147 | Version of Slurm as a string, e.g. "\fB2.1.0\fR". 148 | .TP 149 | .BI get_item " (ITEM)" 150 | Return the spank item indicated by \fIITEM\fR, where 151 | ITEM is the string representation of the spank_item_t enumerated type 152 | as named in slurm/spank.h. (For example, the Slurm job stepid 153 | would be obtained via 154 | .nf 155 | 156 | local stepid = spank:get_item ("\fBS_JOB_STEPID\fR") 157 | 158 | .fi 159 | For items that return multiple values, spank-lua will return a table, 160 | for example, spank:get_item ("S_JOB_ENV") returns a lua table of 161 | the job environment with t[name] = val for each environment entry 162 | \fBname\fR=\fBval\fR. 163 | 164 | For some specific items, \fBget_item\fR will return additional 165 | values following the raw item value. Currently, the only item 166 | for which this is true is \fBS_TASK_EXIT_STATUS\fR: 167 | .nf 168 | 169 | \fIstatus\fR, \fIexitcode\fR, \fItermsig\fR, \fIcore\fR = 170 | spank:get_item ("\fBS_TASK_EXIT_STATUS\fR") 171 | 172 | .fi 173 | Where \fIstatus\fR is the raw exit status as reported by \fBwait\fR(2), 174 | \fIexitcode\fR is the exit code as returned by \fBWEXITSTATUS\fR(\fIstatus\fR) 175 | (or \fInil\fR if not \fBWIFEXITED\fR(\fIstatus\fR)), and \fItermsig\fR and 176 | \fIcore\fR hold the values of \fBWTERMSIG\fR(\fIstatus\fR) and 177 | \fBWCOREDUMP\fR(\fIstatus\fR) respectively, or \fInil\fR if not 178 | \fBWIFSIGNALED\fR(\fIstatus\fR). For example, to print information 179 | about the exiting task, you might run: 180 | .nf 181 | 182 | local fmt = string.format 183 | local msg 184 | local _, exitcode, termsig, core = 185 | spank:get_item ("S_TASK_EXIT_STATUS") 186 | 187 | if exitcode then 188 | msg = fmt ("Exited with code %d\\n", exitcode) 189 | elseif termsig then 190 | local s = "" 191 | if core then s = " (Core dumped)" end 192 | msg = fmt ("Killed by signal %d%s\\n", termsig, s) 193 | end 194 | print (msg) 195 | .fi 196 | 197 | .TP 198 | .BI getenv " (name)" 199 | Return the value of environment variable \fIname\fR from the job's 200 | environment. As with regular \fBspank\fR plugins, this call is only 201 | necessary in remote context, since the job's environment in local 202 | context can be obtained via os.getenv(). For example: 203 | .nf 204 | 205 | local ldpath = spank:getenv ("LD_LIBRARY_PATH") 206 | .fi 207 | .TP 208 | .BI setenv " (name, value, [overwrite])" 209 | Set the environment variable \fIname\fR to \fIvalue\fR in the job's 210 | environment, overwriting the old value of \fI name\fR if the optional 211 | boolean parameter \fIoverwrite\fR is true. For example: 212 | .nf 213 | 214 | if not spank:setenv ("LD_LIBRARY_PATH", ldpath, 1) then 215 | print ("Failed to set LD_LIBRARY_PATH") 216 | end 217 | .fi 218 | .TP 219 | .BI unsetenv " (name)" 220 | Unset the environment variable \fIname\fR from the job's environment 221 | if it exists. 222 | .TP 223 | .BI job_control_setenv " (name, value, [overwrite])" 224 | Like \fBsetenv\fR, but for the \fIjob control\fR environment, which 225 | is passed to the Slurm control scripts like epilog, prolog, SlurmctldProlog, 226 | and SlurmctldEpilog. Variables set in this way have the string SPANK_ 227 | prepended to them before they are set in the environment of these 228 | scripts. 229 | .TP 230 | .BI job_control_getenv " (name)" 231 | Same as \fBgetenv\fR, but for the job control environment. 232 | .TP 233 | .BI job_control_unsetenv " (name)" 234 | Same as \fBunsetenv\fR, but for the job control environment. 235 | .TP 236 | .BI register_option " (spankopt)" 237 | Registers a single option \fIspankopt\fR to Slurm. See \fISPANK OPTIONS\fR 238 | below for the format of the \fIspankopt\fR parameter. This method is 239 | only valid from the \fBslurm_spank_init\fR hook in local and allocator 240 | context. 241 | .TP 242 | .BI getopt " (spankopt) " 243 | Checks for supplied option \fIspankopt\fR in the current context. This 244 | is an alternative to using a callback function and global variable in 245 | normal spank callbacks. This function can only be used after options have 246 | been processed (i.e. after \fBslurm_spank_init\fR in most contexts). In 247 | \fBjob_script\fR context, \fBspank:getopt\fR is the \fIonly\fR supported 248 | method to check for supplied options (that is, from \fBslurm_spank_job_prolog\fR 249 | and \fBslurm_spank_job_epilog\fR). 250 | .LP 251 | In addition to the \fBspank\fR handle passed to spank-lua functions, 252 | spank-lua scripts also have access to utility functions and values in 253 | a global \fBSPANK\fR table. The members of this table include: 254 | .TP 8 255 | .B SPANK.log_* 256 | Utility functions for logging messages using Slurm's log facility. 257 | Included log functions are \fBlog_error\fB, \fBlog_info\fR, \fBlog_verbose\fR, 258 | \fBlog_debug\fR. These functions all take a lua format string and a 259 | variable number of arguments, for example: 260 | .nf 261 | 262 | SPANK.log_error ("%s: %s", myname, errormsg) 263 | 264 | .fi 265 | .TP 266 | .B SPANK.SUCCESS 267 | Return value to indicate a successful return from a spank callback. That is, 268 | lua functions should return \fBSPANK.SUCCESS\fR on successful completion. 269 | .TP 270 | .B SPANK.FAILURE 271 | Return value indicating failure of a spank-lua function. 272 | .LP 273 | 274 | .SH "SPANK OPTIONS" 275 | Exporting options to \fBsrun\fR(1), \fRsalloc\fR(1) and \fBsbatch\fR(1) 276 | may be accomplished with spank-lua in a similar manner as a normal \fBspank\fR 277 | plugin. Each option to be exported is set in a lua table such as: 278 | .nf 279 | 280 | spankopt = { 281 | name = STRING, 282 | usage = STRING, 283 | val = NUM, 284 | cb = STRING, 285 | has_arg = BOOLEAN, 286 | arginfo = STRING, 287 | } 288 | 289 | .fi 290 | Where the meaning of each member of the table has the same meaning as the 291 | struct \fBspank_option\fR members described in \fBspank\fR(8) manpage. That 292 | is: 293 | .TP 8 294 | .B name 295 | is the name of the option. That is the option will be specified by users 296 | as --\fBname\fR. This is a required parameter. 297 | .TP 298 | .B usage 299 | is a short description of the option suitable for \-\-help output. This 300 | is a required parameter. 301 | .TP 302 | .B val 303 | A plugin\-local value which is returned to the option callback function. 304 | This is a required parameter. 305 | .TP 306 | .B cb 307 | Is the name of a global function to use as the callback function when 308 | an option is invoked by the user. The option callback is invoked in both 309 | local/allocator and remote contexts, and must take three arguments like: 310 | .nf 311 | 312 | function cb (val, optarg, remote) 313 | 314 | .fi 315 | Where val is the \fBspankopt.val\fR used when registering the option, 316 | \fBoptarg\fR is the argument (if any) passed to the option, and 317 | \fRremote\fR is a boolean indicating whether the option callback is 318 | being made in local/allocator or remote context. 319 | .LP 320 | Options may be registered by spank-lua scripts either by use of 321 | the spank:register_option() method from \fBslurm_spank_init\fR, or 322 | by exporting a global \fBspank_options\fR table. The \fBspank_options\fR 323 | table must be a list of spank option tables as described above, 324 | for example: 325 | .nf 326 | 327 | spank_options = { 328 | { 329 | name = "test", 330 | usage = "A test option for spank-lua", 331 | val = 1, 332 | cb = "option_handler", 333 | } 334 | } 335 | 336 | .fi 337 | 338 | When \fBspank:getopt\fR is supported, the callback function \fIcb\fR is 339 | optional, since the \fBgetopt\fR function may be used as an alternative. 340 | 341 | .SH EXAMPLE 342 | 343 | The following example \fBspank-lua\fR script exports an environment 344 | varable to the Slurm prolog and epilog to control the current value 345 | of memory overcommit on the nodes of the job. Users can enable this 346 | optional behavior by using the new commandline option --no-memory-overcommit. 347 | 348 | .nf 349 | .sp 350 | -- Global spank_options table: 351 | -- 352 | \fBspank_options\fR = { 353 | { 354 | name = "no-memory-overcommit", 355 | usage = "Disable memory overcommit on nodes of Slurm job", 356 | cb = "opt_handler" 357 | } 358 | } 359 | .sp 360 | got_option = false 361 | function opt_handler (v, arg, remote) got_option = true end 362 | .sp 363 | function \fBslurm_spank_init_post_opt\fR (spank) 364 | -- 365 | -- Return success if we're not in local context, or the user 366 | -- did not specify --no-memory-overcommit. 367 | -- 368 | if \fBspank.context\fR == "remote" or not got_option then 369 | return \fISPANK.SUCCESS\fR 370 | end 371 | 372 | \fBSPANK.log_info\fR ("slurm_spank_init_post_opt") 373 | 374 | -- 375 | -- Set SPANK_NO_OVERCOMMIT in the "job control" environment 376 | -- 377 | local rc, msg = \fBspank:job_control_setenv\fR ("NO_OVERCOMMIT", 1, 1) 378 | 379 | -- 380 | -- Like other lua functions, spank methods return nil and an 381 | -- error string on failure. 382 | -- 383 | if rc == nil then 384 | \fBSPANK.log_error\fR ("Failed to propagate NO_OVERCOMMIT: %s", msg) 385 | return \fISPANK.FAILURE\fR 386 | end 387 | return \fISPANK.SUCCESS\fR 388 | end 389 | .sp 390 | .fi 391 | .LP 392 | The corresponding section of the Slurm prolog might then look like: 393 | 394 | .nf 395 | 396 | if test -n "$SPANK_NO_OVERCOMMIT"; then 397 | echo 2 > /proc/sys/vm/overcommit_memory 398 | fi 399 | 400 | 401 | .fi 402 | (Corresponding code to reset the overcommit_memory value in the epilog 403 | should also be included in any full-featured solution) 404 | 405 | .LP 406 | The \fBsrun\fR command would now present a new option to the user: 407 | 408 | .nf 409 | 410 | $ srun --help 411 | ... 412 | 413 | Options provided by plugins: 414 | --no-memory-overcommit Disable memory overcommit on nodes of Slurm job 415 | 416 | .fi 417 | 418 | .SH COPYRIGHT 419 | Copyright (C) 2009 Lawrence Livermore National Security, LLC. 420 | Produced at Lawrence Livermore National Laboratory. UCRL-CODE-235358 421 | 422 | This is free software; you can redistribute it and/or modify it under the 423 | terms of the GNU General Public License as published by the Free Software 424 | Foundation. 425 | 426 | .SH "SEE ALSO" 427 | .BR spank (8), 428 | .BR srun (1), 429 | .BR salloc (1), 430 | .BR sbatch (1), 431 | .PP 432 | \fBhttp://slurm-spank-plugins.googlecode.com/\fR 433 | --------------------------------------------------------------------------------