diff --git a/.gitignore b/.gitignore index 40a933a..70c02c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,60 @@ +<<<<<<< HEAD +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +build/ +sunneed_log.txt +======= # Prerequisites *.d @@ -54,3 +111,4 @@ dkms.conf build/ sunneed_log.txt +>>>>>>> c1ce9be87ee40f52cd027a965adce8ad5d136a36 diff --git a/.gitmodules b/.gitmodules index 804c283..c9c3a70 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,12 @@ -[submodule "ext/libbq27441"] - path = ext/libbq27441 - url = https://github.com/zacharied/libbq27441.git -[submodule "ext/nng"] - path = ext/nng - url = https://github.com/nanomsg/nng.git -[submodule "test/munit"] - path = test/munit - url = https://github.com/nemequ/munit.git -[submodule "ext/SunneeD_dev_drivers"] - path = ext/SunneeD_dev_drivers - url = https://github.com/jonterry9/SunneeD_dev_drivers.git +[submodule "ext/libbq27441"] + path = ext/libbq27441 + url = https://github.com/zacharied/libbq27441.git +[submodule "ext/nng"] + path = ext/nng + url = https://github.com/nanomsg/nng.git +[submodule "test/munit"] + path = test/munit + url = https://github.com/nemequ/munit.git +[submodule "ext/SunneeD_dev_drivers"] + path = ext/SunneeD_dev_drivers + url = https://github.com/jonterry9/SunneeD_dev_drivers.git diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c2d9d76 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.associations": { + "sunneed_pip_interface.h": "c", + "sunneed_listener.h": "c", + "cstring": "c", + "memory": "c", + "xmemory": "c", + "xstring": "c", + "xutility": "c" + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index d159169..89e08fb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,339 +1,339 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile index 46188fc..35bd648 100644 --- a/Makefile +++ b/Makefile @@ -1,199 +1,199 @@ -# Builds the main sunneed executable. - -ifeq ($(origin CC),default) - export CC = gcc -endif - -CFLAGS ?= -Wall -Wextra -g -PROTOC ?= protoc-c - -SUNNEED_BUILD_TYPE ?= devel -SUNNEED_BUILD_PIP ?= bq27441 -SUNNEED_BUILD_OUT_DIR ?= build -SUNNEED_BUILD_BIN_FILE ?= sunneed -SUNNEED_BUILD_CLIENT_LIB_NAME ?= libsunneedclient -SUNNEED_BUILD_OVERLAY_LIB_NAME ?= sunneed_overlay - -export SOURCE_FORMATTER = clang-format -style=file -i - -export cflags_deps = -I$(PWD)/$(ext_dir)/nng/include -L$(PWD)/$(ext_dir)/nng/build -L$(PWD)/$(ext_dir)/libbq27441 -lnng -lpthread -ldl -lprotobuf-c -latomic -I$(PWD)/$(ext_dir)/libbq27441 -lbq27441 -li2c - -ifeq ($(SUNNEED_BUILD_TYPE),devel) - util_cflags = -Wl,-rpath,$(CURDIR)/$(clientlib_out_dir) -endif - -src_dir = src -sources = $(wildcard $(src_dir)/*.c) - -ext_dir = ext - -out_dir = $(SUNNEED_BUILD_OUT_DIR) -bin_file = $(SUNNEED_BUILD_BIN_FILE) - -pip_out_dir = $(out_dir) -pip_obj = $(pip_out_dir)/pip.o -pip_name = $(SUNNEED_BUILD_PIP) - -clientlib_sources = $(wildcard $(src_dir)/client/*.c) -clientlib_out_dir = $(out_dir)/client -clientlib_obj = $(clientlib_out_dir)/$(SUNNEED_BUILD_CLIENT_LIB_NAME).so - -overlay_sources = $(wildcard $(src_dir)/overlay/*.c) -overlay_out_dir = $(out_dir) -overlay_obj = $(overlay_out_dir)/$(SUNNEED_BUILD_OVERLAY_LIB_NAME).so -overlay_testing_obj = $(patsubst %.so,%_testing.so,$(overlay_obj)) -overlay_runner = $(out_dir)/run-with-overlay - -protobuf_dir = $(src_dir)/protobuf -protobuf_sources = $(wildcard $(protobuf_dir)/*.proto) -protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) -protobuf_out_dir = $(src_dir)/protobuf/c -protobuf_out_sources = $(wildcard $(protobuf_out_dir)/*.c) - -export test_home = test -export test_runner_name = run-tests -test_runner = $(test_home)/$(test_runner_name) -runtime_tests_runner = ./runtime_tests - -device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) -util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) - -all: pre-all main overlay util - -run_valgrind: pre-all main overlay util - valgrind ./build/sunneed - -run_ASAN: pre-all main_ASAN overlay util - ./build/sunneed - - -pre-all: - @echo "Starting all build..." - -log_pwr: pre-all main_pwr_data overlay util - - -main: ext protobuf pip devices - $(call section_title,main executable) - $(CC) $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) - -main_pwr_data: ext protobuf pip devices - $(call section_title, main executable) - $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) - -main_ASAN: ext protobuf pip devices - $(call section_title,main executable) - $(CC) -fsanitize=address $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) - -pip: pre-pip $(src_dir)/pip/$(pip_name).c - $(CC) $(CFLAGS) -o $(pip_obj) -c $(src_dir)/pip/$(pip_name).c -pre-pip: - $(call section_title,pip) - -devices: pre-devices ext $(device_objs) -pre-devices: - $(call section_title,devices) - -util: clientlib pre-util $(util_objs) -pre-util: - $(call section_title,utils) - -$(src_dir)/util/%.o: $(src_dir)/util/%.c - $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) -L$(clientlib_out_dir) -lsunneedclient $(util_cflags) $(cflags_deps) - -$(src_dir)/client/%.o: $(src_dir)/client/%.c - $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) $(cflags_deps) - -$(src_dir)/device/%.o: $(src_dir)/device/%.c - @if [ ! -d "$(out_dir)/device" ]; then mkdir "$(out_dir)/device"; fi - $(CC) $(CFLAGS) -g -shared -o $(patsubst $(src_dir)/device/%.o, $(out_dir)/device/%.so, $@) -fPIC $^ $(cflags_deps) - -protobuf: pre-protobuf $(protobuf_out_files) - @rm -rf "$(protobuf_out_dir)" && mkdir "$(protobuf_out_dir)" - mv $(protobuf_out_files) $(protobuf_out_dir) -pre-protobuf: - $(call section_title,protobuf) - -$(protobuf_out_files): $(protobuf_sources) - $(MAKE) --no-print-directory -C $(protobuf_dir) - -clientlib: ext - $(call section_title,client library) - @if [ ! -d "$(out_dir)/client" ]; then mkdir "$(out_dir)/client"; fi - $(CC) $(CFLAGS) -c -fPIC -o $(out_dir)/client/clientlib.o $(clientlib_sources) $(cflags_deps) - $(CC) $(CFLAGS) -shared -o $(clientlib_obj) $(out_dir)/client/clientlib.o - -# Run the runtime tests -runtime_test: main - $(runtime_tests_runner) - -test: tests - $(test_runner) - -tests: - make -C $(test_home) - -# Note that we compile two overlay libraries. One is a tester, which contains additional output meant for -# debugging/testing purposes. -overlay: clientlib - $(call section_title,overlay) - $(CC) $(CFLAGS) -g -fPIC -shared $(overlay_sources) -o $(overlay_obj) $(cflags_deps) - $(CC) $(CFLAGS) -g -fPIC -shared -DTESTING $(overlay_sources) -o $(overlay_testing_obj) $(cflags_deps) - @echo Generate overlay runscript at $(overlay_runner) - @echo "#!/usr/bin/env bash\n$(overlay_runscript_content)" > $(overlay_runner) - chmod +x $(overlay_runner) - -clean: - rm -rf "$(out_dir)"/* - rm -rf "$(protobuf_out_dir)"/* - $(MAKE) -C $(test_home) clean - @echo '=============================================================' - @echo '= External library files were not cleaned. =' - @echo '= Please run `make -C ext clean` if you wish to clean them. =' - @echo '=============================================================' - -format: - $(SOURCE_FORMATTER) $(shell find '$(src_dir)' -not -path '$(protobuf_dir)/*' -type f -regex '.*\.[ch]') - $(MAKE) -C $(test_home) format - -tags: - ctags -R src/* - -ext: - $(call section_title,dependencies) - $(MAKE) -C $(ext_dir) - -.PHONY: all pip util test runtime_test clean format ext tags - -LeftParens := ( -RightParens := ) - -# Prints a nice little header graphic in the form: -# -# ======================= -# === Building === -# ======================= -# -# When using in the Makefile, in simple cases you can just call this at the beginning of the target. For more -# complicated targets, however, you will have to make a `pre-` target and run that before resolving the rest -# of the target. Remember to put your `pre-` target *after* the targets that your depends upon, but *before* the list -# of files (if any). See the `main`, `util`, and `devices` targets for examples of usage in different situations. -section_count := 1 -num_sections := $(shell grep -E '^\s*\$$\$(LeftParens)\s*call\s+section_title,' Makefile | wc -l) -define section_title - @echo - $(eval _var := $(section_count)/$(num_sections) $(1)) - $(eval _len := $(shell x="$(_var)"; echo -n $${#x})) - @printf '=%.0s' $(shell seq -16 $(_len)) - @echo - @echo === $(section_count)/$(num_sections) Building $(1) === - @printf '=%.0s' $(shell seq -16 $(_len)) - @echo - $(eval section_count := $(shell expr $(section_count) + 1)) -endef - -ifeq ($(SUNNEED_BUILD_TYPE),devel) - overlay_runscript_content := "gdb --args env LD_PRELOAD=$(abspath $(overlay_testing_obj)) $$\@" -else - overlay_runscript_content := "LD_PRELOAD=$(abspath $(overlay_obj)) $$\@" -endif +# Builds the main sunneed executable. + +ifeq ($(origin CC),default) + export CC = gcc +endif + +CFLAGS ?= -Wall -Wextra -g +PROTOC ?= protoc-c + +SUNNEED_BUILD_TYPE ?= devel +SUNNEED_BUILD_PIP ?= bq27441 +SUNNEED_BUILD_OUT_DIR ?= build +SUNNEED_BUILD_BIN_FILE ?= sunneed +SUNNEED_BUILD_CLIENT_LIB_NAME ?= libsunneedclient +SUNNEED_BUILD_OVERLAY_LIB_NAME ?= sunneed_overlay + +export SOURCE_FORMATTER = clang-format -style=file -i + +export cflags_deps = -I$(PWD)/$(ext_dir)/nng/include -L$(PWD)/$(ext_dir)/nng/build -L$(PWD)/$(ext_dir)/libbq27441 -lnng -lpthread -ldl -lprotobuf-c -latomic -I$(PWD)/$(ext_dir)/libbq27441 -lbq27441 -li2c + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + util_cflags = -Wl,-rpath,$(CURDIR)/$(clientlib_out_dir) +endif + +src_dir = src +sources = $(wildcard $(src_dir)/*.c) + +ext_dir = ext + +out_dir = $(SUNNEED_BUILD_OUT_DIR) +bin_file = $(SUNNEED_BUILD_BIN_FILE) + +pip_out_dir = $(out_dir) +pip_obj = $(pip_out_dir)/pip.o +pip_name = $(SUNNEED_BUILD_PIP) + +clientlib_sources = $(wildcard $(src_dir)/client/*.c) +clientlib_out_dir = $(out_dir)/client +clientlib_obj = $(clientlib_out_dir)/$(SUNNEED_BUILD_CLIENT_LIB_NAME).so + +overlay_sources = $(wildcard $(src_dir)/overlay/*.c) +overlay_out_dir = $(out_dir) +overlay_obj = $(overlay_out_dir)/$(SUNNEED_BUILD_OVERLAY_LIB_NAME).so +overlay_testing_obj = $(patsubst %.so,%_testing.so,$(overlay_obj)) +overlay_runner = $(out_dir)/run-with-overlay + +protobuf_dir = $(src_dir)/protobuf +protobuf_sources = $(wildcard $(protobuf_dir)/*.proto) +protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) +protobuf_out_dir = $(src_dir)/protobuf/c +protobuf_out_sources = $(wildcard $(protobuf_out_dir)/*.c) + +export test_home = test +export test_runner_name = run-tests +test_runner = $(test_home)/$(test_runner_name) +runtime_tests_runner = ./runtime_tests + +device_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/device/*.c)) +util_objs = $(patsubst %.c, %.o, $(wildcard $(src_dir)/util/*.c)) + +all: pre-all main overlay util + +run_valgrind: pre-all main overlay util + valgrind ./build/sunneed + +run_ASAN: pre-all main_ASAN overlay util + ./build/sunneed + + +pre-all: + @echo "Starting all build..." + +log_pwr: pre-all main_pwr_data overlay util + + +main: ext protobuf pip devices + $(call section_title,main executable) + $(CC) $(CFLAGS) -DTESTING -ULOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +main_pwr_data: ext protobuf pip devices + $(call section_title, main executable) + $(CC) $(CFLAGS) -DTESTING -DLOG_PWR $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +main_ASAN: ext protobuf pip devices + $(call section_title,main executable) + $(CC) -fsanitize=address $(CFLAGS) -DTESTING $(sources) $(protobuf_out_sources) $(pip_obj) $(cflags_deps) -o $(out_dir)/$(bin_file) + +pip: pre-pip $(src_dir)/pip/$(pip_name).c + $(CC) $(CFLAGS) -o $(pip_obj) -c $(src_dir)/pip/$(pip_name).c +pre-pip: + $(call section_title,pip) + +devices: pre-devices ext $(device_objs) +pre-devices: + $(call section_title,devices) + +util: clientlib pre-util $(util_objs) +pre-util: + $(call section_title,utils) + +$(src_dir)/util/%.o: $(src_dir)/util/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) -L$(clientlib_out_dir) -lsunneedclient $(util_cflags) $(cflags_deps) + +$(src_dir)/client/%.o: $(src_dir)/client/%.c + $(CC) $(CFLAGS) -o $(patsubst $(src_dir)/util/%.o, $(out_dir)/%, $@) $^ $(protobuf_out_sources) $(cflags_deps) + +$(src_dir)/device/%.o: $(src_dir)/device/%.c + @if [ ! -d "$(out_dir)/device" ]; then mkdir "$(out_dir)/device"; fi + $(CC) $(CFLAGS) -g -shared -o $(patsubst $(src_dir)/device/%.o, $(out_dir)/device/%.so, $@) -fPIC $^ $(cflags_deps) + +protobuf: pre-protobuf $(protobuf_out_files) + @rm -rf "$(protobuf_out_dir)" && mkdir "$(protobuf_out_dir)" + mv $(protobuf_out_files) $(protobuf_out_dir) +pre-protobuf: + $(call section_title,protobuf) + +$(protobuf_out_files): $(protobuf_sources) + $(MAKE) --no-print-directory -C $(protobuf_dir) + +clientlib: ext + $(call section_title,client library) + @if [ ! -d "$(out_dir)/client" ]; then mkdir "$(out_dir)/client"; fi + $(CC) $(CFLAGS) -c -fPIC -o $(out_dir)/client/clientlib.o $(clientlib_sources) $(cflags_deps) + $(CC) $(CFLAGS) -shared -o $(clientlib_obj) $(out_dir)/client/clientlib.o + +# Run the runtime tests +runtime_test: main + $(runtime_tests_runner) + +test: tests + $(test_runner) + +tests: + make -C $(test_home) + +# Note that we compile two overlay libraries. One is a tester, which contains additional output meant for +# debugging/testing purposes. +overlay: clientlib + $(call section_title,overlay) + $(CC) $(CFLAGS) -g -fPIC -shared $(overlay_sources) -o $(overlay_obj) $(cflags_deps) + $(CC) $(CFLAGS) -g -fPIC -shared -DTESTING $(overlay_sources) -o $(overlay_testing_obj) $(cflags_deps) + @echo Generate overlay runscript at $(overlay_runner) + @echo "#!/usr/bin/env bash\n$(overlay_runscript_content)" > $(overlay_runner) + chmod +x $(overlay_runner) + +clean: + rm -rf "$(out_dir)"/* + rm -rf "$(protobuf_out_dir)"/* + $(MAKE) -C $(test_home) clean + @echo '=============================================================' + @echo '= External library files were not cleaned. =' + @echo '= Please run `make -C ext clean` if you wish to clean them. =' + @echo '=============================================================' + +format: + $(SOURCE_FORMATTER) $(shell find '$(src_dir)' -not -path '$(protobuf_dir)/*' -type f -regex '.*\.[ch]') + $(MAKE) -C $(test_home) format + +tags: + ctags -R src/* + +ext: + $(call section_title,dependencies) + $(MAKE) -C $(ext_dir) + +.PHONY: all pip util test runtime_test clean format ext tags + +LeftParens := ( +RightParens := ) + +# Prints a nice little header graphic in the form: +# +# ======================= +# === Building === +# ======================= +# +# When using in the Makefile, in simple cases you can just call this at the beginning of the target. For more +# complicated targets, however, you will have to make a `pre-` target and run that before resolving the rest +# of the target. Remember to put your `pre-` target *after* the targets that your depends upon, but *before* the list +# of files (if any). See the `main`, `util`, and `devices` targets for examples of usage in different situations. +section_count := 1 +num_sections := $(shell grep -E '^\s*\$$\$(LeftParens)\s*call\s+section_title,' Makefile | wc -l) +define section_title + @echo + $(eval _var := $(section_count)/$(num_sections) $(1)) + $(eval _len := $(shell x="$(_var)"; echo -n $${#x})) + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + @echo === $(section_count)/$(num_sections) Building $(1) === + @printf '=%.0s' $(shell seq -16 $(_len)) + @echo + $(eval section_count := $(shell expr $(section_count) + 1)) +endef + +ifeq ($(SUNNEED_BUILD_TYPE),devel) + overlay_runscript_content := "gdb --args env LD_PRELOAD=$(abspath $(overlay_testing_obj)) $$\@" +else + overlay_runscript_content := "LD_PRELOAD=$(abspath $(overlay_obj)) $$\@" +endif diff --git a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c new file mode 100644 index 0000000..0e8017b --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_recv.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int remote_fd, read_val; +// int pi_sock; //uncomment for TCP sockets + struct sockaddr_in remote_addr; + int opt = 1; + int addrlen = sizeof(remote_addr); + char buffer[1024] = {0}; + float timeout = 0.5; + + //change from SOCK_DGRAM to SOCK_STREAM for TCP sockets + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failed\n"); + exit(0); + } + + if(setsockopt(remote_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_addr.s_addr = INADDR_ANY; + remote_addr.sin_port = htons(PORT); + + if(bind(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("bind failed\n"); + exit(0); + } + + /* + * Uncomment this section if using TCP sockets, comment out for UDP sockets + */ + + /* + if(listen(remote_fd, 3) < 0) + { + perror("listen failed\n"); + exit(0); + } + + if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + exit(0); + } + */ + + printf("connected to remote host\n"); + int i; + + while((read_val = read(remote_fd, buffer, 1024)) > 0) + { + printf("received: %s\n", buffer); + sleep(timeout); + + timeout += 0.5; + } +} diff --git a/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c new file mode 100644 index 0000000..42aca72 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Pi/pi_send.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies, allowing the network card to idle for varying times + * starts at 500 ms and increases by 500 ms every iteration + * + * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char msg[64] = {'A'}; + float downtime = 0.5; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("socket failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + exit(0); + } + + printf("connected to remote host\n"); + + int i; + for (i = 0; i < 20; i++) + { + printf("sending msg\n"); + send(remote_fd, msg, sizeof(msg), 0); + + sleep(downtime); + + downtime += 0.5; + } +} diff --git a/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c new file mode 100644 index 0000000..bee8cb6 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_recv.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int pi_fd, read_val; +// int pi_sock; //uncomment this if using TCP sockets + struct sockaddr_in pi_addr; + int opt = 1; + int addrlen = sizeof(pi_addr); + char buffer[1024] = {0}; + float downtime = 0.5; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + return 0; + } + + if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_addr.s_addr = INADDR_ANY; + pi_addr.sin_port = htons(PORT); + + if(bind(pI_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + + /* + * Uncomment this section is using TCP sockets + */ + + /* + if(listen(pi_fd, 3) < 0) + { + perror("listen failed\n"); + return 0; + } + + if((pi_sock = accept(pi_fd, (struct sockaddr *)&pi_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + return 0; + } + */ + + printf("connected to pi\n"); + while((read_val = read(pi_fd, buffer, 1024)) > 0) + { + printf("received: %s\n", buffer); + sleep(downtime); + + downtime += 0.5; + } +} \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_send.c b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_send.c new file mode 100644 index 0000000..e315183 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Frequency/Remote/remote_send.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies to the Pi + * + * Uses UDP sockets as written, follow comments below to + * change to TCP sockets + */ + +int +main(void) +{ + int pi_fd, read_val; + struct sockaddr_in pi_addr; + char msg[64]; + msg[0] = 'A'; + float timeout = 0.5; + + //change from SOCK_DGRAM to SOCK_STREAM to create TCP socket + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + exit(0); + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("connection failed\n"); + exit(0); + } + + printf("connected to pi\n"); + int i; + + for(i = 0; i < 20; i++) + { + printf("sending: %s\n", msg); + send(pi_fd, msg, sizeof(msg), 0); + + sleep(timeout); + + timeout += 0.5; + } +} \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c new file mode 100644 index 0000000..8c46589 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_recv.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int sock_fd, remote_sock, read_val; + struct sockaddr_in remote_addr; + int opt = 1; + int addrlen = sizeof(remote_addr); + char buffer[1024] = {0}; + + + //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failure\n"); + return 0; + } + + if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_addr.s_addr = INADDR_ANY; + remote_addr.sin_port = htons(PORT); + + if(bind(sock_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + /* + * Uncomment this section if testing with TCP (make sure to change the socket from + * SOCK_DGRAM to SOCK_STREAM + if(listen(sock_fd, 3) < 0) + { + perror("listen failed\n"); + return 0; + } + + if((remote_sock = accept(sock_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) + { + perror("accept failed\n"); + return 0; + }*/ + printf("connected to remote host\n"); + + while((read_val = read(sock_fd, buffer, 1024)) != 0) + { + printf("received %s\n", buffer); + sleep(1); + } +} diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c new file mode 100644 index 0000000..66ea788 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Pi/pi_send.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * send varying packet sizes from the Pi to a remote host using UDP sockets + * see comments below for how to convert this code to use TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char msg[64]; + char buffer[1024] = {0}; + + msg[0] = 'A'; + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM for a TCP socket + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("failed to create socket\n"); + return 0; + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.214", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert address\n"); + return 0; + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + return 0; + } + + printf("connected to remote host\n"); + int i; + + for(i = 0; i < 64; i++) + { + printf("msg to send: %s\n", msg); + send(remote_fd, msg, strlen(msg), 0); + + msg[i + 1] = (char)(msg[0] + i + 1); + + sleep(1); + } +} diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c new file mode 100644 index 0000000..a554721 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_recv.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +int +main(void) +{ + int pi_fd, read_val; +// int pi_sock; //uncomment this for TCP sockets + struct sockaddr_in pi_addr; + int opt = 1; + int addrlen = sizeof(pi_addr); + char buffer[1024] = {0}; + + //create UDP socket, change from SOCK_DGRAM to SOCK_STREAM for TCP sockets and uncomment the section below + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) + { + perror("socket failed\n"); + return 0; + } + + if(setsockopt(pi_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + { + perror("setsockopt failed\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_addr.s_addr = INADDR_ANY; + pi_addr.sin_port = htons(PORT); + + if(bind(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + perror("bind failed\n"); + return 0; + } + + /* + * Uncomment this section if using TCP sockets, comment out if using UDP sockets + */ + +// if(listen(pi_fd, 3) < 0) +// { +// perror("listen failed\n"); +// return 0; +// } + +// if((pi_sock = accept(pi_fd, (struct sockaddr *)&remote_addr, (socklen_t*)&addrlen)) < 0) +// { +// perror("accept failed\n"); +// return 0; +// } + + printf("connected to pi\n"); + while((read_val = read(pi_fd, buffer, 1024)) > 0) + { + printf("received %s\n", buffer); + sleep(1); + } +} \ No newline at end of file diff --git a/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c new file mode 100644 index 0000000..4b3a539 --- /dev/null +++ b/PowerModel/SunneeD_network_tests/Packet_Size/Remote/remote_send.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send varying sized packets from this host to the Pi + * uses UDP sockets to keep the packet size consistent with what is being sent + * + * to test with TCP, change SOCK_DGRAM to SOCK_STREAM and see the note for testing with + * TCP in recv_test_size.c on the Pi + */ +int +main(void) +{ + int pi_fd, read_val; + struct sockaddr_in pi_addr; + char msg[64]; + msg[0] = 'A'; + + + + char buffer[1024] = {0}; + + if((pi_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + fprintf(stderr, "failed to create socket\n"); + return 0; + } + + pi_addr.sin_family = AF_INET; + pi_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "192.168.1.134", &pi_addr.sin_addr)<=0) + { + fprintf(stderr, "invalid address/failed to convert address\n"); + return 0; + } + + if(connect(pi_fd, (struct sockaddr *)&pi_addr, sizeof(pi_addr)) < 0) + { + fprintf(stderr, "connection failed\n"); + return 0; + } + + printf("connected to pi\n"); + int i; + /* + * starting with an empty string, add one char at a time up to 64 + * since chars are 1 byte each, this should be roughly equivalent to + * increasing the packet size by one byte for each send + */ + for(i = 0; i < 64; i++) + { + printf("msg to send: %s\n", msg); + send(pi_fd, msg, sizeof(msg), 0); + + sleep(1); + + msg[i + 1] = (char)(msg[0] + 1 + i); + } +} \ No newline at end of file diff --git a/PowerModel/tmp.txt b/PowerModel/tmp.txt new file mode 100644 index 0000000..32f95c0 --- /dev/null +++ b/PowerModel/tmp.txt @@ -0,0 +1 @@ +hi \ No newline at end of file diff --git a/README.md b/README.md index 3d9e1ea..ec82026 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,56 @@ -

- Sunny D logo - -

- -[![Build Status](https://dev.azure.com/gwsystems/sunneed/_apis/build/status/gwsystems.sunneed?branchName=master)](https://dev.azure.com/gwsystems/sunneed/_build/latest?definitionId=3&branchName=master) - -`sunneed` (pronounced "Sunny D") is a framework for tracking and managing the distribution of power consumption of individual -processes in multi-tenant computing environments. In systems with uncertain reserves of power available, such as a computer -powered by a solar battery, it is impossible to guarantee unlimited power to each tenant. The basis of this project is to make -toolset for tenants to run **power-constrained code** on the system, limited by a power budget described by `sunneed`. - -# Installation - -Below is instructions for getting a basic instance of `sunneed` running on a Linux host. Package names are intentionally -left ambiguous due to the variety of naming schemes for packages across distros; see the `misc/install_dependencies` -file for package names on Ubuntu 18.04. - -If you are running Ubuntu 18.04 (and likely other versions of Ubuntu and Debian), you may run`misc/install_dependencies` -(with root privileges) and then skip to [Dependencies of Dependencies](#dependencies-of-dependencies). - -## Getting the dependencies - -`sunneed` uses protobufs for I2C. Specifically, it uses the `protobufs-c` library. Both the `protoc-c` compiler and the -`libprotobuf-c` headers and libraries must be installed. - -### Dependencies of dependencies - -We also need to manually compile some dependencies. Run `git submodule update --init --recursive`. This will download -the code for NNG and libbq27441, which must be compiled manually on common distros. - -For building NNG, you will need CMake > 3.13 and Ninja. These are available as packages on common distros. - -For building libbq27441, you will need the headers for the Linux I2C library. This comes under different names depending -on the distro; you want whichever package gives you the file `/usr/include/linux/i2c-dev.h`. - -## Compilation - -Once you have all the dependencies in place, we can begin compilation. First, create the output directory by running -`mkdir build`. Then, begin compilation by running `make` in the root of the `sunneed` directory. This should compile all -the local dependencies, the `sunneed` runtime overlay, client library, and main executable. - -After all this, there should be a file `build/sunneed` in the `sunneed` directory. This is the core `sunneed` binary. -You can run this, and then run one of the example programs to test connectivity to `sunneed`. - - -## Running with overlay tester - -Once you have successfully built sunneed and verified that the `build/sunneed` binary has been made, we can run the overlay tester to verify that sunneed is correctly intercepting `open()` and `write()` requests. *Pro tip: open a second terminal so that you don't have to type around the sunneed log prints.* Execute the sunneed binary by running `sudo ./sunneed &`. Note that the `&` is not necessary if you are running in a second terminal. In your second terminal or after sunneed begins execution, we can run the overlay_tester located in `sunneed/build/test_progs/`. Before running it, we have to `LD_PRELOAD` SunneeD's overlay so that the `open()` and `write()` calls are redirected correctly. You can do all of this with the following command: `LD_PRELOAD= ./build/test_progs/overlay_tester` from the sunneed directory. **Note: the argument to LD_PRELOAD must be the ABSOLUTE path of sunneed_overlay.so, using a relative path will not work.** - +

+ Sunny D logo + +

+ +[![Build Status](https://dev.azure.com/gwsystems/sunneed/_apis/build/status/gwsystems.sunneed?branchName=master)](https://dev.azure.com/gwsystems/sunneed/_build/latest?definitionId=3&branchName=master) + +`sunneed` (pronounced "Sunny D") is a framework for tracking and managing the distribution of power consumption of individual +processes in multi-tenant computing environments. In systems with uncertain reserves of power available, such as a computer +powered by a solar battery, it is impossible to guarantee unlimited power to each tenant. The basis of this project is to make +toolset for tenants to run **power-constrained code** on the system, limited by a power budget described by `sunneed`. + +# Installation + +Below is instructions for getting a basic instance of `sunneed` running on a Linux host. Package names are intentionally +left ambiguous due to the variety of naming schemes for packages across distros; see the `misc/install_dependencies` +file for package names on Ubuntu 18.04. + +If you are running Ubuntu 18.04 (and likely other versions of Ubuntu and Debian), you may run`misc/install_dependencies` +(with root privileges) and then skip to [Dependencies of Dependencies](#dependencies-of-dependencies). + +## Getting the dependencies + +`sunneed` uses protobufs for I2C. Specifically, it uses the `protobufs-c` library. Both the `protoc-c` compiler and the +`libprotobuf-c` headers and libraries must be installed. + +### Dependencies of dependencies + +We also need to manually compile some dependencies. Run `git submodule update --init --recursive`. This will download +the code for NNG and libbq27441, which must be compiled manually on common distros. + +For building NNG, you will need CMake > 3.13 and Ninja. These are available as packages on common distros. + +For building libbq27441, you will need the headers for the Linux I2C library. This comes under different names depending +on the distro; you want whichever package gives you the file `/usr/include/linux/i2c-dev.h`. + +## Compilation + +Once you have all the dependencies in place, we can begin compilation. First, create the output directory by running +`mkdir build`. Then, begin compilation by running `make` in the root of the `sunneed` directory. This should compile all +the local dependencies, the `sunneed` runtime overlay, client library, and main executable. + +After all this, there should be a file `build/sunneed` in the `sunneed` directory. This is the core `sunneed` binary. +You can run this, and then run one of the example programs to test connectivity to `sunneed`. + + +## Running with overlay tester + +Once you have successfully built sunneed and verified that the `build/sunneed` binary has been made, we can run the overlay tester to verify that sunneed is correctly intercepting `open()` and `write()` requests. *Pro tip: open a second terminal so that you don't have to type around the sunneed log prints.* Execute the sunneed binary by running `sudo ./sunneed &`. Note that the `&` is not necessary if you are running in a second terminal. +![Sunneed](./res/Run_SunneeD.JPG) + +In your second terminal or after sunneed begins execution, we can run the overlay_tester located in `sunneed/build/`. Before running it, we have to `LD_PRELOAD` SunneeD's overlay so that the `open()` and `write()` calls are redirected correctly. You can do all of this with the following command: `LD_PRELOAD= ./build/overlay_tester` from the sunneed directory. **Note: the argument to LD_PRELOAD must be the ABSOLUTE path of sunneed_overlay.so, using a relative path will not work.** + +![Overlay_test](./res/Run_Overlay_LDPRELOAD.JPG) + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 86654e4..ee63b87 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,60 +1,60 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -variables: -- name: IS_AZURE_PIPELINE - value: true - -trigger: -- master - -pool: - vmImage: 'ubuntu-18.04' - -container: grahamschock/suneed_env:latest - -steps: -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - sudo ./misc/install_dependencies - git clone https://github.com/gwsystems/sunneed.git - git submodule update --init --recursive - ls -la - displayName: 'Clone system' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - mkdir build - make - displayName: 'Build sunneed' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - rm /usr/bin/gcc - ln -s /usr/bin/gcc-8 /usr/bin/gcc - make tests - displayName: 'Build unit tests' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - ls -la - make test - displayName: 'Run unit tests' - -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - cd src/sunneed - make runtime_test - displayName: 'Run runtime tests' +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +variables: +- name: IS_AZURE_PIPELINE + value: true + +trigger: +- master + +pool: + vmImage: 'ubuntu-18.04' + +container: grahamschock/suneed_env:latest + +steps: +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + sudo ./misc/install_dependencies + git clone https://github.com/gwsystems/sunneed.git + git submodule update --init --recursive + ls -la + displayName: 'Clone system' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + mkdir build + make + displayName: 'Build sunneed' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + rm /usr/bin/gcc + ln -s /usr/bin/gcc-8 /usr/bin/gcc + make tests + displayName: 'Build unit tests' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + ls -la + make test + displayName: 'Run unit tests' + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + cd src/sunneed + make runtime_test + displayName: 'Run runtime tests' diff --git a/design/Build/Build.md b/design/Build/Build.md index c41c389..cceb2fb 100644 --- a/design/Build/Build.md +++ b/design/Build/Build.md @@ -1,40 +1,40 @@ -# SunneeD Build - -## Parts - -* Raspberry Pi 0w - -* ULN2003 Stepper Motor and Driver - -* SparkFun Battery Babysitter - -* LiPo 1000mAh battery - -* Pixy2 Camera - -* Breadboard (1/2+) - -## Wiring - -![img](./SunneeD_Wiring_Diagram.png) - -**Note: the system should be powered by the battery babysitter and not a micro-usb connection to the Pi. Otherwise the i2c connection between the babysitter and Pi may not work.** - - -### Raspberry Pi 0w Pinmap -* All pin #'s below refernce this pinmap - -![img](./RPi0w_pinmap.png) - -| Pi pin | Connection | -| :----- | :------- | -| 2 | 5v out (to stepper driver) | -| 3 | SDA (from babysitter) | -| 5 | SCL (from babysitter) | -| 9 | GND (out to stepper driver)| -| 16 | Stepper driver In1 | -| 17 | 3.3v In (battery) | -| 18 | Stepper driver In2 | -| 19 | Stepper driver In4 | -| 20 | GND (battery) | +# SunneeD Build + +## Parts + +* Raspberry Pi 0w + +* ULN2003 Stepper Motor and Driver + +* SparkFun Battery Babysitter + +* LiPo 1000mAh battery + +* Pixy2 Camera + +* Breadboard (1/2+) + +## Wiring + +![img](./SunneeD_Wiring_Diagram.png) + +**Note: the system should be powered by the battery babysitter and not a micro-usb connection to the Pi. Otherwise the i2c connection between the babysitter and Pi may not work.** + + +### Raspberry Pi 0w Pinmap +* All pin #'s below refernce this pinmap + +![img](./RPi0w_pinmap.png) + +| Pi pin | Connection | +| :----- | :------- | +| 2 | 5v out (to stepper driver) | +| 3 | SDA (from babysitter) | +| 5 | SCL (from babysitter) | +| 9 | GND (out to stepper driver)| +| 16 | Stepper driver In1 | +| 17 | 3.3v In (battery) | +| 18 | Stepper driver In2 | +| 19 | Stepper driver In4 | +| 20 | GND (battery) | | 22 | Stepper driver In3 | \ No newline at end of file diff --git a/design/scheduling/schedulerModel_draft1.tex b/design/scheduling/schedulerModel_draft1.tex index 8bd0d34..a39bbda 100644 --- a/design/scheduling/schedulerModel_draft1.tex +++ b/design/scheduling/schedulerModel_draft1.tex @@ -1,35 +1,35 @@ -\documentclass[12pt,english]{amsart} -\usepackage{amsthm} -\usepackage{amssymb} -\usepackage{amsmath} - - -\begin{document} -The basic idea of the scheduler is a round robin scheduler where we introduce power constraints on tenants and the requests tenants can make. If a tenant's request is at the top of the queue and they've reached their power limit for the current power window, the request is put to the back of the queue. In order to prevent a device from monopolizing power in the system, if a request is at the top of the queue and the request has been made too many times in the current power window, the request is put at the back of the queue. - - Each tentant $t$ on the system is allocated a percentage $c_t$ of power in a fixed power window $P.$ For a basic implementation we can set $c_t = \frac{1}{N}$ where $N$ is the number of tenants in the system. Note that we always have $\sum_{t=1}^Nc_t=1.$ We can track the amount of estimated power used by each tenant in power windows $P$ and dynamically update $c_t$ without tracking much additional data as we already need each tenants estimated power usage to charge them. - -For each operation tenants can request from a device, Sunneed intercepts the request and checks the estimated amount of power this operation will consume. This is checked by referencing the data from the regression model we will write to estimate power usage of each possible write to each device in the system. - -Let $X = \{x_1,x_2,\ldots,x_n\}$ be the set of requests Sunneed can intercept. For each $x_i \in X$ let $p_i$ denote the estimated power usage of the corresponding device operation obtained from regression model. - -Let $a_i$ denote the maximum number of times the request $x_i$ can be made in the power window $P.$ We need $a_i$ such that $\sum_{i=1}^na_ip_i \leq P$ so the system will not overdraw power in the power window $P.$ The cap on the number of times each request can be made - $a_i$ - ensures no one device or operation on a device is able to monopolize the power in the system. - -We can start with $a_i = \frac{\frac{P}{f_i}}{p_i}$ where $f_i=|X|,$ or the number of different requests Sunneed can intercept, for all $i,$ giving each request equal percentage of the power in the power window $P.$ - - We can then keep track of the number of times each call is made in power windows of $P.$ If the average number of calls made is lower than the current $a_i$ for any $x_i$ we can increase $f_i$ so $a_i = \text{average number of calls},$ therefore lowering the percentage of power $x_i$ gets in power window $P.$ If any $x_i$ is called $a_i$ number of times frequently and we have $(a_i+1)p_i \leq P - \sum_{j=1}^np_jf_j : j\ne i$ -- in other words, some other call $x_j$ has had the corresponding $f_j$ increased, making it possible to increase the percent of power allocated to $x_i$ -- we can set $f_i = 1-\frac{P}{\sum_{j=1}^np_jf_j : j}.$ This value of $f_i$ gives us $a_i = \frac{P - \sum_{j=1}^np_jf_j : j}{p_i},$ or the number of calls $x_i$ that would put us at $P$ power used in the power window $P.$ - - \textbf{Problems with idea:} - - 1. If a call $x_i$ is made an average of $0$ times in power window $P$ we don't want to set $a_i=0$ as this will prevent the request from being made in the future. - - 2. We'd have to track the number times each request is made to dynamically update the power allocation - this could potentially be a large amount of data to track and therefore take up memory. However, I don't think tracking tenant power usage to update $c_t$ would present an issue as we already need to track tenants power usage and already have to maintain the number of power windows that have occurred to dynamically update $a_i.$ Since the total power consumed by tenant and the number of power windows the tenant has existed for is all we need for an average, there is no additional data tracked. - - \bigskip - - \textbf{Citations:} - Idea inspired by content of this paper (particularly section 2.2) - - Utility Accrual Real-Time Scheduling Under the Unimodal Arbitrary Arrival Model with Energy Bounds(Haisang Wu; Binoy Ravindran; E. Douglas Jensen): - https://ieeexplore-ieee-org.proxygw.wrlc.org/document/4302708 +\documentclass[12pt,english]{amsart} +\usepackage{amsthm} +\usepackage{amssymb} +\usepackage{amsmath} + + +\begin{document} +The basic idea of the scheduler is a round robin scheduler where we introduce power constraints on tenants and the requests tenants can make. If a tenant's request is at the top of the queue and they've reached their power limit for the current power window, the request is put to the back of the queue. In order to prevent a device from monopolizing power in the system, if a request is at the top of the queue and the request has been made too many times in the current power window, the request is put at the back of the queue. + + Each tentant $t$ on the system is allocated a percentage $c_t$ of power in a fixed power window $P.$ For a basic implementation we can set $c_t = \frac{1}{N}$ where $N$ is the number of tenants in the system. Note that we always have $\sum_{t=1}^Nc_t=1.$ We can track the amount of estimated power used by each tenant in power windows $P$ and dynamically update $c_t$ without tracking much additional data as we already need each tenants estimated power usage to charge them. + +For each operation tenants can request from a device, Sunneed intercepts the request and checks the estimated amount of power this operation will consume. This is checked by referencing the data from the regression model we will write to estimate power usage of each possible write to each device in the system. + +Let $X = \{x_1,x_2,\ldots,x_n\}$ be the set of requests Sunneed can intercept. For each $x_i \in X$ let $p_i$ denote the estimated power usage of the corresponding device operation obtained from regression model. + +Let $a_i$ denote the maximum number of times the request $x_i$ can be made in the power window $P.$ We need $a_i$ such that $\sum_{i=1}^na_ip_i \leq P$ so the system will not overdraw power in the power window $P.$ The cap on the number of times each request can be made - $a_i$ - ensures no one device or operation on a device is able to monopolize the power in the system. + +We can start with $a_i = \frac{\frac{P}{f_i}}{p_i}$ where $f_i=|X|,$ or the number of different requests Sunneed can intercept, for all $i,$ giving each request equal percentage of the power in the power window $P.$ + + We can then keep track of the number of times each call is made in power windows of $P.$ If the average number of calls made is lower than the current $a_i$ for any $x_i$ we can increase $f_i$ so $a_i = \text{average number of calls},$ therefore lowering the percentage of power $x_i$ gets in power window $P.$ If any $x_i$ is called $a_i$ number of times frequently and we have $(a_i+1)p_i \leq P - \sum_{j=1}^np_jf_j : j\ne i$ -- in other words, some other call $x_j$ has had the corresponding $f_j$ increased, making it possible to increase the percent of power allocated to $x_i$ -- we can set $f_i = 1-\frac{P}{\sum_{j=1}^np_jf_j : j}.$ This value of $f_i$ gives us $a_i = \frac{P - \sum_{j=1}^np_jf_j : j}{p_i},$ or the number of calls $x_i$ that would put us at $P$ power used in the power window $P.$ + + \textbf{Problems with idea:} + + 1. If a call $x_i$ is made an average of $0$ times in power window $P$ we don't want to set $a_i=0$ as this will prevent the request from being made in the future. + + 2. We'd have to track the number times each request is made to dynamically update the power allocation - this could potentially be a large amount of data to track and therefore take up memory. However, I don't think tracking tenant power usage to update $c_t$ would present an issue as we already need to track tenants power usage and already have to maintain the number of power windows that have occurred to dynamically update $a_i.$ Since the total power consumed by tenant and the number of power windows the tenant has existed for is all we need for an average, there is no additional data tracked. + + \bigskip + + \textbf{Citations:} + Idea inspired by content of this paper (particularly section 2.2) + + Utility Accrual Real-Time Scheduling Under the Unimodal Arbitrary Arrival Model with Energy Bounds(Haisang Wu; Binoy Ravindran; E. Douglas Jensen): + https://ieeexplore-ieee-org.proxygw.wrlc.org/document/4302708 \end{document} \ No newline at end of file diff --git a/ext/Makefile b/ext/Makefile index 78756aa..e301c8e 100644 --- a/ext/Makefile +++ b/ext/Makefile @@ -1,19 +1,19 @@ -.ONESHELL: -dependencies: nng libbq27441 - -nng: - @cd nng - @[ ! -d build ] && mkdir build; cd build - cmake -G Ninja .. - if [ ! -f libnng.a ]; then - ninja - fi - -libbq27441: - make -C libbq27441 - -clean: - rm -rf nng/build - make -C libbq27441 clean - -.PHONY: nng libbq27441 dependencies clean +.ONESHELL: +dependencies: nng libbq27441 + +nng: + @cd nng + @[ ! -d build ] && mkdir build; cd build + cmake -G Ninja .. + if [ ! -f libnng.a ]; then + ninja + fi + +libbq27441: + make -C libbq27441 + +clean: + rm -rf nng/build + make -C libbq27441 clean + +.PHONY: nng libbq27441 dependencies clean diff --git a/misc/install_dependencies b/misc/install_dependencies index 3434d82..d392cf1 100755 --- a/misc/install_dependencies +++ b/misc/install_dependencies @@ -1,4 +1,5 @@ -apt install -y cmake -apt install -y ninja-build -apt install -y libprotobuf-c-dev protobuf-c-compiler -apt install -y libi2c-dev +apt install -y cmake +apt install -y ninja-build +apt install -y libprotobuf-c-dev protobuf-c-compiler +apt install -y libi2c-dev +echo "done installing dependencies" diff --git a/misc/tools/Dockerfile b/misc/tools/Dockerfile index 2150f9f..2016e18 100644 --- a/misc/tools/Dockerfile +++ b/misc/tools/Dockerfile @@ -1,36 +1,36 @@ -FROM ubuntu:18.04 -RUN apt-get -y update &&\ - apt-get -y install \ - htop \ - ninja-build\ - libprotobuf-c-dev protobuf-c-compiler\ - libi2c-dev\ - zsh \ - python3 \ - git \ - wget \ - bc \ - g++-multilib \ - gcc-8-multilib \ - libssl-dev \ - binutils-dev \ - curl \ - python \ - tmux \ - gpg \ - software-properties-common \ - vim &&\ - chsh -s /bin/zsh root &&\ - wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true - -RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null &&\ - apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main'&&\ - apt update -y &&\ - apt install -y cmake - -RUN apt-get -y install \ - ninja-build \ - libprotobuf-c-dev protobuf-c-compiler \ - libi2c-dev -RUN chmod a+rwx -R /usr - +FROM ubuntu:18.04 +RUN apt-get -y update &&\ + apt-get -y install \ + htop \ + ninja-build\ + libprotobuf-c-dev protobuf-c-compiler\ + libi2c-dev\ + zsh \ + python3 \ + git \ + wget \ + bc \ + g++-multilib \ + gcc-8-multilib \ + libssl-dev \ + binutils-dev \ + curl \ + python \ + tmux \ + gpg \ + software-properties-common \ + vim &&\ + chsh -s /bin/zsh root &&\ + wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true + +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null &&\ + apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main'&&\ + apt update -y &&\ + apt install -y cmake + +RUN apt-get -y install \ + ninja-build \ + libprotobuf-c-dev protobuf-c-compiler \ + libi2c-dev +RUN chmod a+rwx -R /usr + diff --git a/misc/wpa_supplicant_real.service b/misc/wpa_supplicant_real.service index f6d5c69..503461c 100644 --- a/misc/wpa_supplicant_real.service +++ b/misc/wpa_supplicant_real.service @@ -1,10 +1,10 @@ -# Symlink this to /etc/systemd/system . - -[Unit] -Description=WPA Supplicant but not broken - -[Service] -ExecStart=/sbin/wpa_supplicant -d -iwlan0 -c/etc/wpa_supplicant/wpa_supplicant.conf - -[Install] -WantedBy=multi-user.target +# Symlink this to /etc/systemd/system . + +[Unit] +Description=WPA Supplicant but not broken + +[Service] +ExecStart=/sbin/wpa_supplicant -d -iwlan0 -c/etc/wpa_supplicant/wpa_supplicant.conf + +[Install] +WantedBy=multi-user.target diff --git a/res/Run_Overlay_LDPRELOAD.JPG b/res/Run_Overlay_LDPRELOAD.JPG new file mode 100644 index 0000000..b70e973 Binary files /dev/null and b/res/Run_Overlay_LDPRELOAD.JPG differ diff --git a/res/Run_SunneeD.JPG b/res/Run_SunneeD.JPG new file mode 100644 index 0000000..65e4745 Binary files /dev/null and b/res/Run_SunneeD.JPG differ diff --git a/res/logo.svg b/res/logo.svg index 0c78b64..935b01e 100644 --- a/res/logo.svg +++ b/res/logo.svg @@ -1,196 +1,196 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - Sun vector illustration from: https://www.123freevectors.com/sun-vector-illustrator/ - - - - - - - - - - - - - - - - - SUNNEE D - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Sun vector illustration from: https://www.123freevectors.com/sun-vector-illustrator/ + + + + + + + + + + + + + + + + + SUNNEE D + + diff --git a/run_PyDriver.c b/run_PyDriver.c index d82e7ef..bc8e619 100644 --- a/run_PyDriver.c +++ b/run_PyDriver.c @@ -1,54 +1,54 @@ -#include -#include -#include -#include -#include - -int -main(void) -{ - PyObject *module, *func, *py_args, *py_kwargs; - PyGILState_STATE state; - - Py_Initialize(); - PyRun_SimpleString("import sys"); - PyRun_SimpleString("import os"); - PyRun_SimpleString("sys.path.append(os.getcwd()+'/ext/SunneeD_dev_drivers/PiCamDriver')"); - - if ( (module = PyImport_Import(PyUnicode_FromString("picam_driver"))) == NULL) { - PyErr_Print(); - abort(); - } - func = PyObject_GetAttrString(module, "read_pipe"); - - state = PyGILState_Ensure(); - - if (!PyCallable_Check(func)) { - fprintf(stderr, "Function not callable\n"); - goto fail; - } - - py_args = NULL; - py_kwargs = NULL; - - printf("calling function\n"); - PyObject_Call(func, py_args, py_kwargs); - - Py_DECREF(py_args); - Py_XDECREF(py_kwargs); - - if(PyErr_Occurred()) { - PyErr_Print(); - goto fail; - } - - PyGILState_Release(state); - - Py_Finalize(); - - return 0; - -fail: - PyGILState_Release(state); - abort(); +#include +#include +#include +#include +#include + +int +main(void) +{ + PyObject *module, *func, *py_args, *py_kwargs; + PyGILState_STATE state; + + Py_Initialize(); + PyRun_SimpleString("import sys"); + PyRun_SimpleString("import os"); + PyRun_SimpleString("sys.path.append(os.getcwd()+'/ext/SunneeD_dev_drivers/PiCamDriver')"); + + if ( (module = PyImport_Import(PyUnicode_FromString("picam_driver"))) == NULL) { + PyErr_Print(); + abort(); + } + func = PyObject_GetAttrString(module, "read_pipe"); + + state = PyGILState_Ensure(); + + if (!PyCallable_Check(func)) { + fprintf(stderr, "Function not callable\n"); + goto fail; + } + + py_args = NULL; + py_kwargs = NULL; + + printf("calling function\n"); + PyObject_Call(func, py_args, py_kwargs); + + Py_DECREF(py_args); + Py_XDECREF(py_kwargs); + + if(PyErr_Occurred()) { + PyErr_Print(); + goto fail; + } + + PyGILState_Release(state); + + Py_Finalize(); + + return 0; + +fail: + PyGILState_Release(state); + abort(); } \ No newline at end of file diff --git a/runtime_tests b/runtime_tests index f8ab4bd..08eb6c0 100755 --- a/runtime_tests +++ b/runtime_tests @@ -1,19 +1,19 @@ -#!/usr/bin/env bash - -testcount=$(build/sunneed -c) - -if [[ $testcount == 0 ]]; then - echo "No runtime tests available." >&2 - exit -fi - -echo "Found $testcount tests" - -for i in `seq 0 $((testcount - 1))`; do - echo -n "Running test #$i... " - build/sunneed -t $i 2>&1 | sed "s/Failure:.*/$(tput setaf 1)\\0$(tput sgr0)/" - code=${PIPESTATUS[0]} # Gets us the return value of sunneed (since sed is always 0). Pretty cool huh? - if [[ $code == 0 ]]; then - echo "$(tput setaf 2)Success!$(tput sgr0)" - fi -done +#!/usr/bin/env bash + +testcount=$(build/sunneed -c) + +if [[ $testcount == 0 ]]; then + echo "No runtime tests available." >&2 + exit +fi + +echo "Found $testcount tests" + +for i in `seq 0 $((testcount - 1))`; do + echo -n "Running test #$i... " + build/sunneed -t $i 2>&1 | sed "s/Failure:.*/$(tput setaf 1)\\0$(tput sgr0)/" + code=${PIPESTATUS[0]} # Gets us the return value of sunneed (since sed is always 0). Pretty cool huh? + if [[ $code == 0 ]]; then + echo "$(tput setaf 2)Success!$(tput sgr0)" + fi +done diff --git a/src/.gitignore b/src/.gitignore index 1edf03c..a8b2799 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1 @@ -protobuf/c/ +protobuf/c/ diff --git a/src/client/sunneed_client.c b/src/client/sunneed_client.c index 1d89241..dfa1111 100644 --- a/src/client/sunneed_client.c +++ b/src/client/sunneed_client.c @@ -1,244 +1,440 @@ -#include "sunneed_client.h" -#include "../log.h" -#include -#include -#include - -struct { - char *path; - int fd; -} locked_paths[MAX_LOCKED_FILES] = { { NULL, 0 } }; - -nng_socket sunneed_socket; - -static void -nngfatal(const char *func, int rv) { - FATAL(rv, "%s: %s\n", func, nng_strerror(rv)); -} - -/** - * Packs the given SunneedRequest into an NNG message and sends it to sunneed. If any failures occur in the packing - * or sending processes, this client will crash with a fatal error. - */ -static void -send_request(SunneedRequest *req) { - nng_msg *msg; - - int req_len = sunneed_request__get_packed_size(req); - void *buf = malloc(req_len); - if (!buf) - FATAL(-1, "unable to allocate buffer for request"); - sunneed_request__pack(req, buf); - -// SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, req_len); -// SUNNEED_NNG_TRY(nng_msg_insert, != 0, msg, buf, req_len); - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, 0); - SUNNEED_NNG_TRY(nng_msg_append, != 0, msg, buf, req_len); - SUNNEED_NNG_TRY(nng_sendmsg, != 0, sunneed_socket, msg, 0); - - free(buf); -// nng_msg_free(msg); -} - -static SunneedResponse * -receive_response(SunneedResponse__MessageTypeCase message_type) { - nng_msg *reply; - SUNNEED_NNG_TRY(nng_recvmsg, != 0, sunneed_socket, &reply, 0); - - size_t msg_len = nng_msg_len(reply); -// SUNNEED_NNG_MSG_LEN_FIX(msg_len); - SunneedResponse *resp = sunneed_response__unpack(NULL, msg_len, nng_msg_body(reply)); - - if (resp->status != 0) { - return NULL; - } else if (resp->message_type_case != message_type) { - FATAL(-1, "incorrect message type received (expected %d, got %d)", message_type, resp->status); - } - - nng_msg_free(reply); - return resp; -} - -int -sunneed_client_init(const char *name) { - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nngfatal); - SUNNEED_NNG_TRY(nng_req0_open, != 0, &sunneed_socket); - SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, NULL, 0); - - // Register this client with sunneed. - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT; - RegisterClientRequest register_req = REGISTER_CLIENT_REQUEST__INIT; - register_req.name = malloc(strlen(name) + 1); - if (!register_req.name) { - FATAL(-1, "failed to allocate memory for client name"); - return -1; - } - strncpy(register_req.name, name, strlen(name) + 1); - req.register_client = ®ister_req; - send_request(&req); - free(register_req.name); - - // Check the response. - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT); - for (size_t i = 0; i < resp->register_client->n_locked_paths; i++) { - locked_paths[i].path = malloc(strlen(resp->register_client->locked_paths[i]) + 1); - strcpy(locked_paths[i].path, resp->register_client->locked_paths[i]); - client_printf(-1, "Registered locked path '%s'", locked_paths[i].path); - } - sunneed_response__free_unpacked(resp, NULL); - - return 0; -} - -/** Allocate a string containing the path of the dummy file corresponding to the given path. */ -char * - -sunneed_client_fetch_locked_file_path(const char *pathname, int flags, int mode) { - // TODO Check socket opened. - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE; - OpenFileRequest open_file_req = OPEN_FILE_REQUEST__INIT; - open_file_req.path = malloc(strlen(pathname) + 1); - if (!open_file_req.path) { - FATAL(-1, "failed to allocated memory for path"); - } - strncpy(open_file_req.path, pathname, strlen(pathname) + 1); - - open_file_req.flags = flags; - - req.open_file = &open_file_req; - - open_file_req.flags = flags; - open_file_req.mode = mode; - - - send_request(&req); - free(open_file_req.path); - - // TODO Handle request of a path that isn't locked. - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE); - if (resp == NULL) { - // TODO Gotos - return 0; - } - - client_printf("Opening dummy path '%s'\n", resp->open_file->path); - char *path = malloc(strlen(resp->open_file->path) + 1); - if (!path) - FATAL(-1, "unable to allocate path"); - strcpy(path, resp->open_file->path); - - sunneed_response__free_unpacked(resp, NULL); - - return path; -} - -int -sunneed_client_check_locked_file(const char *pathname) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_paths[i].path == NULL) - return -1; - else if (strncmp(pathname, locked_paths[i].path, strlen(pathname)) == 0) - return i; - } - - return -1; -} - -int -sunneed_client_on_locked_path_open(int i, char *pathname, int fd) { - if (i < 0 || i >= MAX_LOCKED_FILES) - FATAL(-1, "locked-file array index out of bounds"); - if (pathname == NULL) - FATAL(-1, "pathname is null"); - if (fd <= 0) - FATAL(-1, "illegal FD"); - - locked_paths[i].path = pathname; - locked_paths[i].fd = fd; - - return 0; -} - -bool -sunneed_client_fd_is_locked(int fd) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) - if (locked_paths[i].fd == fd) - return true; - return false; -} - -ssize_t -sunneed_client_remote_write(int fd, const void *data, size_t n_bytes) { - // Get the dummy path corresponding to the FD. - int locked_file_i; - char *dummy_path = NULL; - for (locked_file_i = 0; locked_file_i < MAX_LOCKED_FILES; locked_file_i++) - if (locked_paths[locked_file_i].fd == fd) { - dummy_path = locked_paths[locked_file_i].path; - break; - } - - if (dummy_path == NULL) - FATAL(-1, "cannot remote write a non-dummy file"); - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_WRITE; - - WriteRequest write_req = WRITE_REQUEST__INIT; - write_req.dummy_path = malloc(strlen(dummy_path) + 1); - write_req.data.data = malloc(n_bytes); - if (!write_req.dummy_path || !write_req.data.data) - FATAL(-1, "failed to allocate write request data"); - strncpy((char *)write_req.data.data, data, n_bytes); - strncpy(write_req.dummy_path, dummy_path, strlen(dummy_path) + 1); - write_req.data.len = n_bytes; - - req.write = &write_req; - send_request(&req); - - free(write_req.dummy_path); - free(write_req.data.data); - - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE); - if (resp == NULL) { - // TODO Handle - FATAL(-1, "write response was NULL"); - } - sunneed_response__free_unpacked(resp, NULL); - - return 0; -} - -int -sunneed_client_disconnect(void) { - // TODO Check socket opened. - - SunneedRequest req = SUNNEED_REQUEST__INIT; - req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT; - UnregisterClientRequest unregister_req = UNREGISTER_CLIENT_REQUEST__INIT; - req.unregister_client = &unregister_req; - send_request(&req); - - SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); - if (resp == NULL) { - // TODO Handle - FATAL(-1, "Disconnect response was NULL\n"); - } - sunneed_response__free_unpacked(resp, NULL); - - client_printf("Unregistered.\n"); - return 0; -} - -void -sunneed_client_debug_print_locked_path_table(void) { - client_printf("locked files: [\n"); - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_paths[i].path != NULL) - client_printf(" FD %d : '%s'\n", locked_paths[i].fd, locked_paths[i].path); - } - client_printf("]\n"); -} +#include "sunneed_client.h" +#include "../log.h" +#include +#include +#include + +struct { + char *path; + int fd; +} locked_paths[MAX_LOCKED_FILES] = { { NULL, 0 } }; + +struct { + int dummy_sockfd; +} dummy_sockets[MAX_TENANT_SOCKETS] = { { -1 } }; + +nng_socket sunneed_socket; +nng_dialer sunneed_socket_dialer; +nng_msg *msg; +static void +nngfatal(const char *func, int rv) { + FATAL(rv, "%s: %s\n", func, nng_strerror(rv)); +} + +/** + * Packs the given SunneedRequest into an NNG message and sends it to sunneed. If any failures occur in the packing + * or sending processes, this client will crash with a fatal error. + */ +static void +send_request(SunneedRequest *req) { + //nng_msg *msg; + int req_len = sunneed_request__get_packed_size(req); + void *buf = malloc(req_len); + if (!buf) + FATAL(-1, "unable to allocate buffer for request"); + sunneed_request__pack(req, buf); + int ret = -1; + SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, 0); + SUNNEED_NNG_TRY(nng_msg_append, != 0, msg, buf, req_len); + SUNNEED_NNG_TRY(nng_sendmsg, != 0, sunneed_socket, msg, 0); + + + free(buf); +} + +static SunneedResponse * +receive_response(SunneedResponse__MessageTypeCase message_type) { + nng_msg *reply; + SUNNEED_NNG_TRY(nng_recvmsg, != 0, sunneed_socket, &reply, 0); + + size_t msg_len = nng_msg_len(reply); + //SUNNEED_NNG_MSG_LEN_FIX(msg_len); + SunneedResponse *resp = sunneed_response__unpack(NULL, msg_len, nng_msg_body(reply)); + + if (resp->status != 0) { + return NULL; + } else if (resp->message_type_case != message_type) { + FATAL(-1, "incorrect message type received (expected %d, got %d)", message_type, resp->status); + } + + nng_msg_free(reply); + return resp; +} + +int +sunneed_client_init(const char *name) { + + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nngfatal); + SUNNEED_NNG_TRY(nng_req0_open, != 0, &sunneed_socket); + SUNNEED_NNG_TRY(nng_dial, != 0, sunneed_socket, SUNNEED_LISTENER_URL, &sunneed_socket_dialer, 0); + + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT; + RegisterClientRequest register_req = REGISTER_CLIENT_REQUEST__INIT; + register_req.name = malloc(strlen(name) + 1); + if (!register_req.name) { + FATAL(-1, "failed to allocate memory for client name"); + return -1; + } + strncpy(register_req.name, name, strlen(name) + 1); + req.register_client = ®ister_req; + send_request(&req); + free(register_req.name); + + // Check the response. + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT); + for (size_t i = 0; i < resp->register_client->n_locked_paths; i++) { + locked_paths[i].path = malloc(strlen(resp->register_client->locked_paths[i]) + 1); + strcpy(locked_paths[i].path, resp->register_client->locked_paths[i]); + client_printf(-1, "Registered locked path '%s'", locked_paths[i].path); + } + sunneed_response__free_unpacked(resp, NULL); + + return 0; +} + +/** Allocate a string containing the path of the dummy file corresponding to the given path. */ +char * +sunneed_client_fetch_locked_file_path(const char *pathname, int flags) { + // TODO Check socket opened. + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE; + OpenFileRequest open_file_req = OPEN_FILE_REQUEST__INIT; + open_file_req.path = malloc(strlen(pathname) + 1); + if (!open_file_req.path) { + FATAL(-1, "failed to allocated memory for path"); + } + strncpy(open_file_req.path, pathname, strlen(pathname) + 1); + + open_file_req.flags = flags; + + req.open_file = &open_file_req; + + send_request(&req); + free(open_file_req.path); + + // TODO Handle request of a path that isn't locked. + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE); + if (resp == NULL) { + // TODO Gotos + return 0; + } + + client_printf("Opening dummy path '%s'\n", resp->open_file->path); + char *path = malloc(strlen(resp->open_file->path) + 1); + if (!path) + FATAL(-1, "unable to allocate path"); + strcpy(path, resp->open_file->path); + + sunneed_response__free_unpacked(resp, NULL); + + return path; +} + +int +sunneed_client_check_locked_file(const char *pathname) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (locked_paths[i].path == NULL) + return -1; + else if (strncmp(pathname, locked_paths[i].path, strlen(pathname)) == 0) + return i; + } + + return -1; +} + +int +sunneed_client_on_locked_path_open(int i, char *pathname, int fd) { + if (i < 0 || i >= MAX_LOCKED_FILES) + FATAL(-1, "locked-file array index out of bounds"); + if (pathname == NULL) + FATAL(-1, "pathname is null"); + if (fd <= 0) + FATAL(-1, "illegal FD"); + + //toSend[REQUETS_PER_PWR_LOG * 3] = pwr_change; + locked_paths[i].path = pathname; + locked_paths[i].fd = fd; + + return 0; +} + +bool +sunneed_client_fd_is_locked(int fd) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) + if (locked_paths[i].fd == fd) + return true; + return false; +} + +ssize_t +sunneed_client_remote_write(int fd, const void *data, size_t n_bytes) { + // Get the dummy path corresponding to the FD. + int locked_file_i; + char *dummy_path = NULL; + for (locked_file_i = 0; locked_file_i < MAX_LOCKED_FILES; locked_file_i++) + if (locked_paths[locked_file_i].fd == fd) { + dummy_path = locked_paths[locked_file_i].path; + break; + } + + if (dummy_path == NULL) + FATAL(-1, "cannot remote write a non-dummy file"); + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_WRITE; + + WriteRequest write_req = WRITE_REQUEST__INIT; + write_req.dummy_path = malloc(strlen(dummy_path) + 1); + write_req.data.data = malloc(n_bytes); + if (!write_req.dummy_path || !write_req.data.data) + FATAL(-1, "failed to allocate write request data"); + strncpy((char *)write_req.data.data, data, n_bytes); + strncpy(write_req.dummy_path, dummy_path, strlen(dummy_path) + 1); + write_req.data.len = n_bytes; + + req.write = &write_req; + send_request(&req); + + free(write_req.dummy_path); + free(write_req.data.data); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE); + if (resp == NULL) { + // TODO Handle + FATAL(-1, "write response was NULL"); + } + sunneed_response__free_unpacked(resp, NULL); + + return 0; +} + +int +sunneed_client_socket(int domain, int type, int protocol) +{ + + /* + * sunneed_socket_dialer points to the nng_dialer associated with the tenant <--> suneeed nng socket + * since the dialer is the last part of the socket set up, this should indicate whether the nng socket is set up or not + * if not, nng_dialer_id will return -1 to signal an invalid dialer, therefore we need to call SUPER to create the nng socket + */ + if(nng_dialer_id(sunneed_socket_dialer) == -1) + { + return -1; + } + + /* + * currently only support IPv4 UDP packets for power logging + * can extend for TCP and IPv6 later if needed + */ + if(domain != AF_INET) + { + return -1; + } + + if(type != SOCK_DGRAM) + { + //TODO: determine type of getnameinfo and getaddrinfo packets, both end up triggering this return case + //should error out if not one of those, but for now just return -1 to signal a call to SUPER + return -1; + } + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET; + + SocketRequest sock = SOCKET_REQUEST__INIT; + sock.domain = domain; + sock.type = type; + + + sock.protocol = protocol; + + req.socket = &sock; + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET); + if(resp == NULL) + { + FATAL(-1, "failed to create socket, no response"); + } + + //add the new fake socket from sunneed to the table, future sends to this fd will be caught by the send overlay + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_sockets[i].dummy_sockfd == -1) + { + dummy_sockets[i].dummy_sockfd = resp -> socket -> dummy_sockfd; + sunneed_response__free_unpacked(resp, NULL); + return dummy_sockets[i].dummy_sockfd; + } + } + + //TODO: handle not having enough free sockets + sunneed_response__free_unpacked(resp, NULL); + return -1; + +} + +/* + * dummy socket table lookup function for convenience + * TODO: could probably speed this up a little bit (currently O(n)) + */ +int +sunneed_client_is_dummysocket(int sockfd) +{ + int i; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + if(dummy_sockets[i].dummy_sockfd == sockfd) + { + return 1; + } + } + return 0; +} + +int +sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + + char host_name[NI_MAXHOST]; + char address[INET_ADDRSTRLEN]; + int port = 0; + struct hostent *requested_host; + struct sockaddr_in *addr_info; + struct addrinfo hints; + struct addrinfo *result, *rp; + void *addr_ptr; + + //need to check that nng is set up via dialer here as well + if(nng_dialer_id(sunneed_socket_dialer) == -1) + { + return -1; + } + + + /* + * get remote address and port from given sockaddr struct + * uses AF_INET since we assume IPv4 packets, change to AF_UNSPEC when IPv6 support is added + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + getnameinfo(addr, addrlen, host_name, NI_MAXHOST, NULL, 0, 0); + if(getaddrinfo(host_name, NULL, &hints, &result)) + { + FATAL(-1, "getaddrinfo\n"); + } + + //getaddrinfo() returns a list of socket structs, since we only have one socket per address this will give us the address the tenant requested + while(result) + { + inet_ntop(result->ai_family, result->ai_addr->sa_data, address, NI_MAXHOST); + addr_ptr = &((struct sockaddr_in*)result->ai_addr)->sin_addr; + + inet_ntop(result->ai_family, addr_ptr, address, NI_MAXHOST); + + + result = result->ai_next; + } + + addr_info = (struct sockaddr_in*) addr; + port = htons(addr_info->sin_port); + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT; + + ConnectRequest conn = CONNECT_REQUEST__INIT; + conn.port = port; + conn.address = address; + conn.addrlen = sizeof(address); + conn.sockfd = sockfd; + + req.connect = &conn; + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if(resp == NULL) + { + FATAL(-1, "connect response was null\n"); + return -1; + } + + sunneed_response__free_unpacked(resp, NULL); + + return 1; +} + +ssize_t +sunneed_client_remote_send(int sockfd, const void *data, size_t len, int flags) +{ + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_SEND; + + SendRequest send_req = SEND_REQUEST__INIT; + send_req.sockfd = sockfd; + send_req.data.data = malloc(len); + + if(!(send_req.data.data)) + { + printf("failed to malloc request data\n"); + return -1; + } + + strncpy(send_req.data.data, data, len); + send_req.data.len = len; + + req.send = &send_req; + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if(resp == NULL) + { + FATAL(-1, "send response was null\n"); + return -1; + } + free(send_req.data.data); + sunneed_response__free_unpacked(resp, NULL); + + return 0; +} + +int +sunneed_client_disconnect(void) { + // TODO Check socket opened. + + SunneedRequest req = SUNNEED_REQUEST__INIT; + req.message_type_case = SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT; + UnregisterClientRequest unregister_req = UNREGISTER_CLIENT_REQUEST__INIT; + req.unregister_client = &unregister_req; + send_request(&req); + + SunneedResponse *resp = receive_response(SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC); + if (resp == NULL) { + // TODO Handle + FATAL(-1, "Disconnect response was NULL\n"); + } + sunneed_response__free_unpacked(resp, NULL); + + client_printf("Unregistered.\n"); + return 0; +} + +void +sunneed_client_debug_print_locked_path_table(void) { + client_printf("locked files: [\n"); + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (locked_paths[i].path != NULL) + client_printf(" FD %d : '%s'\n", locked_paths[i].fd, locked_paths[i].path); + } + client_printf("]\n"); +} diff --git a/src/client/sunneed_client.h b/src/client/sunneed_client.h index 6704795..3444c93 100644 --- a/src/client/sunneed_client.h +++ b/src/client/sunneed_client.h @@ -1,46 +1,67 @@ -#include "../protobuf/c/device.pb-c.h" - -#include "../protobuf/c/server.pb-c.h" -#include "../shared/sunneed_ipc.h" -#include "../shared/sunneed_files.h" - -#include - -#include -#include -#include - -#define FATAL(CODE, FMT, ...) \ - { \ - fprintf(stderr, "fatal (%d): " FMT "\n", CODE, ##__VA_ARGS__); \ - exit(CODE); \ - } - -#define client_printf(FMT, ...) \ - printf("\e[38;5;240mclient:\e[0m " FMT, ##__VA_ARGS__) - -typedef unsigned int sunneed_device_handle_t; - -int -sunneed_client_init(const char *name); - -char * -sunneed_client_fetch_locked_file_path(const char *pathname, int flags, int mode); - -int -sunneed_client_check_locked_file(const char *pathname); - -bool -sunneed_client_fd_is_locked(int fd); - -ssize_t -sunneed_client_remote_write(int fd, const void *data, size_t n_bytes); - -int -sunneed_client_disconnect(void); - -int -sunneed_client_on_locked_path_open(int i, char *pathname, int fd); - -void -sunneed_client_debug_print_locked_path_table(void); +#include "../protobuf/c/device.pb-c.h" + +#include "../protobuf/c/server.pb-c.h" +#include "../shared/sunneed_ipc.h" +#include "../shared/sunneed_files.h" + +#include +#include +#include +#include +#include + + +#include +#include +#include + +#define FATAL(CODE, FMT, ...) \ + { \ + fprintf(stderr, "fatal (%d): " FMT "\n", CODE, ##__VA_ARGS__); \ + exit(CODE); \ + } + +#define client_printf(FMT, ...) \ + printf("\e[38;5;240mclient:\e[0m " FMT, ##__VA_ARGS__) + + + +typedef unsigned int sunneed_device_handle_t; + +int +sunneed_client_init(const char *name); + +char * +sunneed_client_fetch_locked_file_path(const char *pathname, int flags); + +int +sunneed_client_check_locked_file(const char *pathname); + +bool +sunneed_client_fd_is_locked(int fd); + +ssize_t +sunneed_client_remote_write(int fd, const void *data, size_t n_bytes); + +int +sunneed_client_disconnect(void); + +int +sunneed_client_on_locked_path_open(int i, char *pathname, int fd); + +void +sunneed_client_debug_print_locked_path_table(void); + +int +sunneed_client_socket(int domain, int type, int protocol); + +int +sunneed_client_is_dummysocket(int sockfd); + +int +sunneed_client_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +ssize_t +sunneed_client_remote_send(int sockfd, const void *data, size_t n_bytes, int flags); + + diff --git a/src/device/camera.c b/src/device/camera.c index 7d20c0f..ddac51f 100644 --- a/src/device/camera.c +++ b/src/device/camera.c @@ -1,27 +1,27 @@ -// An example device that implements the bare minimum. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = { "/tmp/camera" } -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; - -// TODO Device type. +// An example device that implements the bare minimum. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = { "/tmp/camera" } +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; + +// TODO Device type. diff --git a/src/device/stepperMotor.c b/src/device/stepperMotor.c index a3ac94b..daef753 100644 --- a/src/device/stepperMotor.c +++ b/src/device/stepperMotor.c @@ -1,27 +1,27 @@ -// An example device that implements the bare minimum. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = { "/tmp/stepper" } -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; - -// TODO Device type. +// An example device that implements the bare minimum. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = { "/tmp/stepper" } +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; + +// TODO Device type. diff --git a/src/device/stepper_motor.c b/src/device/stepper_motor.c index d4d9c90..f421a1e 100644 --- a/src/device/stepper_motor.c +++ b/src/device/stepper_motor.c @@ -1,23 +1,23 @@ -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = {"/dev/stepper"} -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = {"/dev/stepper"} +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; diff --git a/src/device/test_broken.c b/src/device/test_broken.c index 88e483b..4dcf422 100644 --- a/src/device/test_broken.c +++ b/src/device/test_broken.c @@ -1,15 +1,15 @@ -// An example of a broken device -- it does not define a `get_device_type_data`. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -unsigned int device_flags = SUNNEED_DEVICE_FLAG_SILENT_FAIL; +// An example of a broken device -- it does not define a `get_device_type_data`. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +unsigned int device_flags = SUNNEED_DEVICE_FLAG_SILENT_FAIL; diff --git a/src/device/test_file_lock.c b/src/device/test_file_lock.c index 3bfc115..92f7b0b 100644 --- a/src/device/test_file_lock.c +++ b/src/device/test_file_lock.c @@ -1,27 +1,27 @@ -// An example device that implements the bare minimum. - -#include "../shared/sunneed_device_interface.h" -#include "../shared/sunneed_testing.h" -#include -#include - -struct sunneed_device_type_file_lock data = { - .len = 1, - .paths = { TEST_FILE_LOCK_FILE_PATH } -}; - -int -init(void) { - return 0; -} - -enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; - -void * -get_device_type_data(void) { - return &data; -} - -unsigned int device_flags = 0; - -// TODO Device type. +// An example device that implements the bare minimum. + +#include "../shared/sunneed_device_interface.h" +#include "../shared/sunneed_testing.h" +#include +#include + +struct sunneed_device_type_file_lock data = { + .len = 1, + .paths = { TEST_FILE_LOCK_FILE_PATH } +}; + +int +init(void) { + return 0; +} + +enum sunneed_device_type device_type_kind = DEVICE_TYPE_FILE_LOCK; + +void * +get_device_type_data(void) { + return &data; +} + +unsigned int device_flags = 0; + +// TODO Device type. diff --git a/src/log.h b/src/log.h index f56495d..52ba438 100644 --- a/src/log.h +++ b/src/log.h @@ -1,52 +1,52 @@ -#ifndef _LOG_H_ -#define _LOG_H_ - -/* - * Defines a variety of macros to be used for logging information. - * Assign a FILE* to `logfile` during runtime to redirect the log to that file. - */ - -#include -#include - -FILE *logfile, *logfile_pwr; - -#define LOGL_DEBUG "D\e[38;5;240m" -#define LOGL_INFO "I" -#define LOGL_WARN "W\e[0;33m" -#define LOGL_ERROR "E\e[0;31m" - - -#define LOG_PWR(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = logfile_pwr; \ - if (logfile_pwr) { \ - _logfile = fopen("sunneed_pwr_log.txt", "w+"); \ - } \ - time_t _now = time(NULL); \ - struct tm *_time = localtime(&_now); \ - char _time_str[21]; \ - strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ - fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ - fflush(_logfile); \ - } - -#define LOG(LEVEL, MESSAGE, ...) \ - { \ - FILE *_logfile = logfile; \ - if (!logfile) { \ - _logfile = stdout; \ - } \ - time_t _now = time(NULL); \ - struct tm *_time = localtime(&_now); \ - char _time_str[21]; \ - strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ - fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ - } - -#define LOG_D(MESSAGE, ...) LOG(LOGL_DEBUG, MESSAGE, ##__VA_ARGS__); -#define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); -#define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); -#define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); -#define LOG_P(MESSAGE, ...) LOG_PWR(LOGL_INFO, MESSAGE, ##__VA_ARGS__); -#endif +#ifndef _LOG_H_ +#define _LOG_H_ + +/* + * Defines a variety of macros to be used for logging information. + * Assign a FILE* to `logfile` during runtime to redirect the log to that file. + */ + +#include +#include + +FILE *logfile, *logfile_pwr; + +#define LOGL_DEBUG "D\e[38;5;240m" +#define LOGL_INFO "I" +#define LOGL_WARN "W\e[0;33m" +#define LOGL_ERROR "E\e[0;31m" + + +#define LOG_PWR_EVENT(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = logfile_pwr; \ + if (logfile_pwr) { \ + _logfile = fopen("sunneed_pwr_log.txt", "w+"); \ + } \ + time_t _now = time(NULL); \ + struct tm *_time = localtime(&_now); \ + char _time_str[21]; \ + strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ + fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ + fflush(_logfile); \ + } + +#define LOG(LEVEL, MESSAGE, ...) \ + { \ + FILE *_logfile = logfile; \ + if (!logfile) { \ + _logfile = stdout; \ + } \ + time_t _now = time(NULL); \ + struct tm *_time = localtime(&_now); \ + char _time_str[21]; \ + strftime(_time_str, 21, "%Y-%m-%d %H:%M:%S", _time); \ + fprintf(_logfile, "%s[%s] " MESSAGE "\e[0m\n", LEVEL, _time_str, ##__VA_ARGS__); \ + } + +#define LOG_D(MESSAGE, ...) LOG(LOGL_DEBUG, MESSAGE, ##__VA_ARGS__); +#define LOG_I(MESSAGE, ...) LOG(LOGL_INFO, MESSAGE, ##__VA_ARGS__); +#define LOG_W(MESSAGE, ...) LOG(LOGL_WARN, MESSAGE, ##__VA_ARGS__); +#define LOG_E(MESSAGE, ...) LOG(LOGL_ERROR, MESSAGE, ##__VA_ARGS__); +#define LOG_P(MESSAGE, ...) LOG_PWR_EVENT(LOGL_INFO, MESSAGE, ##__VA_ARGS__); +#endif diff --git a/src/overlay/sunneed_overlay.c b/src/overlay/sunneed_overlay.c index 75de883..03a5fbb 100644 --- a/src/overlay/sunneed_overlay.c +++ b/src/overlay/sunneed_overlay.c @@ -1,55 +1,120 @@ -#include "sunneed_overlay.h" - -void -on_load() { - sunneed_client_init("TODO"); - - printf("Overlay: Client init\n"); -} - -void -on_unload() { - sunneed_client_disconnect(); -} - -int -open(const char *pathname, int flags, mode_t mode) { - printf("Opening file %s\n", pathname); - - int locked = sunneed_client_check_locked_file(pathname); - if (locked < 0) { - printf("'%s' is not locked; opening normally\n", pathname); - } else { - printf("'%s' is locked; opening via dummy\n", pathname); - char *dummy_path = sunneed_client_fetch_locked_file_path(pathname, flags, mode); - pathname = dummy_path; - } - - int fd; - SUPER(fd, open, int, (pathname, flags, mode), const char *, int, mode_t); - - // TODO Handle errors from open - - sunneed_client_on_locked_path_open(locked, (char *)pathname, fd); - - sunneed_client_debug_print_locked_path_table(); - - return fd; -} - -ssize_t -write(int fd, const void *buf, size_t count) { - printf("Overlay write %d\n", fd); - int ret; - - if (!sunneed_client_fd_is_locked(fd)) { - // Perform the write as normal. - SUPER(ret, write, int, (fd, buf, count), int, const void *, size_t); - return ret; - } - - // Ask sunneed to do the write for us. - sunneed_client_remote_write(fd, buf, count); - - return 0; -} +#include "sunneed_overlay.h" + +void +on_load() { + + sunneed_client_init("TODO"); + printf("Overlay: Client init\n"); + +} + +void +on_unload() { + sunneed_client_disconnect(); +} + +int +open(const char *pathname, int flags, mode_t mode) { + printf("Opening file %s\n", pathname); + + int locked = sunneed_client_check_locked_file(pathname); + if (locked < 0) { + printf("'%s' is not locked; opening normally\n", pathname); + } else { + printf("'%s' is locked; opening via dummy\n", pathname); + char *dummy_path = sunneed_client_fetch_locked_file_path(pathname, flags); + pathname = dummy_path; + } + + int fd; + SUPER(fd, open, int, (pathname, flags, mode), const char *, int, mode_t); + + // TODO Handle errors from open + + if(locked > 0) sunneed_client_on_locked_path_open(locked, (char *)pathname, fd); + + sunneed_client_debug_print_locked_path_table(); + + + return fd; +} + +ssize_t +write(int fd, const void *buf, size_t count) { + printf("Overlay write %d\n", fd); + int ret; + + if (!sunneed_client_fd_is_locked(fd)) { + // Perform the write as normal. + SUPER(ret, write, int, (fd, buf, count), int, const void *, size_t); + return ret; + } + + // Ask sunneed to do the write for us. + sunneed_client_remote_write(fd, buf, count); + + return 0; +} + +int +socket(int domain, int type, int protocol) +{ + + int ret = sunneed_client_socket(domain, type, protocol); + + if(ret == -1) + { + //nng socket is not open (caught in sunneed_client_socket), call SUPER + SUPER(ret, socket, int, (domain, type, protocol), int, int, int); + return ret; + } + + return ret; + +} + +int +connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + + int ret; + + if(sunneed_client_is_dummysocket(sockfd)) + { + ret = sunneed_client_connect(sockfd, addr, addrlen); + if(ret == -1) + { + //NNG dialer wasn't set up or the socket type was wrong + SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); + return ret; + } + + return ret; + }else if(sockfd){ + //non-sunneed socket (maybe dont need this? Need to think of a case where we'd want to allow a connection to a non sunneed socket) + SUPER(ret, connect, int, (sockfd, addr, addrlen), int, const struct sockaddr *, socklen_t); + return ret; + }else{ + perror("overlay connect: bad socketfd and not a dummy socket\n"); + return 0; + } + +} + +ssize_t +send(int sockfd, const void *buf, size_t len, int flags) +{ + if(sunneed_client_is_dummysocket(sockfd)) + { + return sunneed_client_remote_send(sockfd, buf, len, flags); + }else if(sockfd){ + int ret; + SUPER(ret, send, int, (sockfd, buf, len, flags), int, const void *, size_t, int); + return ret; + } + return 0; +} + + + + diff --git a/src/overlay/sunneed_overlay.h b/src/overlay/sunneed_overlay.h index 0d795ae..95ba2b7 100644 --- a/src/overlay/sunneed_overlay.h +++ b/src/overlay/sunneed_overlay.h @@ -1,51 +1,66 @@ -// Symbols to overlay on top of existing programs via LD_PRELOAD. - -#define _GNU_SOURCE - -#include -#include -#include -#include - -#include "../client/sunneed_client.h" -#include "../shared/sunneed_device_type.h" - -/** - * Call the overridden function from within an overlay function. This is done via the RTLD_NEXT flag, which instructs - * dlsym to find the next match for the symbol in libraries loaded after the current one. - * - * Arguments: - * RETVAR is a variable to assign the return value of the function to. - * NAME is the name of the function (not in a string). - * RETURN_TYPE is the type signature for the return value of the function. - * ARGS should be the argument names passed to the current overlay function, wrapped in parentheses. For example, - * calling the base `open(const char *path, int flags)`, the ARGS should be in the form `(path, flags)`. - * Finally, the varargs is the type signatures of the arguments, comma-separated. These should end up lining up with - * the types of the args passed as ARGS. - * - * To illustrate, let's return to our example using `open`. We'll declare an `int` to hold the return value, and then - * invoke `SUPER`. - * - * int ret; - * SUPER(ret, open, int, (path, flags), const char *, int); - * - * TODO The potential pitfall here is that we perform a `dlsym` lookup every single time we use `SUPER`. We should - * either implement some caching system, or make sure that `dlsym` is caching these values under the hood. - */ -#define SUPER(RETVAR, NAME, RETURN_TYPE, ARGS, ...) { \ - RETURN_TYPE (*_base)(__VA_ARGS__); \ - _base = dlsym(RTLD_NEXT, # NAME); \ - RETVAR = (*_base) ARGS; \ -} - -// This will be run as soon as the library is linked (program start). -void __attribute__((constructor)) on_load(); - -// ...and likewise, this one when unlinked (program end). -void __attribute__((destructor)) on_unload(); - -int -open(const char *pathname, int flags, mode_t mode); - -ssize_t -write(int fd, const void *buf, size_t count); +// Symbols to overlay on top of existing programs via LD_PRELOAD. + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "../client/sunneed_client.h" +#include "../shared/sunneed_device_type.h" + +/** + * Call the overridden function from within an overlay function. This is done via the RTLD_NEXT flag, which instructs + * dlsym to find the next match for the symbol in libraries loaded after the current one. + * + * Arguments: + * RETVAR is a variable to assign the return value of the function to. + * NAME is the name of the function (not in a string). + * RETURN_TYPE is the type signature for the return value of the function. + * ARGS should be the argument names passed to the current overlay function, wrapped in parentheses. For example, + * calling the base `open(const char *path, int flags)`, the ARGS should be in the form `(path, flags)`. + * Finally, the varargs is the type signatures of the arguments, comma-separated. These should end up lining up with + * the types of the args passed as ARGS. + * + * To illustrate, let's return to our example using `open`. We'll declare an `int` to hold the return value, and then + * invoke `SUPER`. + * + * int ret; + * SUPER(ret, open, int, (path, flags), const char *, int); + * + * TODO The potential pitfall here is that we perform a `dlsym` lookup every single time we use `SUPER`. We should + * either implement some caching system, or make sure that `dlsym` is caching these values under the hood. + */ +#define SUPER(RETVAR, NAME, RETURN_TYPE, ARGS, ...) { \ + RETURN_TYPE (*_base)(__VA_ARGS__); \ + _base = dlsym(RTLD_NEXT, # NAME); \ + RETVAR = (*_base) ARGS; \ +} + + +// This will be run as soon as the library is linked (program start). +void __attribute__((constructor)) on_load(); + +// ...and likewise, this one when unlinked (program end). +void __attribute__((destructor)) on_unload(); + +int +open(const char *pathname, int flags, mode_t mode); + +ssize_t +write(int fd, const void *buf, size_t count); + +int +socket(int domain, int type, int protocol); + +int +connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +ssize_t +send(int sockfd, const void *buf, size_t len, int flags); + + diff --git a/src/pip/bq27441.c b/src/pip/bq27441.c index fd6327a..88f73d0 100644 --- a/src/pip/bq27441.c +++ b/src/pip/bq27441.c @@ -1,13 +1,18 @@ -#include "../shared/sunneed_pip_interface.h" -#include "../../ext/libbq27441/bq27441.c" - -struct sunneed_pip -pip_info() { - return (struct sunneed_pip){"bq27441", 1000, 50}; -} - -unsigned int -present_power() { - bq27441_init(1); - return bq27441_nominal_avail_cap(); -} +#include "../shared/sunneed_pip_interface.h" +#include "../../ext/libbq27441/bq27441.c" + +struct sunneed_pip +pip_info() { + return (struct sunneed_pip){"bq27441", 1000, 50}; +} + +signed int +present_power() { + +#ifdef log_pwr + bq27441_init(1); + return bq27441_average_power(); +#endif + + return 0; +} diff --git a/src/protobuf/Makefile b/src/protobuf/Makefile index d05c606..587c252 100644 --- a/src/protobuf/Makefile +++ b/src/protobuf/Makefile @@ -1,7 +1,7 @@ -PROTOC ?= protoc-c - -protobuf_sources = $(wildcard *.proto) -protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) - -$(protobuf_out_files): $(protobuf_sources) - $(PROTOC) --c_out=. $(patsubst $(protobuf_dir)/%,./%,$^) +PROTOC ?= protoc-c + +protobuf_sources = $(wildcard *.proto) +protobuf_out_files = $(foreach src,$(protobuf_sources),$(subst !!!, ,$(join $(src:.proto=.pb-c.c!!!),$(src:.proto=.pb-c.h)))) + +$(protobuf_out_files): $(protobuf_sources) + $(PROTOC) --c_out=. $(patsubst $(protobuf_dir)/%,./%,$^) diff --git a/src/protobuf/device.proto b/src/protobuf/device.proto index ac4efef..9a0c18f 100644 --- a/src/protobuf/device.proto +++ b/src/protobuf/device.proto @@ -1,5 +1,5 @@ -syntax = "proto3"; - -message DeviceRandomResponse { - int32 val = 1; -} +syntax = "proto3"; + +message DeviceRandomResponse { + int32 val = 1; +} diff --git a/src/protobuf/server.proto b/src/protobuf/server.proto index a66baa2..6369ac1 100644 --- a/src/protobuf/server.proto +++ b/src/protobuf/server.proto @@ -1,79 +1,108 @@ -syntax = "proto3"; - -// {{{1 Request - -// Generic container type for messages sent from client to server. -message SunneedRequest { - oneof message_type { - RegisterClientRequest register_client = 1; - UnregisterClientRequest unregister_client = 2; - OpenFileRequest open_file = 3; - WriteRequest write = 4; - } -} - -message RegisterClientRequest { - string name = 1; -} - -message UnregisterClientRequest {} - -message OpenFileRequest { - string path = 1; - int32 flags = 2; - int32 mode = 3; -} - -message WriteRequest { - string dummy_path = 1; - bytes data = 2; -} - -// {{{1 Response - -// Generic container type for messages sent from server to client. -message SunneedResponse { - int32 status = 1; - oneof message_type { - GenericResponse generic = 2; - DeviceResponse device = 3; - OpenFileResponse open_file = 4; - RegisterClientResponse register_client = 5; - WriteResponse call_write = 6; - } -} - -message RegisterClientResponse { - repeated string locked_paths = 1; -} - -message GenericResponse { - bytes data = 1; -} - -message OpenFileResponse { - string path = 1; -} - -message WriteResponse { - // Zero if no error. - int32 errno_value = 1; - - // This is only set from values of type `size_t`, so don't worry about data loss when converting it back from - // uint64. - uint64 bytes_written = 2; -} - -import "device.proto"; - -// Dear whoever is reading this, I hope you like the word "Device"... -message DeviceResponse { - - oneof message_type { - DeviceRandomResponse random = 1; - } -}; - -// }}} - -// vim: fdm=marker : +syntax = "proto3"; + +// {{{1 Request + +// Generic container type for messages sent from client to server. +message SunneedRequest { + oneof message_type { + RegisterClientRequest register_client = 1; + UnregisterClientRequest unregister_client = 2; + OpenFileRequest open_file = 3; + WriteRequest write = 4; + SocketRequest socket = 5; + ConnectRequest connect = 6; + SendRequest send = 7; + } +} + +message RegisterClientRequest { + string name = 1; +} + +message UnregisterClientRequest {} + +message OpenFileRequest { + string path = 1; + int32 flags = 2; +} + +message WriteRequest { + string dummy_path = 1; + bytes data = 2; +} + +message SocketRequest { + int32 domain = 1; + int32 type = 2; + int32 protocol = 3; +} + +message ConnectRequest { + int32 port = 1; + string address = 2; + int32 addrlen = 3; + int32 sockfd = 4; +} + +message SendRequest { + int32 sockfd = 1; + bytes data = 2; + int32 flags = 3; +} + + + +// {{{1 Response + +// Generic container type for messages sent from server to client. +message SunneedResponse { + int32 status = 1; + oneof message_type { + GenericResponse generic = 2; + DeviceResponse device = 3; + OpenFileResponse open_file = 4; + RegisterClientResponse register_client = 5; + WriteResponse call_write = 6; + SocketResponse socket = 7; + } +} + +message RegisterClientResponse { + repeated string locked_paths = 1; +} + +message GenericResponse { + bytes data = 1; +} + +message OpenFileResponse { + string path = 1; +} + +message WriteResponse { + // Zero if no error. + int32 errno_value = 1; + + // This is only set from values of type `size_t`, so don't worry about data loss when converting it back from + // uint64. + uint64 bytes_written = 2; +} + +message SocketResponse { + //fake socket fd given back by sunneed + int32 dummy_sockfd = 1; +} + +import "device.proto"; + +// Dear whoever is reading this, I hope you like the word "Device"... +message DeviceResponse { + + oneof message_type { + DeviceRandomResponse random = 1; + } +}; + +// }}} + +// vim: fdm=marker : diff --git a/src/shared/sunneed_device_interface.h b/src/shared/sunneed_device_interface.h index c2c4a92..fdeb3ca 100644 --- a/src/shared/sunneed_device_interface.h +++ b/src/shared/sunneed_device_interface.h @@ -1,28 +1,28 @@ -#ifndef _SUNNEED_DEVICE_INTERFACE_H_ -#define _SUNNEED_DEVICE_INTERFACE_H_ - -#include "sunneed_device_type.h" -#include "../protobuf/c/device.pb-c.h" - -/** - * Runs once when module is loaded. - * If the return value is greater than zero, loading will be aborted with an error. - * If the return value is less than zero, loading will be silently aborted. - */ -extern int -init(void); - -/** - * The type of the implementing device. - * Under the hood, defines the union member of `device_type_data` to write to. - */ -extern enum sunneed_device_type device_type_kind; - -/** Should return a pointer to a struct, the type of which corresponds with the `device_type_kind` of this. */ -extern void * -get_device_type_data(void); - -/** A set of bitflags as defined in `sunneed_device_interface.h`. */ -extern unsigned int device_flags; - -#endif +#ifndef _SUNNEED_DEVICE_INTERFACE_H_ +#define _SUNNEED_DEVICE_INTERFACE_H_ + +#include "sunneed_device_type.h" +#include "../protobuf/c/device.pb-c.h" + +/** + * Runs once when module is loaded. + * If the return value is greater than zero, loading will be aborted with an error. + * If the return value is less than zero, loading will be silently aborted. + */ +extern int +init(void); + +/** + * The type of the implementing device. + * Under the hood, defines the union member of `device_type_data` to write to. + */ +extern enum sunneed_device_type device_type_kind; + +/** Should return a pointer to a struct, the type of which corresponds with the `device_type_kind` of this. */ +extern void * +get_device_type_data(void); + +/** A set of bitflags as defined in `sunneed_device_interface.h`. */ +extern unsigned int device_flags; + +#endif diff --git a/src/shared/sunneed_device_type.h b/src/shared/sunneed_device_type.h index 5f6cfcc..74dab6f 100644 --- a/src/shared/sunneed_device_type.h +++ b/src/shared/sunneed_device_type.h @@ -1,16 +1,16 @@ -// For testing purposes. -#define CAMERA_PATH "camera" - -#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) - -enum sunneed_device_type { - DEVICE_TYPE_FILE_LOCK = 1 -}; - -struct sunneed_device_type_file_lock { - /** The number of elements in `paths`. */ - unsigned int len; - - /** An array of strings; these are the paths to lock. */ - char *paths[]; -}; +// For testing purposes. +#define CAMERA_PATH "camera" + +#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) + +enum sunneed_device_type { + DEVICE_TYPE_FILE_LOCK = 1 +}; + +struct sunneed_device_type_file_lock { + /** The number of elements in `paths`. */ + unsigned int len; + + /** An array of strings; these are the paths to lock. */ + char *paths[]; +}; diff --git a/src/shared/sunneed_files.h b/src/shared/sunneed_files.h index 27fd05d..759fa6b 100644 --- a/src/shared/sunneed_files.h +++ b/src/shared/sunneed_files.h @@ -1,6 +1,7 @@ -#ifndef _SUNNEED_FILES_H_ -#define _SUNNEED_FILES_H_ - -#define MAX_LOCKED_FILES 1024 - -#endif +#ifndef _SUNNEED_FILES_H_ +#define _SUNNEED_FILES_H_ + +#define MAX_LOCKED_FILES 1024 +#define MAX_TENANT_SOCKETS 128 + +#endif diff --git a/src/shared/sunneed_ipc.h b/src/shared/sunneed_ipc.h index 2c5105f..1987b6c 100644 --- a/src/shared/sunneed_ipc.h +++ b/src/shared/sunneed_ipc.h @@ -1,84 +1,84 @@ -#ifndef _SUNNEED_IPC_H_ -#define _SUNNEED_IPC_H_ - -#include -#include - -#define SUNNEED_LISTENER_URL "ipc:///tmp/sunneed.ipc" - -void (*_sunneed_nng_error_func)(const char *nng_call_name, int rv); - -/** - * Sets the function to be called when an error is encountered during a SUNNEED_NNG_TRY_* macro. - * This function takes two arguments: a string representing the name of the function, and an integer containing the - * return value of the failed NNG function call. - * Usually this function should somehow call `nng_strerror` to report NNG's description of the error. - */ -#define SUNNEED_NNG_SET_ERROR_REPORT_FUNC(FUNCNAME) _sunneed_nng_error_func = FUNCNAME; - -/** Try to perform an NNG function, expanding to returning 1 if the NNG call fails. */ -#define SUNNEED_NNG_TRY_RET(CALL, ERROR_COND, ARGS...) \ - { \ - int _rv; \ - SUNNEED_NNG_TRY_RET_SET(CALL, _rv, ERROR_COND, ##ARGS); \ - } - -/** - * Try to perform an NNG function, expanding to returning 1 if the NNG call fails. - * In the process of calling the function, the variable TARGET will be assigned its return value. - */ -#define SUNNEED_NNG_TRY_RET_SET(CALL, TARGET, ERROR_COND, ARGS...) \ - { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, return 1, ##ARGS); } - -/** Try to perform an NNG function. */ -#define SUNNEED_NNG_TRY(CALL, ERROR_COND, ARGS...) \ - { \ - int _rv; \ - SUNNEED_NNG_TRY_SET(CALL, _rv, ERROR_COND, ##ARGS); \ - } - -/** - * Try to perform an NNG function. - * In the process of calling the function, the variable TARGET will be assigned its return value. - */ -#define SUNNEED_NNG_TRY_SET(CALL, TARGET, ERROR_COND, ARGS...) \ - { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, , ##ARGS); } - -#ifdef SUNNEED_BUILD_TYPE_PRODUCTION -#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ - { \ - if ((TARGET = CALL(ARGS)) ERROR_COND) { \ - _sunneed_nng_error_func(#CALL, TARGET); \ - ON_ERROR; \ - } \ - } -#else -#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ - { \ - if ((TARGET = CALL(ARGS)) ERROR_COND) { \ - if (!_sunneed_nng_error_func) { \ - fprintf(stderr, \ - "An NNG error was encountered but there is no error handler.\n" \ - "Error details: " #CALL ": %s\n", \ - nng_strerror(TARGET)); \ - exit(1); \ - } \ - _sunneed_nng_error_func(#CALL, TARGET); \ - ON_ERROR; \ - } \ - } -#endif - -#define SUNNEED_NNG_MSG_LEN_FIX(LEN_VAR) \ - if ((LEN_VAR / 2) % 2 == 1) \ - LEN_VAR++; - - -/**** - * IPC helpers - ***/ - -int -sunneed_ipc_register_self(void); - -#endif +#ifndef _SUNNEED_IPC_H_ +#define _SUNNEED_IPC_H_ + +#include +#include + +#define SUNNEED_LISTENER_URL "ipc:///tmp/sunneed.ipc" + +void (*_sunneed_nng_error_func)(const char *nng_call_name, int rv); + +/** + * Sets the function to be called when an error is encountered during a SUNNEED_NNG_TRY_* macro. + * This function takes two arguments: a string representing the name of the function, and an integer containing the + * return value of the failed NNG function call. + * Usually this function should somehow call `nng_strerror` to report NNG's description of the error. + */ +#define SUNNEED_NNG_SET_ERROR_REPORT_FUNC(FUNCNAME) _sunneed_nng_error_func = FUNCNAME; + +/** Try to perform an NNG function, expanding to returning 1 if the NNG call fails. */ +#define SUNNEED_NNG_TRY_RET(CALL, ERROR_COND, ARGS...) \ + { \ + int _rv; \ + SUNNEED_NNG_TRY_RET_SET(CALL, _rv, ERROR_COND, ##ARGS); \ + } + +/** + * Try to perform an NNG function, expanding to returning 1 if the NNG call fails. + * In the process of calling the function, the variable TARGET will be assigned its return value. + */ +#define SUNNEED_NNG_TRY_RET_SET(CALL, TARGET, ERROR_COND, ARGS...) \ + { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, return 1, ##ARGS); } + +/** Try to perform an NNG function. */ +#define SUNNEED_NNG_TRY(CALL, ERROR_COND, ARGS...) \ + { \ + int _rv; \ + SUNNEED_NNG_TRY_SET(CALL, _rv, ERROR_COND, ##ARGS); \ + } + +/** + * Try to perform an NNG function. + * In the process of calling the function, the variable TARGET will be assigned its return value. + */ +#define SUNNEED_NNG_TRY_SET(CALL, TARGET, ERROR_COND, ARGS...) \ + { _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, , ##ARGS); } + +#ifdef SUNNEED_BUILD_TYPE_PRODUCTION +#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ + { \ + if ((TARGET = CALL(ARGS)) ERROR_COND) { \ + _sunneed_nng_error_func(#CALL, TARGET); \ + ON_ERROR; \ + } \ + } +#else +#define _SUNNEED_NNG_TRY_CONTAINER(CALL, TARGET, ERROR_COND, ON_ERROR, ARGS...) \ + { \ + if ((TARGET = CALL(ARGS)) ERROR_COND) { \ + if (!_sunneed_nng_error_func) { \ + fprintf(stderr, \ + "An NNG error was encountered but there is no error handler.\n" \ + "Error details: " #CALL ": %s\n", \ + nng_strerror(TARGET)); \ + exit(1); \ + } \ + _sunneed_nng_error_func(#CALL, TARGET); \ + ON_ERROR; \ + } \ + } +#endif + +#define SUNNEED_NNG_MSG_LEN_FIX(LEN_VAR) \ + if ((LEN_VAR / 2) % 2 == 1) \ + LEN_VAR++; + + +/**** + * IPC helpers + ***/ + +int +sunneed_ipc_register_self(void); + +#endif diff --git a/src/shared/sunneed_pip_interface.h b/src/shared/sunneed_pip_interface.h index 1c90354..ae2a04c 100644 --- a/src/shared/sunneed_pip_interface.h +++ b/src/shared/sunneed_pip_interface.h @@ -1,35 +1,36 @@ -#ifndef _SUNNEED_PIP_H_ -#define _SUNNEED_PIP_H_ - -/* - * Describes the interface for a power information provider (a PIP). - * A PIP is what tells sunneed statistics about the battery, such as power and - * electrical current usage. - * This API is unstable and new things will definitely be added as we progress - * in sunneed's development. - */ - -struct sunneed_pip { - // A textual name to identify the PIP by. Probably won't be used in any - // actual logic. - const char *name; - - // The maximum power the system can have. Units are arbitrary, as this - // value will only be used in comparison to the present power. - unsigned int max_power; - - // The interval, in milliseconds, that the device can update its power - // statistics. This helps reduce unnecessary device accesses by - // sunneed. - // TODO: Some devices may have different updatee intervals across their - // components; should we account for that? - unsigned int update_interval; -}; - -struct sunneed_pip -pip_info(); - -unsigned int -present_power(); - -#endif +#ifndef _SUNNEED_PIP_H_ +#define _SUNNEED_PIP_H_ +#include "../../ext/libbq27441/bq27441.h" + +/* + * Describes the interface for a power information provider (a PIP). + * A PIP is what tells sunneed statistics about the battery, such as power and + * electrical current usage. + * This API is unstable and new things will definitely be added as we progress + * in sunneed's development. + */ + +struct sunneed_pip { + // A textual name to identify the PIP by. Probably won't be used in any + // actual logic. + const char *name; + + // The maximum power the system can have. Units are arbitrary, as this + // value will only be used in comparison to the present power. + unsigned int max_power; + + // The interval, in milliseconds, that the device can update its power + // statistics. This helps reduce unnecessary device accesses by + // sunneed. + // TODO: Some devices may have different updatee intervals across their + // components; should we account for that? + unsigned int update_interval; +}; + +struct sunneed_pip +pip_info(); + +signed int +present_power(); + +#endif diff --git a/src/shared/sunneed_testing.h b/src/shared/sunneed_testing.h index b1f16e5..a96a72e 100644 --- a/src/shared/sunneed_testing.h +++ b/src/shared/sunneed_testing.h @@ -1,3 +1,3 @@ -#define TEST_DEVICE_OUTPUT "TEST" - -#define TEST_FILE_LOCK_FILE_PATH "/tmp/test" +#define TEST_DEVICE_OUTPUT "TEST" + +#define TEST_FILE_LOCK_FILE_PATH "/tmp/test" diff --git a/src/sunneed.h b/src/sunneed.h index aa9c816..51c0401 100644 --- a/src/sunneed.h +++ b/src/sunneed.h @@ -1,24 +1,24 @@ -#ifndef _SUNNEED_H_ -#define _SUNNEED_H_ - -#include -#include -#include -#include - -#define APP_NAME "sunneed" - -typedef void* sunneed_worker_thread_result_t; - -#define SUNNEED_MAX_IPC_CLIENTS 512 - -// Control flow: -// When a new pipe connects, we use this struct to make a mapping of its pipe ID to a tenant. Then, when further -// requests are made, the pipe ID is used to identify a tenant to the request. -// The client will have to send some notification in order to unregister; I don't think we can tell if a pipe -// closed. -struct tenant_pipe { - struct sunneed_tenant *tenant; - nng_pipe pipe; -} tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; -#endif +#ifndef _SUNNEED_H_ +#define _SUNNEED_H_ + +#include +#include +#include +#include + +#define APP_NAME "sunneed" + +typedef void* sunneed_worker_thread_result_t; + +#define SUNNEED_MAX_IPC_CLIENTS 512 + +// Control flow: +// When a new pipe connects, we use this struct to make a mapping of its pipe ID to a tenant. Then, when further +// requests are made, the pipe ID is used to identify a tenant to the request. +// The client will have to send some notification in order to unregister; I don't think we can tell if a pipe +// closed. +struct tenant_pipe { + struct sunneed_tenant *tenant; + nng_pipe pipe; +} tenant_pipes[SUNNEED_MAX_IPC_CLIENTS]; +#endif diff --git a/src/sunneed_core.c b/src/sunneed_core.c index 9825262..0ebf9c9 100644 --- a/src/sunneed_core.c +++ b/src/sunneed_core.c @@ -1,151 +1,151 @@ -#include "sunneed_core.h" - -extern struct sunneed_device devices[MAX_DEVICES]; - -struct sunneed_pip pip; - -sunneed_worker_thread_result_t (*worker_thread_functions[])(void *) = {sunneed_proc_monitor, sunneed_quantum_worker, sunneed_stepperMotor_driver, sunneed_camera_driver, NULL}; - -void -handle_exit(void) { - LOG_I("Sunneed exiting"); - LOG_I("\tKilling stepper motor"); - kill(sunneed_stepper_driver_pid, SIGTERM); - LOG_I("\tKilling camera driver"); - kill(sunneed_camera_driver_pid, SIGTERM); -} - -#ifdef TESTING - -#include "sunneed_runtime_test_collection.h" - -int (*runtime_tests[])(void) = RUNTIME_TESTS; - -static unsigned int -testcase_count(void) { - unsigned int testcases = 0; - for (int (**cur)(void) = runtime_tests; *cur != NULL; cur++) - testcases++; - // TODO Why minus one... - return testcases - 1; -} - -static int -run_testcase(unsigned int testcase) { - if (testcase >= testcase_count()) { - LOG_E("Cannot run testcase #%d because it does not exist", testcase); - return 1; - } - - int ret = runtime_tests[testcase](); - if (ret != 0) { - fprintf(stderr, "Failure: %s (%d)\n", sunneed_runtime_test_error, ret); - return ret; - } - - return 0; -} -#endif - -static int -spawn_worker_threads(void) { - int ret; - int worker_thread_count = 0; - for (void *(**cur)(void *) = worker_thread_functions; *cur != NULL; cur++) - worker_thread_count++; - - pthread_t worker_threads[worker_thread_count]; - - LOG_I("Launching %d worker threads", worker_thread_count); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - for (int i = 0; i < worker_thread_count; i++) { - if ((ret = pthread_create(&worker_threads[i], &attr, worker_thread_functions[i], NULL)) != 0) { - LOG_E("Failed to launch worker thread %d (error %d)", i, ret); - return 1; - }; - } - - return 0; -} - - -void -sunneed_init(void) { - atexit(handle_exit); - pip = pip_info(); -} - -int -main(int argc, char *argv[]) { - int opt; - extern int optopt; - -#ifdef LOG_PWR - logfile_pwr = fopen("sunneed_pwr_log.txt", "w+"); -#endif - -#ifdef TESTING - const char *optstring = ":ht:c"; -#else - const char *optstring = ":h"; -#endif - // TODO Long-form getopts. - while ((opt = getopt(argc, argv, optstring)) != -1) { - switch (opt) { - case 'h': - printf(HELP_TEXT, argv[0]); - exit(0); -#ifdef TESTING - case 't': ; - logfile = fopen("sunneed_log.txt", "w+"); - int testcase = strtol(optarg, NULL, 10); - if (errno) { - LOG_E("Failed to parse testcase index: %s", strerror(errno)); - return 1; - } - return run_testcase(testcase); - case 'c': - printf("%d\n", testcase_count()); - exit(0); -#endif - case '?': - fprintf(stderr, "%s: illegal option -%c\n", APP_NAME, optopt); - exit(1); - case ':': - fprintf(stderr, "%s: expected argument for option -%c\n", APP_NAME, optopt); - exit(1); - } - } - - int ret = 0; - - LOG_I("sunneed is initializing..."); - - sunneed_init(); - - LOG_I("Acquired PIP: %s", pip.name); - - LOG_I("Loading devices..."); - if ((ret = sunneed_load_devices(devices)) != 0) { - LOG_E("Failed to load devices"); - ret = 1; - goto end; - } - - if ((ret = spawn_worker_threads()) != 0) { - LOG_E("Error occurred while spawning worker threads"); - ret = 1; - goto end; - } - if ((ret = sunneed_listen()) != 0) { - LOG_E("sunneed listener encountered a fatal error. Exiting."); - ret = 1; - goto end; - } - -end: - return 0; -} +#include "sunneed_core.h" + +extern struct sunneed_device devices[MAX_DEVICES]; + +struct sunneed_pip pip; + +sunneed_worker_thread_result_t (*worker_thread_functions[])(void *) = {sunneed_proc_monitor, sunneed_quantum_worker, sunneed_stepperMotor_driver, sunneed_camera_driver, NULL}; + +void +handle_exit(void) { + LOG_I("Sunneed exiting"); + LOG_I("\tKilling stepper motor"); + kill(sunneed_stepper_driver_pid, SIGTERM); + LOG_I("\tKilling camera driver"); + kill(sunneed_camera_driver_pid, SIGTERM); +} + +#ifdef TESTING + +#include "sunneed_runtime_test_collection.h" + +int (*runtime_tests[])(void) = RUNTIME_TESTS; + +static unsigned int +testcase_count(void) { + unsigned int testcases = 0; + for (int (**cur)(void) = runtime_tests; *cur != NULL; cur++) + testcases++; + // TODO Why minus one... + return testcases - 1; +} + +static int +run_testcase(unsigned int testcase) { + if (testcase >= testcase_count()) { + LOG_E("Cannot run testcase #%d because it does not exist", testcase); + return 1; + } + + int ret = runtime_tests[testcase](); + if (ret != 0) { + fprintf(stderr, "Failure: %s (%d)\n", sunneed_runtime_test_error, ret); + return ret; + } + + return 0; +} +#endif + +static int +spawn_worker_threads(void) { + int ret; + int worker_thread_count = 0; + for (void *(**cur)(void *) = worker_thread_functions; *cur != NULL; cur++) + worker_thread_count++; + + pthread_t worker_threads[worker_thread_count]; + + LOG_I("Launching %d worker threads", worker_thread_count); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (int i = 0; i < worker_thread_count; i++) { + if ((ret = pthread_create(&worker_threads[i], &attr, worker_thread_functions[i], NULL)) != 0) { + LOG_E("Failed to launch worker thread %d (error %d)", i, ret); + return 1; + }; + } + + return 0; +} + + +void +sunneed_init(void) { + atexit(handle_exit); + pip = pip_info(); +} + +int +main(int argc, char *argv[]) { + int opt; + extern int optopt; + +#ifdef LOG_PWR + logfile_pwr = fopen("sunneed_pwr_log.txt", "w+"); +#endif + +#ifdef TESTING + const char *optstring = ":ht:c"; +#else + const char *optstring = ":h"; +#endif + // TODO Long-form getopts. + while ((opt = getopt(argc, argv, optstring)) != -1) { + switch (opt) { + case 'h': + printf(HELP_TEXT, argv[0]); + exit(0); +#ifdef TESTING + case 't': ; + logfile = fopen("sunneed_log.txt", "w+"); + int testcase = strtol(optarg, NULL, 10); + if (errno) { + LOG_E("Failed to parse testcase index: %s", strerror(errno)); + return 1; + } + return run_testcase(testcase); + case 'c': + printf("%d\n", testcase_count()); + exit(0); +#endif + case '?': + fprintf(stderr, "%s: illegal option -%c\n", APP_NAME, optopt); + exit(1); + case ':': + fprintf(stderr, "%s: expected argument for option -%c\n", APP_NAME, optopt); + exit(1); + } + } + + int ret = 0; + + LOG_I("sunneed is initializing..."); + + sunneed_init(); + + LOG_I("Acquired PIP: %s", pip.name); + + LOG_I("Loading devices..."); + if ((ret = sunneed_load_devices(devices)) != 0) { + LOG_E("Failed to load devices"); + ret = 1; + goto end; + } + + if ((ret = spawn_worker_threads()) != 0) { + LOG_E("Error occurred while spawning worker threads"); + ret = 1; + goto end; + } + if ((ret = sunneed_listen()) != 0) { + LOG_E("sunneed listener encountered a fatal error. Exiting."); + ret = 1; + goto end; + } + +end: + return 0; +} diff --git a/src/sunneed_core.h b/src/sunneed_core.h index fa6b7f6..7dcf211 100644 --- a/src/sunneed_core.h +++ b/src/sunneed_core.h @@ -1,43 +1,43 @@ -#ifndef _SUNNEED_CORE_H_ -#define _SUNNEED_CORE_H_ - -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_pip_interface.h" -#include "sunneed.h" -#include "sunneed_listener.h" -#include "sunneed_proc.h" -#include "sunneed_loader.h" -#include "sunneed_device.h" - -#define _HELP_TEXT_HEAD \ - APP_NAME ": enforce power usage policies\n" \ - "\nUSAGE\n" \ - "%s OPTIONS\n" \ - "\nOPTIONS\n" \ - "\t-h --help Show this help.\n" -#define _HELP_TEXT_TAIL \ - "\n" - -#ifdef TESTING -#define HELP_TEXT _HELP_TEXT_HEAD \ - "\t-c --testcase-count Print out the number of runtime tests.\n" \ - "\t-t --run-test TEST Run a runtime test by given its numerical ID.\n" \ - _HELP_TEXT_TAIL -#else -#define HELP_TEXT _HELP_TEXT_HEAD _HELP_TEXT_TAIL -#endif - -#ifdef TESTING - -#define MAX_TESTS_PER_SUITE 64 - -#endif - -#endif +#ifndef _SUNNEED_CORE_H_ +#define _SUNNEED_CORE_H_ + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "shared/sunneed_pip_interface.h" +#include "sunneed.h" +#include "sunneed_listener.h" +#include "sunneed_proc.h" +#include "sunneed_loader.h" +#include "sunneed_device.h" + +#define _HELP_TEXT_HEAD \ + APP_NAME ": enforce power usage policies\n" \ + "\nUSAGE\n" \ + "%s OPTIONS\n" \ + "\nOPTIONS\n" \ + "\t-h --help Show this help.\n" +#define _HELP_TEXT_TAIL \ + "\n" + +#ifdef TESTING +#define HELP_TEXT _HELP_TEXT_HEAD \ + "\t-c --testcase-count Print out the number of runtime tests.\n" \ + "\t-t --run-test TEST Run a runtime test by given its numerical ID.\n" \ + _HELP_TEXT_TAIL +#else +#define HELP_TEXT _HELP_TEXT_HEAD _HELP_TEXT_TAIL +#endif + +#ifdef TESTING + +#define MAX_TESTS_PER_SUITE 64 + +#endif + +#endif diff --git a/src/sunneed_device.c b/src/sunneed_device.c index ecf702f..2477a6b 100644 --- a/src/sunneed_device.c +++ b/src/sunneed_device.c @@ -1,66 +1,66 @@ -#include "sunneed_device.h" - -struct sunneed_device devices[MAX_DEVICES] = { { .is_ready = false } }; -struct { - bool init; - char pathname[DUMMY_FILE_PATH_LEN]; // TODO Don't hardcode. - char dummypath[DUMMY_FILE_PATH_LEN]; -} dummy_map[MAX_LOCKED_FILES] = { { false, { '\0' }, { '\0' } } }; - -/** - * Check for devices locking the specified pathname. - * Returns the device specifying the lock if one is found, otherwise returns NULL. - */ -struct sunneed_device * -sunneed_device_file_locker(const char *pathname) { - for (int i = 0; i < MAX_DEVICES; i++) { - // Find devices of type FILE_LOCK. - if (!devices[i].is_ready || devices[i].device_type_kind != DEVICE_TYPE_FILE_LOCK) - continue; - - // Compare to each locked file specified by the device. - for (unsigned int s = 0; s < devices[i].device_type_data.file_lock->len; s++) { - LOG_D("Comparing %s to %s", pathname, devices[i].device_type_data.file_lock->paths[s]); - - // TODO Treat locked filepaths as a list of paths. - if (!strncmp(pathname, devices[i].device_type_data.file_lock->paths[s], strlen(pathname))) { - return &devices[i]; - } - } - } - - return NULL; -} - -char * -sunneed_device_get_dummy_file(const char *orig_path) { - // Try to find the already-created locked file with that name. - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (dummy_map[i].init) - if (strncmp(orig_path, dummy_map[i].pathname, sizeof(dummy_map[i].pathname)) == 0) - return dummy_map[i].dummypath; - } - - // We haven't made a dummy file for this yet. - char template[] = "locked_XXXXXX"; - int dummy; - if ((dummy = mkstemp(template)) == -1) { - LOG_E("Error creating temp dummy file"); - return NULL; - } - - LOG_I("Created dummy file '%s'", template); - - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (!dummy_map[i].init) { - dummy_map[i].init = dummy; - strncpy(dummy_map[i].pathname, orig_path, DUMMY_FILE_PATH_LEN); - strncpy(dummy_map[i].dummypath, template, DUMMY_FILE_PATH_LEN); - return dummy_map[i].dummypath; - } - } - - // Out of entires in dummy file table. - LOG_E("Cannot create dummy file mapping for '%s'", orig_path); - return NULL; -} +#include "sunneed_device.h" + +struct sunneed_device devices[MAX_DEVICES] = { { .is_ready = false } }; +struct { + bool init; + char pathname[DUMMY_FILE_PATH_LEN]; // TODO Don't hardcode. + char dummypath[DUMMY_FILE_PATH_LEN]; +} dummy_map[MAX_LOCKED_FILES] = { { false, { '\0' }, { '\0' } } }; + +/** + * Check for devices locking the specified pathname. + * Returns the device specifying the lock if one is found, otherwise returns NULL. + */ +struct sunneed_device * +sunneed_device_file_locker(const char *pathname) { + for (int i = 0; i < MAX_DEVICES; i++) { + // Find devices of type FILE_LOCK. + if (!devices[i].is_ready || devices[i].device_type_kind != DEVICE_TYPE_FILE_LOCK) + continue; + + // Compare to each locked file specified by the device. + for (unsigned int s = 0; s < devices[i].device_type_data.file_lock->len; s++) { + LOG_D("Comparing %s to %s", pathname, devices[i].device_type_data.file_lock->paths[s]); + + // TODO Treat locked filepaths as a list of paths. + if (!strncmp(pathname, devices[i].device_type_data.file_lock->paths[s], strlen(pathname))) { + return &devices[i]; + } + } + } + + return NULL; +} + +char * +sunneed_device_get_dummy_file(const char *orig_path) { + // Try to find the already-created locked file with that name. + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (dummy_map[i].init) + if (strncmp(orig_path, dummy_map[i].pathname, sizeof(dummy_map[i].pathname)) == 0) + return dummy_map[i].dummypath; + } + + // We haven't made a dummy file for this yet. + char template[] = "locked_XXXXXX"; + int dummy; + if ((dummy = mkstemp(template)) == -1) { + LOG_E("Error creating temp dummy file"); + return NULL; + } + + LOG_I("Created dummy file '%s'", template); + + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (!dummy_map[i].init) { + dummy_map[i].init = dummy; + strncpy(dummy_map[i].pathname, orig_path, DUMMY_FILE_PATH_LEN); + strncpy(dummy_map[i].dummypath, template, DUMMY_FILE_PATH_LEN); + return dummy_map[i].dummypath; + } + } + + // Out of entires in dummy file table. + LOG_E("Cannot create dummy file mapping for '%s'", orig_path); + return NULL; +} diff --git a/src/sunneed_device.h b/src/sunneed_device.h index 9493f90..8c70875 100644 --- a/src/sunneed_device.h +++ b/src/sunneed_device.h @@ -1,50 +1,50 @@ -#ifndef _SUNNEED_DEVICE_H_ -#define _SUNNEED_DEVICE_H_ - -#include "sunneed.h" -#include "log.h" - -#include "shared/sunneed_device_type.h" -#include "shared/sunneed_files.h" -#include "protobuf/c/device.pb-c.h" - -#include -#include -#include - -#define DEVICE_IDENTIFIER_LEN 32 - -#define MAX_DEVICES 64 - -#define DUMMY_FILE_PATH_LEN 128 - -#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) - -struct sunneed_device { - /** Set to true when all data has been prepared in this struct. */ - bool is_ready; - - /** Numerical identifier of the device. */ - int handle; - - /** Human-readable name for the device (e.g. "camera"). */ - char *identifier; - - /** Specifices the union member to write to in `device_type_data`. */ - enum sunneed_device_type device_type_kind; - - /** One of many different structs representing arbitrary data of the device interface. */ - union { - struct sunneed_device_type_file_lock *file_lock; - } device_type_data; -}; - -struct sunneed_device * -sunneed_device_file_locker(const char *pathname); - -char * -sunneed_device_get_dummy_file(const char *orig_path); - -extern struct sunneed_device devices[]; - -#endif +#ifndef _SUNNEED_DEVICE_H_ +#define _SUNNEED_DEVICE_H_ + +#include "sunneed.h" +#include "log.h" + +#include "shared/sunneed_device_type.h" +#include "shared/sunneed_files.h" +#include "protobuf/c/device.pb-c.h" + +#include +#include +#include + +#define DEVICE_IDENTIFIER_LEN 32 + +#define MAX_DEVICES 64 + +#define DUMMY_FILE_PATH_LEN 128 + +#define SUNNEED_DEVICE_FLAG_SILENT_FAIL (1 << 0) + +struct sunneed_device { + /** Set to true when all data has been prepared in this struct. */ + bool is_ready; + + /** Numerical identifier of the device. */ + int handle; + + /** Human-readable name for the device (e.g. "camera"). */ + char *identifier; + + /** Specifices the union member to write to in `device_type_data`. */ + enum sunneed_device_type device_type_kind; + + /** One of many different structs representing arbitrary data of the device interface. */ + union { + struct sunneed_device_type_file_lock *file_lock; + } device_type_data; +}; + +struct sunneed_device * +sunneed_device_file_locker(const char *pathname); + +char * +sunneed_device_get_dummy_file(const char *orig_path); + +extern struct sunneed_device devices[]; + +#endif diff --git a/src/sunneed_listener.c b/src/sunneed_listener.c index 9e08fc5..2ae77c6 100644 --- a/src/sunneed_listener.c +++ b/src/sunneed_listener.c @@ -1,358 +1,586 @@ -#include "sunneed_listener.h" - -#include "protobuf/c/server.pb-c.h" - -#define SUB_RESPONSE_BUF_SZ 4096 - -extern struct sunneed_device devices[]; -extern struct sunneed_tenant tenants[]; -extern const char *locked_file_paths[]; - -/** - * Maps dummy paths (typically sent by clients during a read or write) to FDs pointing to the real device, held by - * sunneed. - */ -struct { - char *path; - int fd; -} dummy_path_fd_map[MAX_LOCKED_FILES] = { { NULL, 0 } }; - -static int -get_fd_from_dummy_path(char *path) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) - if (dummy_path_fd_map[i].path && strncmp(dummy_path_fd_map[i].path, path, strlen(path)) == 0) - return dummy_path_fd_map[i].fd; - return -1; -} - -// TODO This is probably slow -- O(n) lookup for every request made. -static struct sunneed_tenant * -tenant_of_pipe(int pipe_id) { - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) - if (nng_pipe_id(tenant_pipes[i].pipe) == pipe_id) - return tenant_pipes[i].tenant; - return NULL; -} - -// Get the PID of a pipe and use that to create a new sunneed tenant with that ID. -// TODO: This shouldn't always create a new tenant, since we want multiple processes -// mapped to one tenant. -static struct sunneed_tenant * -register_client(nng_pipe pipe) { - struct sunneed_tenant *tenant; - - // Get PID of pipe. - uint64_t pid_int; - SUNNEED_NNG_TRY(nng_pipe_get_uint64, != 0, pipe, NNG_OPT_IPC_PEER_PID, &pid_int); - pid_t pid = (pid_t)pid_int; - - if ((tenant = sunneed_tenant_register(pid)) == NULL) { - LOG_E("Failed to initialize tenant from PID %d", pid); - return NULL; - } - - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { - if (tenant_pipes[i].tenant == NULL) { - tenant_pipes[i].tenant = tenant; - tenant_pipes[i].pipe = pipe; - break; - } - } - - return tenant; -} - -// The `serve_*` methods take a `sub_resp_buf` parameter. This is a pointer to a buffer in which the client -// can store their sub-response (the message in the oneof field of the SunneedResponse). Example: -// -// GetDeviceHandleResponse *sub_resp = sub_resp_buf; -// *sub_resp = (GetDeviceHandleResponse)GET_DEVICE_HANDLE_RESPONSE__INIT; -// -// This example writes the initializer for the `GetDeviceHandleResponse` to the address pointed to by -// `sub_resp_buf`. -// The rationale for this whole process comes next: once the `serve_*` function returns, its sub-response -// data is contained within the buffer, to which a pointer is in scope in the main request listening -// loop. - -// Create a mapping between this pipe and a sunneed tenant. -// TODO Currently, this just spawns a new tenant for each different pipe. We want tenants to be able to have multiple -// pipes to sunneed open. -static int -serve_register_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe) { - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT; - RegisterClientResponse *sub_resp = sub_resp_buf; - *sub_resp = (RegisterClientResponse)REGISTER_CLIENT_RESPONSE__INIT; - resp->register_client = sub_resp; - - struct sunneed_tenant *tenant = NULL; - - // Register as a new client. - if ((tenant = register_client(pipe)) == NULL) { - LOG_W("Registration failed for pipe %d", pipe.id); - return 1; - } - - // Construct the list of locked file paths to send to the client. - size_t locked_paths_len = 0; - for (locked_paths_len = 0; locked_file_paths[locked_paths_len] != NULL; locked_paths_len++) ; - sub_resp->n_locked_paths = locked_paths_len; - sub_resp->locked_paths = malloc(sizeof(char *) * sub_resp->n_locked_paths); - for (size_t i = 0; i < sub_resp->n_locked_paths; i++) - sub_resp->locked_paths[i] = (char *)locked_file_paths[i]; - - LOG_D("Registered pipe %d with tenant %d", pipe.id, tenant->id); - - return 0; -} - -static int -serve_unregister_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe, struct sunneed_tenant *tenant) { - int retval; - - LOG_D("Unregistering tenant %d", tenant->id); - - // Find the entry in the tenant pipe mappings. - bool cleared = false; - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) - if (tenant_pipes[i].pipe.id == pipe.id) { - LOG_D("Removing mapping from pipe %d to tenant %d", pipe.id, tenant->id); - tenant_pipes[i].tenant = NULL; - tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; - cleared = true; - break; - } - - if (!cleared) { - LOG_E("No mapping cleared when unregistering pipe %d; something is wrong with the pipe->tenant table", pipe.id); - return 1; - } - - if ((retval = sunneed_tenant_unregister(tenant)) != 0) - // TODO Handle (follow the pattern of the `register_client` stuff by making a secondary `unregister` function - // that handles interacting with tenants). - return 1; - - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; - GenericResponse *sub_resp = sub_resp_buf; - *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; - resp->generic = sub_resp; - - return 0; -} - -static int -serve_open_file( - SunneedResponse *resp, - void *sub_resp_buf, - __attribute__((unused)) struct sunneed_tenant *tenant, - OpenFileRequest *request) { - LOG_D("Got request to open file '%s'", request->path); - - OpenFileResponse *sub_resp = sub_resp_buf; - *sub_resp = (OpenFileResponse)OPEN_FILE_RESPONSE__INIT; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE; - resp->open_file = sub_resp; - - // TODO Take flags!! - - - struct sunneed_device *locker; - if ((locker = sunneed_device_file_locker(request->path)) != NULL) { - // TODO Wait for availability, perform power calcs, etc. - - // Open the real file and save its FD. - int real_fd = open(request->path, request->flags, request->mode); - - if (real_fd == -1) { - LOG_E("Failed to open file '%s'", request->path); - return 1; - } - - char *dummypath = sunneed_device_get_dummy_file(request->path); - - int i; - for (i = 0; i < MAX_LOCKED_FILES; i++) { - // Find open slot. - if (dummy_path_fd_map[i].path == NULL) { - dummy_path_fd_map[i].path = malloc(strlen(dummypath) + 1); - strncpy(dummy_path_fd_map[i].path, dummypath, strlen(dummypath) + 1); - dummy_path_fd_map[i].fd = real_fd; - - LOG_I("Opened locked path '%s' as '%s' (FD %d)", request->path, dummypath, real_fd); - - break; - } - } - if (i == MAX_LOCKED_FILES) { - // Theoretically this should never happen (since MAX_LOCKED_FILES also bounds the number of possible locked - // paths) but good to check. - LOG_E("No slots remaining in dummy_path_fd_map"); - return 1; - } - - // TODO Free this - sub_resp->path = malloc(strlen(dummypath) + 1); - strncpy(sub_resp->path, dummypath, strlen(dummypath) + 1); - } else { - // They requested a non-dummy file. - return 1; - } - - return 0; -} - -static int -serve_write( - SunneedResponse *resp, - void *sub_resp_buf, - struct sunneed_tenant *tenant, - WriteRequest *request) { - LOG_D("Got request from %d to write %ld bytes to '%s' (real file FD %d)", tenant->id, request->data.len, request->dummy_path, get_fd_from_dummy_path(request->dummy_path)); - - WriteResponse *sub_resp = sub_resp_buf; - *sub_resp = (WriteResponse)WRITE_RESPONSE__INIT; - resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE; - resp->call_write = sub_resp; - - - #ifdef LOG_PWR - char buf[1024]; - char real_path[1024]; - - sprintf(buf, "/proc/self/fd/%d", get_fd_from_dummy_path(request->dummy_path)); - memset(real_path, 0, sizeof(real_path)); - readlink(buf, real_path, sizeof(real_path)); - printf("real path: %s\n", real_path); - if (strcmp(real_path, "/dev/stepper") == 0) { - LOG_D("writing to stepper motor driver"); - } - #endif - - // Perform the write. - ssize_t bytes_written; - if ((bytes_written = write(get_fd_from_dummy_path(request->dummy_path), request->data.data, request->data.len)) - < 0) { - int errno_val = errno; - - sub_resp->errno_value = errno_val; - sub_resp->bytes_written = bytes_written; - - LOG_E("`write` for client %d failed with: %s", tenant->id, strerror(errno_val)); - - return 1; - } - - sub_resp->bytes_written = bytes_written; - sub_resp->errno_value = 0; - - return 0; -} - -static void -report_nng_error(const char *func, int rv) { - LOG_E("nng error: (%s) %s", func, nng_strerror(rv)); -} - -int -sunneed_listen(void) { - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); - - // Initialize client states. - for (int i = 0; i < MAX_TENANTS; i++) { - tenant_pipes[i] = (struct tenant_pipe){.tenant = NULL, - // TODO Why do I need to cast this... - .pipe = (nng_pipe)NNG_PIPE_INITIALIZER}; - } - - nng_socket sock; - - LOG_I("Starting listener loop..."); - - // Make a socket and attach it to the sunneed URL. - SUNNEED_NNG_TRY_RET(nng_rep0_open, != 0, &sock); - SUNNEED_NNG_TRY_RET(nng_listen, < 0, sock, SUNNEED_LISTENER_URL, NULL, 0); - - // Buffer for `serve_` methods to write their sub-response to. - void *sub_resp_buf = malloc(SUB_RESPONSE_BUF_SZ); - // TODO Check malloc. - - // Await messages. - for (;;) { - nng_msg *msg; - - SUNNEED_NNG_TRY_RET(nng_recvmsg, != 0, sock, &msg, NNG_FLAG_ALLOC); - - // TODO They claim nng_msg_get_pipe() returns -1 on error, but its return type is nng_pipe, which can't - // be compared to an integer. - nng_pipe pipe = nng_msg_get_pipe(msg); - - // Get contents of message. - size_t msg_len = nng_msg_len(msg); - -// SUNNEED_NNG_MSG_LEN_FIX(msg_len); - - SunneedRequest *request = sunneed_request__unpack(NULL, msg_len, nng_msg_body(msg)); - - if (request == NULL) { - LOG_W("Received null request from %d", pipe.id); - goto end; - } - - struct sunneed_tenant *tenant = NULL; - - // Find the pipe's associated tenant. If we can't find it, we error out unless the message is of type REGISTER_CLIENT. - if ((tenant = tenant_of_pipe(pipe.id)) == NULL && request->message_type_case != SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT) { - // This client has not registered! - LOG_W("Received message from %d, who is not registered.", pipe.id); - goto end; - } - - // Begin setting up our response. - SunneedResponse resp = SUNNEED_RESPONSE__INIT; - int ret = -1; - - switch (request->message_type_case) { - case SUNNEED_REQUEST__MESSAGE_TYPE__NOT_SET: - LOG_W("Request from pipe %d has no message type set.", pipe.id); - ret = -1; - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT: - ret = serve_register_client(&resp, sub_resp_buf, pipe); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT: - ret = serve_unregister_client(&resp, sub_resp_buf, pipe, tenant); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE: - ret = serve_open_file(&resp, sub_resp_buf, tenant, request->open_file); - break; - case SUNNEED_REQUEST__MESSAGE_TYPE_WRITE: - ret = serve_write(&resp, sub_resp_buf, tenant, request->write); - break; - default: - LOG_W("Received request with invalid type %d", request->message_type_case); - ret = -1; - break; - } - - resp.status = ret; - - // Create and send the response message. - nng_msg *resp_msg; - int resp_len = sunneed_response__get_packed_size(&resp); - void *resp_buf = malloc(resp_len); - sunneed_response__pack(&resp, resp_buf); - - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, 0); - // SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, resp_len); - SUNNEED_NNG_TRY(nng_msg_append, != 0, resp_msg, resp_buf, resp_len); - // SUNNEED_NNG_TRY(nng_msg_insert, != 0, resp_msg, resp_buf, resp_len); - SUNNEED_NNG_TRY(nng_sendmsg, != 0, sock, resp_msg, 0); - - end: - sunneed_request__free_unpacked(request, NULL); - - nng_msg_free(msg); - } - - free(sub_resp_buf); -} +#include "sunneed_listener.h" + +#include "protobuf/c/server.pb-c.h" + +#define SUB_RESPONSE_BUF_SZ 4096 + +extern struct sunneed_device devices[]; +extern struct sunneed_tenant tenants[]; +extern const char *locked_file_paths[]; +extern int errno; +/*n + * Maps dummy paths (typically sent by clients during a read or write) to FDs pointing to the real device, held by + * sunneed. + */ +struct { + char *path; + int fd; +} dummy_path_fd_map[MAX_LOCKED_FILES] = { { NULL, 0 } }; + +struct { + int id; + int sockfd; + int domain; +} dummy_socket_map[MAX_TENANT_SOCKETS] = { {-1, -1, 0} }; + +static int +get_fd_from_dummy_path(char *path) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) + if (dummy_path_fd_map[i].path && strncmp(dummy_path_fd_map[i].path, path, strlen(path)) == 0) + return dummy_path_fd_map[i].fd; + return -1; +} + +// TODO This is probably slow -- O(n) lookup for every request made. +static struct sunneed_tenant * +tenant_of_pipe(int pipe_id) { + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) + if (nng_pipe_id(tenant_pipes[i].pipe) == pipe_id) + return tenant_pipes[i].tenant; + return NULL; +} + + +// Get the PID of a pipe and use that to create a new sunneed tenant with that ID. +// TODO: This shouldn't always create a new tenant, since we want multiple processes +// mapped to one tenant. +static struct sunneed_tenant * +register_client(nng_pipe pipe) { + struct sunneed_tenant *tenant; + + // Get PID of pipe. + uint64_t pid_int; + SUNNEED_NNG_TRY(nng_pipe_get_uint64, != 0, pipe, NNG_OPT_IPC_PEER_PID, &pid_int); + pid_t pid = (pid_t)pid_int; + + if ((tenant = sunneed_tenant_register(pid)) == NULL) { + LOG_E("Failed to initialize tenant from PID %d", pid); + return NULL; + } + + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { + if (tenant_pipes[i].tenant == NULL) { + tenant_pipes[i].tenant = tenant; + tenant_pipes[i].pipe = pipe; + break; + } + } + + return tenant; +} + +// The `serve_*` methods take a `sub_resp_buf` parameter. This is a pointer to a buffer in which the client +// can store their sub-response (the message in the oneof field of the SunneedResponse). Example: +// +// GetDeviceHandleResponse *sub_resp = sub_resp_buf; +// *sub_resp = (GetDeviceHandleResponse)GET_DEVICE_HANDLE_RESPONSE__INIT; +// +// This example writes the initializer for the `GetDeviceHandleResponse` to the address pointed to by +// `sub_resp_buf`. +// The rationale for this whole process comes next: once the `serve_*` function returns, its sub-response +// data is contained within the buffer, to which a pointer is in scope in the main request listening +// loop. + +// Create a mapping between this pipe and a sunneed tenant. +// TODO Currently, this just spawns a new tenant for each different pipe. We want tenants to be able to have multiple +// pipes to sunneed open. +static int +serve_register_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe) { + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT; + RegisterClientResponse *sub_resp = sub_resp_buf; + *sub_resp = (RegisterClientResponse)REGISTER_CLIENT_RESPONSE__INIT; + resp->register_client = sub_resp; + + struct sunneed_tenant *tenant = NULL; + + // Register as a new client. + if ((tenant = register_client(pipe)) == NULL) { + LOG_W("Registration failed for pipe %d", pipe.id); + return 1; + } + + // Construct the list of locked file paths to send to the client. + size_t locked_paths_len = 0; + for (locked_paths_len = 0; locked_file_paths[locked_paths_len] != NULL; locked_paths_len++) ; + sub_resp->n_locked_paths = locked_paths_len; + sub_resp->locked_paths = malloc(sizeof(char *) * sub_resp->n_locked_paths); + for (size_t i = 0; i < sub_resp->n_locked_paths; i++) + sub_resp->locked_paths[i] = (char *)locked_file_paths[i]; + + LOG_D("Registered pipe %d with tenant %d", pipe.id, tenant->id); + + return 0; +} + +static int +serve_unregister_client(SunneedResponse *resp, void *sub_resp_buf, nng_pipe pipe, struct sunneed_tenant *tenant) { + int retval; + + LOG_D("Unregistering tenant %d", tenant->id); + + // Find the entry in the tenant pipe mappings. + bool cleared = false; + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) + if (tenant_pipes[i].pipe.id == pipe.id) { + LOG_D("Removing mapping from pipe %d to tenant %d", pipe.id, tenant->id); + tenant_pipes[i].tenant = NULL; + tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; + cleared = true; + break; + } + + if (!cleared) { + LOG_E("No mapping cleared when unregistering pipe %d; something is wrong with the pipe->tenant table", pipe.id); + return 1; + } + + if ((retval = sunneed_tenant_unregister(tenant)) != 0) + // TODO Handle (follow the pattern of the `register_client` stuff by making a secondary `unregister` function + // that handles interacting with tenants). + return 1; + + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; + + return 0; +} + +static int +serve_open_file( + SunneedResponse *resp, + void *sub_resp_buf, + __attribute__((unused)) struct sunneed_tenant *tenant, + OpenFileRequest *request) { + LOG_D("Got request to open file '%s'", request->path); + + OpenFileResponse *sub_resp = sub_resp_buf; + *sub_resp = (OpenFileResponse)OPEN_FILE_RESPONSE__INIT; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_OPEN_FILE; + resp->open_file = sub_resp; + + // TODO Take flags!! + + + struct sunneed_device *locker; + if ((locker = sunneed_device_file_locker(request->path)) != NULL) { + // TODO Wait for availability, perform power calcs, etc. + + // Open the real file and save its FD. +// int real_fd = open(request->path, O_RDWR); // TODO Use flags given by client. + int real_fd = open(request->path, request->flags); + + if (real_fd == -1) { + LOG_E("Failed to open file '%s'", request->path); + return 1; + } + + char *dummypath = sunneed_device_get_dummy_file(request->path); + + int i; + for (i = 0; i < MAX_LOCKED_FILES; i++) { + // Find open slot. + if (dummy_path_fd_map[i].path == NULL) { + dummy_path_fd_map[i].path = malloc(strlen(dummypath) + 1); + strncpy(dummy_path_fd_map[i].path, dummypath, strlen(dummypath) + 1); + dummy_path_fd_map[i].fd = real_fd; + + LOG_I("Opened locked path '%s' as '%s' (FD %d)", request->path, dummypath, real_fd); + + break; + } + } + if (i == MAX_LOCKED_FILES) { + // Theoretically this should never happen (since MAX_LOCKED_FILES also bounds the number of possible locked + // paths) but good to check. + LOG_E("No slots remaining in dummy_path_fd_map"); + return 1; + } + + // TODO Free this + sub_resp->path = malloc(strlen(dummypath) + 1); + strncpy(sub_resp->path, dummypath, strlen(dummypath) + 1); + } else { + // They requested a non-dummy file. + return 1; + } + + return 0; +} + +static int +serve_write( + SunneedResponse *resp, + void *sub_resp_buf, + struct sunneed_tenant *tenant, + WriteRequest *request) { + LOG_D("Got request from %d to write %ld bytes to '%s' (real file FD %d)", tenant->id, request->data.len, request->dummy_path, get_fd_from_dummy_path(request->dummy_path)); + + WriteResponse *sub_resp = sub_resp_buf; + *sub_resp = (WriteResponse)WRITE_RESPONSE__INIT; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_CALL_WRITE; + resp->call_write = sub_resp; + + // Perform the write. + ssize_t bytes_written; + if ((bytes_written = write(get_fd_from_dummy_path(request->dummy_path), request->data.data, request->data.len)) + < 0) { + int errno_val = errno; + + sub_resp->errno_value = errno_val; + sub_resp->bytes_written = bytes_written; + + LOG_E("`write` for client %d failed with: %s", tenant->id, strerror(errno_val)); + + return 1; + } + + sub_resp->bytes_written = bytes_written; + sub_resp->errno_value = 0; + + return 0; +} + +static int +serve_socket(SunneedResponse *resp, void *sub_response_buf, SocketRequest *request) +{ + LOG_D("Got request to open a new socket\n"); + + SocketResponse *sock_resp = sub_response_buf; + *sock_resp = (SocketResponse)SOCKET_RESPONSE__INIT; + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_SOCKET; + resp->socket = sock_resp; + + /* + * add domain and real socket file descriptor to dummy socket map + * need to store domain for use in connect + * + * the tenant will receive the index into the table of the socket fd and their dummy sockfd + */ + int i, new_id, sockfd; + for(i = 0; i < MAX_TENANT_SOCKETS; i++) + { + new_id = i; + if(dummy_socket_map[i].id == -1) + { + sockfd = socket(request->domain, request->type, request->protocol); + if(sockfd) + { + dummy_socket_map[i].id = new_id; + dummy_socket_map[i].sockfd = sockfd; + + //need to store the domain (AF_INET) to use in connect + dummy_socket_map[i].domain = request->domain; + LOG_D("Socket %d created successfully\n", new_id); + + /* + * add 3 to the dummy sockfd to be returned to avoid overwriting STDIN, STDOUT, or STDERR + * using (request->sockfd - 3) in connect and send should give us O(1) lookup for this table + */ + sock_resp->dummy_sockfd = new_id + 3; + return 0; + }else{ + LOG_E("Failed to create socket. domain %d type %d protocol %d\n", request->domain, request->type, request->protocol); + return 1; + } + } + } + LOG_E("Maximum tenant sockets have been created\n"); + return 1; + +} + +static int +serve_connect(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *tenant, ConnectRequest *request) +{ + LOG_D("got connect request\n"); + int sockfd, domain; + struct sockaddr_in remote_addr; + sockfd = domain = 0; + + /* + * the fake sockfd passed by the request is the index into the dummy socket map of the tenants socket + * can take advantage of this for O(1) retrieval of the real socket fd and domain + */ + sockfd = dummy_socket_map[request->sockfd - 3].sockfd; + domain = dummy_socket_map[request->sockfd - 3].domain; + + //check sockfd and domain + if(sockfd < 0) + { + LOG_E("failed to find socket for tenant %d\n", tenant->id); + return 1; + } + + /* + * should have errored out in socket if the tenant passed an IPv6 domain + * but worth the check here in case the dummy socket table got messed with + */ + if(!(domain == AF_INET)) + { + LOG_E("tenant %d tried to create a non-IPv4 socket %d\n", tenant->id, domain); + return 1; + } + + remote_addr.sin_family = domain; + remote_addr.sin_port = htons(request->port); + + + if(inet_pton(domain, request->address, &remote_addr.sin_addr) <= 0) + { + LOG_E("invalid address/domain or failed to convert\n"); + return 1; + } + + if(connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + LOG_E("Failed to connect to %s\n", request->address); + return 1; + } + + //connection successful, send generic response to the tenant + LOG_D("connected to remote host %s on port %d\n", request->address, request->port); + + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; + + return 0; + + +} + +static int +serve_send(SunneedResponse *resp, void* sub_resp_buf, struct sunneed_tenant *tenant, SendRequest *request) +{ + + LOG_D("Got request from %d to send %ld bytes", tenant->id, request->data.len); + + //same lookup as serve_connect, use the requested sockfd as the index for O(1) lookup + int sockfd = dummy_socket_map[request->sockfd - 3].sockfd; + + if(sockfd < 0) + { + LOG_E("Bad socket descriptor: %d\n", sockfd); + return 1; + } + if(!(request->data.data)) + { + LOG_E("couldnt get data from request\n"); + return 1; + } + +#ifdef LOG_PWR + // log time since last sent packet and size of the current packet + if(last_send == 0) + { + last_send = clock(); + LOG_P ("%f ", (((double)(last_send))/CLOCKS_PER_SEC)); + LOG_D ("first send %f ", (((double) (last_send))/CLOCKS_PER_SEC)); + }else{ + time_since_send = (double)(clock() - last_send) / (double)CLOCKS_PER_SEC; + LOG_P("%f ", time_since_send); + LOG_D("%f since last send", time_since_send); + } + + LOG_P("%d ", request->data.len); + LOG_D("msg size %d\n", request->data.len); + +#endif + + if((send(sockfd, request->data.data, request->data.len, request->flags)) < 0) + { + LOG_E("Failed to send data for tenant %d error %d\n", tenant->id, errno); + return 1; + }else{ + + LOG_D("Sent data from tenant %d\n", tenant->id); + } + +#ifdef LOG_PWR + //log power change from send and reset last_capacity and last_send + curr_read = present_power(); + + LOG_P("%d\n", curr_read); + LOG_D("%d\n", curr_read); + + last_read = curr_read; + last_send = clock(); + +#endif + + resp->message_type_case = SUNNEED_RESPONSE__MESSAGE_TYPE_GENERIC; + GenericResponse *sub_resp = sub_resp_buf; + *sub_resp = (GenericResponse)GENERIC_RESPONSE__INIT; + resp->generic = sub_resp; + + + return 0; +} +static void +report_nng_error(const char *func, int rv) { + LOG_E("nng error: (%s) %s", func, nng_strerror(rv)); +} + +int +sunneed_listen(void) { + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(report_nng_error); + + #ifdef LOG_PWR + last_read = present_power(); + int capacity_change; + last_send = 0; + #endif + + // Initialize client states. + for (int i = 0; i < MAX_TENANTS; i++) { + tenant_pipes[i] = (struct tenant_pipe){.tenant = NULL, + // TODO Why do I need to cast this... + .pipe = (nng_pipe)NNG_PIPE_INITIALIZER}; + } + + nng_socket sock; + + LOG_I("Starting listener loop..."); + + // Make a socket and attach it to the sunneed URL. + + SUNNEED_NNG_TRY_RET(nng_rep0_open, != 0, &sock); + + SUNNEED_NNG_TRY_RET(nng_listen, < 0, sock, SUNNEED_LISTENER_URL, NULL, 0); + + + + + // Buffer for `serve_` methods to write their sub-response to. + void *sub_resp_buf = malloc(SUB_RESPONSE_BUF_SZ); + // TODO Check malloc. + if(sub_resp_buf == NULL) + { + LOG_E ("ERROR, failed to malloc sub_resp_buf in sunneed_listener"); + } + int ret = -1; + // Await messages. + for (;;) { + nng_msg *msg = NULL; + + + SUNNEED_NNG_TRY_SET(nng_recvmsg, ret, != 0, sock, &msg, 0); + if(ret) + { + LOG_E("nng_recvmsg failed with error %d\n", ret); + } + // TODO They claim nng_msg_get_pipe() returns -1 on error, but its return type is nng_pipe, which can't + // be compared to an integer. + if(!(msg)) + { + LOG_E("recv msg couldn't get the request"); + } + nng_pipe pipe = nng_msg_get_pipe(msg); + + // Get contents of message. + size_t msg_len = nng_msg_len(msg); + //SUNNEED_NNG_MSG_LEN_FIX(msg_len); + SunneedRequest *request = sunneed_request__unpack(NULL, msg_len, nng_msg_body(msg)); + + if (request == NULL) { + LOG_W("Received null request from %d", pipe.id); + goto end; + } + + struct sunneed_tenant *tenant = NULL; + + // Find the pipe's associated tenant. If we can't find it, we error out unless the message is of type REGISTER_CLIENT. + if ((tenant = tenant_of_pipe(pipe.id)) == NULL && request->message_type_case != SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT) { + // This client has not registered! + LOG_W("Received message from %d, who is not registered.", pipe.id); + goto end; + } + + // Begin setting up our response. + SunneedResponse resp = SUNNEED_RESPONSE__INIT; + int ret = -1; + + #ifdef LOG_PWR + reqs_since_last_log++; + #endif + + switch (request->message_type_case) { + case SUNNEED_REQUEST__MESSAGE_TYPE__NOT_SET: + LOG_W("Request from pipe %d has no message type set.", pipe.id); + ret = -1; + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_REGISTER_CLIENT: + ret = serve_register_client(&resp, sub_resp_buf, pipe); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_UNREGISTER_CLIENT: + ret = serve_unregister_client(&resp, sub_resp_buf, pipe, tenant); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_OPEN_FILE: + ret = serve_open_file(&resp, sub_resp_buf, tenant, request->open_file); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_WRITE: + #ifdef LOG_PWR + if (reqs_since_last_log < REQS_PER_LOG) { + LOG_D("%d, ",reqs_since_last_log); + LOG_I("%d\n",capacity_change); + } else if (reqs_since_last_log > REQS_PER_LOG) { + curr_capacity = present_power(); + capacity_change = last_capacity - curr_capacity; + last_capacity = curr_capacity; + LOG_D("%d\n",capacity_change); + LOG_D("%d, ",reqs_since_last_log); + reqs_since_last_log = 1; + } + #endif + ret = serve_write(&resp, sub_resp_buf, tenant, request->write); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_SOCKET: + ret = serve_socket(&resp, sub_resp_buf, request->socket); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_CONNECT: + ret = serve_connect(&resp, sub_resp_buf, tenant, request->connect); + break; + case SUNNEED_REQUEST__MESSAGE_TYPE_SEND: + ret = serve_send(&resp, sub_resp_buf, tenant, request->send); + break; + default: + LOG_W("Received request with invalid type %d", request->message_type_case); + ret = -1; + #ifdef LOG_PWR + reqs_since_last_log--; + #endif + break; + } + + resp.status = ret; + // Create and send the response message. + nng_msg *resp_msg; + int resp_len = sunneed_response__get_packed_size(&resp); + void *resp_buf = malloc(resp_len); + sunneed_response__pack(&resp, resp_buf); + + SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &resp_msg, 0); + SUNNEED_NNG_TRY(nng_msg_append, != 0, resp_msg, resp_buf, resp_len); + SUNNEED_NNG_TRY(nng_sendmsg, != 0, sock, resp_msg, 0); + + if(resp.message_type_case == SUNNEED_RESPONSE__MESSAGE_TYPE_REGISTER_CLIENT) + { + //need to free the locked paths array after we send the message + free(resp.register_client->locked_paths); + } + + //nng_msg_free(resp_msg); + + end: + if(request != NULL) sunneed_request__free_unpacked(request, NULL); + + free(resp_buf); + + } + free(sub_resp_buf); + + +} diff --git a/src/sunneed_listener.h b/src/sunneed_listener.h index 9ab45dc..7f11fa9 100644 --- a/src/sunneed_listener.h +++ b/src/sunneed_listener.h @@ -1,29 +1,33 @@ -#ifndef _SUNNEED_LISTENER_H_ -#define _SUNNEED_LISTENER_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_ipc.h" -#include "sunneed.h" -#include "sunneed_power.h" -#include "sunneed_proc.h" - -#define SUNNEED_MESSAGE_DEFAULT_BODY_SZ 64 -#define SUNNEED_DEVICE_PATH_MAX_LEN 64 - -int -sunneed_listen(void); - -#endif +#ifndef _SUNNEED_LISTENER_H_ +#define _SUNNEED_LISTENER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "shared/sunneed_ipc.h" +#include "sunneed.h" +#include "sunneed_power.h" +#include "sunneed_proc.h" + +#define SUNNEED_MESSAGE_DEFAULT_BODY_SZ 64 +#define SUNNEED_MAX_IPC_CLIENTS 512 +#define SUNNEED_DEVICE_PATH_MAX_LEN 64 + +int +sunneed_listen(void); + +#endif diff --git a/src/sunneed_loader.c b/src/sunneed_loader.c index 8339b0f..4616356 100644 --- a/src/sunneed_loader.c +++ b/src/sunneed_loader.c @@ -1,250 +1,251 @@ -#include "sunneed_loader.h" - -/* Convenience array containing pointers to all locked file paths. It is generated as devices are loaded. */ -const char *locked_file_paths[MAX_LOCKED_FILES] = { NULL }; - -static void -add_locked_file_path(const char *path) { - for (int i = 0; i < MAX_LOCKED_FILES; i++) { - if (locked_file_paths[i] == NULL) { - LOG_I("Adding locked file path %d: '%s'", i, path); - locked_file_paths[i] = path; - return; - } - } - - LOG_E("No space left in locked file paths array."); - // TODO Real exit function. - exit(1); -} - -/** - * Assign to the `device_type_data` member of a device struct, choosing the correct union member based on what has been assigned - * to `device_type_kind`. - */ -static int -assign_device_type_data_field(struct sunneed_device *dev, void *data) { - // We're gonna dereference a void pointer. Try not to worry about it. - switch (dev->device_type_kind) { - case DEVICE_TYPE_FILE_LOCK: ; - // Copy each string from the data's `paths`. - struct sunneed_device_type_file_lock *file_lock = (struct sunneed_device_type_file_lock *)data; - dev->device_type_data.file_lock = (struct sunneed_device_type_file_lock *)malloc(sizeof(struct sunneed_device_type_file_lock) + sizeof(char *) * file_lock->len); - dev->device_type_data.file_lock->len = file_lock->len; - for (unsigned int i = 0; i < file_lock->len; i++) { - // Copy the string, adding a null terminator. - size_t len = strlen(file_lock->paths[i]); - dev->device_type_data.file_lock->paths[i] = malloc(len + 1); - strncpy(dev->device_type_data.file_lock->paths[i], file_lock->paths[i], len); - dev->device_type_data.file_lock->paths[i][len] = '\0'; - - add_locked_file_path(dev->device_type_data.file_lock->paths[i]); - } - break; - - default: - LOG_E("Invalid device kind %d", dev->device_type_kind); - return 1; - } - - return 0; -} - -static bool -is_object_file(char *path) { - size_t len = strlen(path); - if (strncmp(path + len - OBJ_EXTENSION_LEN, OBJ_EXTENSION, len) == 0) - return true; - else - return false; -} - -static int -load_device(const char *device_path, const char *device_name, int handle, struct sunneed_device *dev) { - int retval = 0; - - void *sym; - - // Load the object. - void *dlhandle = dlopen(device_path, RTLD_LAZY | RTLD_LOCAL); - if (!dlhandle) { - LOG_E("Error loading device from '%s': %s", device_name, dlerror()); - return 1; - } - - sym = dlsym(dlhandle, "device_flags"); - if (!sym) { - LOG_E("Failed to load device flags: %s", dlerror()); - retval = 1; - goto end; - } - unsigned int flags = *(unsigned int *)sym; - - // Set up device flags. - bool silent_fail = false; - if (flags) { - silent_fail = flags & SUNNEED_DEVICE_FLAG_SILENT_FAIL; - } - - char *identifier = malloc(DEVICE_IDENTIFIER_LEN); - if (!identifier) { - if (!silent_fail) LOG_E("Failed to allocate memory for device name."); - retval = 1; - goto end; - } - - sym = dlsym(dlhandle, "device_type_kind"); - if (!sym) { - if (!silent_fail) LOG_E("Getting `device_type_kind` failed: %s", dlerror()); - retval = 1; - goto end; - } - enum sunneed_device_type kind = *(enum sunneed_device_type *)sym; - - // Write to struct. - *dev = (struct sunneed_device) { - .is_ready = false, - .handle = handle, - .identifier = identifier, - .device_type_kind = kind - }; - strncpy(dev->identifier, device_name, DEVICE_IDENTIFIER_LEN); - dev->identifier[DEVICE_IDENTIFIER_LEN - 1] = '\0'; - - sym = dlsym(dlhandle, "init"); - if (!sym) { - if (!silent_fail) LOG_E("Getting `init` failed: %s", dlerror()); - retval = 1; - goto end; - } - int (*init)(void) = (int (*)(void))sym; - - // Call `init`, failing on nonzero. - int init_val = init(); - if (init_val != 0) { - if (!silent_fail && init_val > 0) LOG_E("`init` failed with %d", init_val); - retval = 1; - goto end; - } - - // Set up device type data. - sym = dlsym(dlhandle, "get_device_type_data"); - if (!sym) { - if (!silent_fail) LOG_E("Getting `get_device_type_data` failed: %s", dlerror()); - retval = 1; - goto end; - } - - // Obtain the pointer to the device type data. - void *data = ((void *(*)(void))sym)(); - if (!data) { - if (!silent_fail) LOG_E("No device type data returned!"); - retval = 1; - goto end; - } - - // Write that data to the appropriate union field. - if (assign_device_type_data_field(dev, data)) { - if (!silent_fail) LOG_E("Failed to assign to device type data field."); - retval = 1; - goto end; - } - - // Hooray! - dev->is_ready = true; - -end: - dlclose(dlhandle); - - // Clean up if error. - if (retval != 0) { - if (identifier) free(identifier); - } - - return retval; -} - -/* Loads all objects in the device directory as sunneed devices, storing them in the `target` array. */ -int -sunneed_load_devices(struct sunneed_device *target) { - int ret = 0, res; - - DIR *dir = opendir("build/device"); - struct dirent *ent; - - if (!dir) { - LOG_E("Failed to open devices directory"); - ret = 1; - goto end; - } - - unsigned int device_count = 0; - while ((ent = readdir(dir)) != NULL) { - if (strlen(ent->d_name) <= OBJ_EXTENSION_LEN) - // Can't be a real filename. - continue; - - // Strip the `.so` from the path to get the device name. - char device_name[DEVICE_PATH_LEN]; - strncpy(device_name, ent->d_name, strlen(ent->d_name) - OBJ_EXTENSION_LEN); - device_name[strlen(ent->d_name) - OBJ_EXTENSION_LEN] = '\0'; - - char device_path[DEVICE_PATH_LEN] = "build/device/"; - strncat(device_path, ent->d_name, DEVICE_PATH_LEN); - - if (is_object_file(device_path)) { - if ((res = load_device(device_path, device_name, device_count++, target)) != 0) - continue; - - LOG_I("Loaded device '%s'", device_name); - - target++; - } - } - - - -end: - closedir(dir); - - return ret; -} - -#ifdef TESTING - -int -TEST_load_device(void) { - int res; - - struct sunneed_device dev; - if ((res = load_device("build/device/test_file_lock.so", "test", 0, &dev)) != 0) - return set_sunneed_error(1, "`load_device` failed: %d", res); - - if (dev.handle != 0) - return set_sunneed_error(2, "invalid handle %d", dev.handle); - - if (strcmp(dev.identifier, "test") != 0) - return set_sunneed_error(3, "incorrect identifier '%s'", dev.identifier); - - if (strcmp(dev.device_type_data.file_lock->paths[0], TEST_FILE_LOCK_FILE_PATH) != 0) - return set_sunneed_error(4, "wrong file lock path '%s'", dev.device_type_data.file_lock->paths[0]); - - if (dev.device_type_data.file_lock->len != 1) - return set_sunneed_error(5, "wrong number of locked files %d", dev.device_type_data.file_lock->len); - - return 0; -} - -int -TEST_load_broken_device(void) { - int res; - - struct sunneed_device dev; - if ((res = load_device("build/device/test_broken.so", "test", 0, &dev)) == 0) - // Should fail. - return set_sunneed_error(1, "loading broken device didn't fail"); - - return 0; -} - -#endif +#include "sunneed_loader.h" + +/* Convenience array containing pointers to all locked file paths. It is generated as devices are loaded. */ +const char *locked_file_paths[MAX_LOCKED_FILES] = { NULL }; + +static void +add_locked_file_path(const char *path) { + for (int i = 0; i < MAX_LOCKED_FILES; i++) { + if (locked_file_paths[i] == NULL) { + LOG_I("Adding locked file path %d: '%s'", i, path); + locked_file_paths[i] = path; + return; + } + } + + LOG_E("No space left in locked file paths array."); + // TODO Real exit function. + exit(1); +} + +/** + * Assign to the `device_type_data` member of a device struct, choosing the correct union member based on what has been assigned + * to `device_type_kind`. + */ +static int +assign_device_type_data_field(struct sunneed_device *dev, void *data) { + // We're gonna dereference a void pointer. Try not to worry about it. + switch (dev->device_type_kind) { + case DEVICE_TYPE_FILE_LOCK: ; + // Copy each string from the data's `paths`. + struct sunneed_device_type_file_lock *file_lock = (struct sunneed_device_type_file_lock *)data; + dev->device_type_data.file_lock = (struct sunneed_device_type_file_lock *)malloc(sizeof(struct sunneed_device_type_file_lock) + sizeof(char *) * file_lock->len); + dev->device_type_data.file_lock->len = file_lock->len; + for (unsigned int i = 0; i < file_lock->len; i++) { + // Copy the string, adding a null terminator. + size_t len = strlen(file_lock->paths[i]); + dev->device_type_data.file_lock->paths[i] = malloc(len + 1); + strncpy(dev->device_type_data.file_lock->paths[i], file_lock->paths[i], len); + dev->device_type_data.file_lock->paths[i][len] = '\0'; + + add_locked_file_path(dev->device_type_data.file_lock->paths[i]); + } + break; + + default: + LOG_E("Invalid device kind %d", dev->device_type_kind); + return 1; + } + + return 0; +} + +static bool +is_object_file(char *path) { + size_t len = strlen(path); + if (strncmp(path + len - OBJ_EXTENSION_LEN, OBJ_EXTENSION, len) == 0) + return true; + else + return false; +} + +static int +load_device(const char *device_path, const char *device_name, int handle, struct sunneed_device *dev) { + int retval = 0; + + void *sym; + + // Load the object. + void *dlhandle = dlopen(device_path, RTLD_LAZY | RTLD_LOCAL); + if (!dlhandle) { + LOG_E("Error loading device from '%s': %s", device_name, dlerror()); + return 1; + } + + sym = dlsym(dlhandle, "device_flags"); + if (!sym) { + LOG_E("Failed to load device flags: %s", dlerror()); + retval = 1; + goto end; + } + unsigned int flags = *(unsigned int *)sym; + + // Set up device flags. + bool silent_fail = false; + if (flags) { + silent_fail = flags & SUNNEED_DEVICE_FLAG_SILENT_FAIL; + } + + char *identifier = malloc(DEVICE_IDENTIFIER_LEN); + if (!identifier) { + if (!silent_fail) LOG_E("Failed to allocate memory for device name."); + retval = 1; + goto end; + } + + sym = dlsym(dlhandle, "device_type_kind"); + if (!sym) { + if (!silent_fail) LOG_E("Getting `device_type_kind` failed: %s", dlerror()); + retval = 1; + goto end; + } + enum sunneed_device_type kind = *(enum sunneed_device_type *)sym; + + // Write to struct. + *dev = (struct sunneed_device) { + .is_ready = false, + .handle = handle, + .identifier = identifier, + .device_type_kind = kind + }; + strncpy(dev->identifier, device_name, DEVICE_IDENTIFIER_LEN); + dev->identifier[DEVICE_IDENTIFIER_LEN - 1] = '\0'; + + sym = dlsym(dlhandle, "init"); + if (!sym) { + if (!silent_fail) LOG_E("Getting `init` failed: %s", dlerror()); + retval = 1; + goto end; + } + int (*init)(void) = (int (*)(void))sym; + + // Call `init`, failing on nonzero. + int init_val = init(); + if (init_val != 0) { + if (!silent_fail && init_val > 0) LOG_E("`init` failed with %d", init_val); + retval = 1; + goto end; + } + + // Set up device type data. + sym = dlsym(dlhandle, "get_device_type_data"); + if (!sym) { + if (!silent_fail) LOG_E("Getting `get_device_type_data` failed: %s", dlerror()); + retval = 1; + goto end; + } + + // Obtain the pointer to the device type data. + void *data = ((void *(*)(void))sym)(); + if (!data) { + if (!silent_fail) LOG_E("No device type data returned!"); + retval = 1; + goto end; + } + + // Write that data to the appropriate union field. + if (assign_device_type_data_field(dev, data)) { + if (!silent_fail) LOG_E("Failed to assign to device type data field."); + retval = 1; + goto end; + } + + // Hooray! + dev->is_ready = true; + +end: + dlclose(dlhandle); + + // Clean up if error. + if (retval != 0) { + if (identifier) free(identifier); + }else if (identifier) free(identifier); + + + return retval; +} + +/* Loads all objects in the device directory as sunneed devices, storing them in the `target` array. */ +int +sunneed_load_devices(struct sunneed_device *target) { + int ret = 0, res; + + DIR *dir = opendir("build/device"); + struct dirent *ent; + + if (!dir) { + LOG_E("Failed to open devices directory"); + ret = 1; + goto end; + } + + unsigned int device_count = 0; + while ((ent = readdir(dir)) != NULL) { + if (strlen(ent->d_name) <= OBJ_EXTENSION_LEN) + // Can't be a real filename. + continue; + + // Strip the `.so` from the path to get the device name. + char device_name[DEVICE_PATH_LEN]; + strncpy(device_name, ent->d_name, strlen(ent->d_name) - OBJ_EXTENSION_LEN); + device_name[strlen(ent->d_name) - OBJ_EXTENSION_LEN] = '\0'; + + char device_path[DEVICE_PATH_LEN] = "build/device/"; + strncat(device_path, ent->d_name, DEVICE_PATH_LEN); + + if (is_object_file(device_path)) { + if ((res = load_device(device_path, device_name, device_count++, target)) != 0) + continue; + + LOG_I("Loaded device '%s'", device_name); + + target++; + } + } + + + +end: + closedir(dir); + + return ret; +} + +#ifdef TESTING + +int +TEST_load_device(void) { + int res; + + struct sunneed_device dev; + if ((res = load_device("build/device/test_file_lock.so", "test", 0, &dev)) != 0) + return set_sunneed_error(1, "`load_device` failed: %d", res); + + if (dev.handle != 0) + return set_sunneed_error(2, "invalid handle %d", dev.handle); + + if (strcmp(dev.identifier, "test") != 0) + return set_sunneed_error(3, "incorrect identifier '%s'", dev.identifier); + + if (strcmp(dev.device_type_data.file_lock->paths[0], TEST_FILE_LOCK_FILE_PATH) != 0) + return set_sunneed_error(4, "wrong file lock path '%s'", dev.device_type_data.file_lock->paths[0]); + + if (dev.device_type_data.file_lock->len != 1) + return set_sunneed_error(5, "wrong number of locked files %d", dev.device_type_data.file_lock->len); + + return 0; +} + +int +TEST_load_broken_device(void) { + int res; + + struct sunneed_device dev; + if ((res = load_device("build/device/test_broken.so", "test", 0, &dev)) == 0) + // Should fail. + return set_sunneed_error(1, "loading broken device didn't fail"); + + return 0; +} + +#endif diff --git a/src/sunneed_loader.h b/src/sunneed_loader.h index 7775414..79786e7 100644 --- a/src/sunneed_loader.h +++ b/src/sunneed_loader.h @@ -1,32 +1,32 @@ -#ifndef _SUNNEED_LOADER_H_ -#define _SUNNEED_LOADER_H_ - -#include -#include -#include - -#include "sunneed_device.h" - -#define DEVICE_PATH_LEN 64 - -#define OBJ_EXTENSION ".so" -#define OBJ_EXTENSION_LEN 3 - -int -sunneed_load_devices(struct sunneed_device *target); - -#ifdef TESTING - -#include "shared/sunneed_testing.h" -#include "sunneed_test.h" - -int TEST_load_device(void); -int TEST_load_broken_device(void); - -#define SUNNEED_RUNTIME_TESTS_LOADER \ - TEST_load_device, \ - TEST_load_broken_device - -#endif - -#endif +#ifndef _SUNNEED_LOADER_H_ +#define _SUNNEED_LOADER_H_ + +#include +#include +#include + +#include "sunneed_device.h" + +#define DEVICE_PATH_LEN 64 + +#define OBJ_EXTENSION ".so" +#define OBJ_EXTENSION_LEN 3 + +int +sunneed_load_devices(struct sunneed_device *target); + +#ifdef TESTING + +#include "shared/sunneed_testing.h" +#include "sunneed_test.h" + +int TEST_load_device(void); +int TEST_load_broken_device(void); + +#define SUNNEED_RUNTIME_TESTS_LOADER \ + TEST_load_device, \ + TEST_load_broken_device + +#endif + +#endif diff --git a/src/sunneed_power.c b/src/sunneed_power.c index 96b356f..79e73e6 100644 --- a/src/sunneed_power.c +++ b/src/sunneed_power.c @@ -1,151 +1,165 @@ -#include "sunneed_power.h" - -extern struct sunneed_tenant tenants[]; - -static struct { - // Index. - int id; - - // Power at the start of the quantum. - int present_power; - - // Timestamp for the start of the quantum. - struct timespec begin_time; - - // Whether events can be recorded to this quantum. - bool is_active; - - // If a power event has occurred in this quantum. - bool has_power_event; -} current_quantum = {-1, 0.0, {0}, false, false}; - -int -sunneed_record_power_usage_event(struct sunneed_power_usage_event ev) { - if (!current_quantum.is_active) { - LOG_E("Cannot record a power event outside of an active quantum"); - return 1; - } - - struct sunneed_power_usage_event *cur = power_usage_evs; - if (cur == NULL) { - LOG_E("Power usage events head node is unallocated!"); - return 1; - } - - // Find tail of events list. - while (cur->next != NULL) { - cur = cur->next; - } - - // Copy given event to heap and attach to end of events list. - cur->next = malloc(sizeof(struct sunneed_power_usage_event)); - if (!cur->next) { - LOG_E("Failed to allocate space for power usage event."); - return 1; - } - - *cur->next = ev; - - current_quantum.has_power_event = true; - - return 0; -} - -int -sunneed_quantum_begin(void) { - LOG_D("Begin start procedure for quantum %d", current_quantum.id + 1); - - // Set quantum metadata. - current_quantum.id++; - current_quantum.present_power = present_power(); - timespec_get(¤t_quantum.begin_time, TIME_UTC); - - // Reset power events array. - struct sunneed_power_usage_event *cur = power_usage_evs; - while (cur != NULL) { - struct sunneed_power_usage_event *next = cur->next; - free(cur); - cur = next; - } - - power_usage_evs = (struct sunneed_power_usage_event*) malloc(sizeof(struct sunneed_power_usage_event)); - - if (!power_usage_evs) { - LOG_E("Failed to allocate space for power usage events!"); - return 1; - } - power_usage_evs->next = NULL; - - LOG_I("Started quantum %d", current_quantum.id); - current_quantum.is_active = true; - - return 0; -} - -int -sunneed_quantum_end(void) { - LOG_I("Ending quantum %d", current_quantum.id); - - current_quantum.is_active = false; - - double power_consumed[MAX_TENANTS] = {0.0}; - - // Add up power used in this quantum by each tenant. - struct sunneed_power_usage_event *ev = power_usage_evs; - while (ev != NULL) { - if (!current_quantum.has_power_event) - // No power events to add to tenant. - break; - - if (ev->ev.device == NULL) { - // This is a CPU usage digest. - // TODO Check CPU usage lol. - power_consumed[ev->ev.tenant->id] += 0; - } else { - // TODO Get power from event. - power_consumed[ev->ev.tenant->id] += 0; - } - ev = ev->next; - } - - float unscaled_proportions[MAX_TENANTS] = {0.0}; - float unscaled_sum = 0.0; - - // Update power proportions for tenants. - - // First, get the percentage of their given power that each tenant used. - for (int i = 0; i < MAX_TENANTS; i++) { - unscaled_proportions[i] - = 1.0 - power_consumed[i] / (current_quantum.present_power * tenants[i].power_proportion); - unscaled_sum += unscaled_proportions[i]; - } - - // Multiply the total percentage by a scaling factor such that the sum of new proportions adds up to 1. - float scale_factor = 1.0 / unscaled_sum; - for (int i = 0; i < MAX_TENANTS; i++) { - tenants[i].power_proportion = unscaled_proportions[i] * scale_factor; - } - - LOG_D("Finished ending quantum %d", current_quantum.id); - - return 0; -} - -sunneed_worker_thread_result_t -sunneed_quantum_worker(__attribute__((unused)) void *args) { - int ret; - power_usage_evs = NULL; - while (true) { - if ((ret = sunneed_quantum_begin()) != 0) { - goto end; - } - usleep(QUANTUM_DURATION_MS * 1000); - if ((ret = sunneed_quantum_end()) != 0) { - goto end; - } - } - -end: - LOG_E("Error with quantum; quantum thread stopping"); - return NULL; -} +#include "sunneed_power.h" + +extern struct sunneed_tenant tenants[]; + +static struct { + // Index. + int id; + + // Power at the start of the quantum. + int present_power; + + // Timestamp for the start of the quantum. + struct timespec begin_time; + + // Whether events can be recorded to this quantum. + bool is_active; + + // If a power event has occurred in this quantum. + bool has_power_event; +} current_quantum = {-1, 0.0, {0}, false, false}; + +int +sunneed_record_power_usage_event(struct sunneed_power_usage_event ev) { + if (!current_quantum.is_active) { + LOG_E("Cannot record a power event outside of an active quantum"); + return 1; + } + + struct sunneed_power_usage_event *cur = power_usage_evs; + if (cur == NULL) { + LOG_E("Power usage events head node is unallocated!"); + return 1; + } + + // Find tail of events list. + while (cur->next != NULL) { + cur = cur->next; + } + + // Copy given event to heap and attach to end of events list. + cur->next = malloc(sizeof(struct sunneed_power_usage_event)); + if (!cur->next) { + LOG_E("Failed to allocate space for power usage event."); + return 1; + } + *cur->next = ev; + + current_quantum.has_power_event = true; + + return 0; +} + +int +sunneed_quantum_begin(void) { + LOG_D("Begin start procedure for quantum %d", current_quantum.id + 1); + + // Set quantum metadata. + current_quantum.id++; + +#ifdef LOG_PWR + current_quantum.present_power = present_power(); +#endif + + timespec_get(¤t_quantum.begin_time, TIME_UTC); + + // Reset power events array. + struct sunneed_power_usage_event *cur = power_usage_evs; + while (cur != NULL) { + struct sunneed_power_usage_event *next = cur->next; + free(cur); + cur = next; + } + + power_usage_evs = malloc(sizeof(struct sunneed_power_usage_event)); + if (!power_usage_evs) { + LOG_E("Failed to allocate space for power usage events!"); + return 1; + } + + power_usage_evs->next = NULL; + + LOG_I("Started quantum %d", current_quantum.id); + current_quantum.is_active = true; + + return 0; +} + +int +sunneed_quantum_end(void) { + LOG_I("Ending quantum %d", current_quantum.id); + + current_quantum.is_active = false; + + double power_consumed[MAX_TENANTS] = {0.0}; + + // Add up power used in this quantum by each tenant. + struct sunneed_power_usage_event *ev = power_usage_evs; + while (ev != NULL) { + if (!current_quantum.has_power_event) + // No power events to add to tenant. + break; + + if (ev->ev.device == NULL) { + // This is a CPU usage digest. + // TODO Check CPU usage lol. + power_consumed[ev->ev.tenant->id] += 0; + } else { + // TODO Get power from event. + power_consumed[ev->ev.tenant->id] += 0; + } + ev = ev->next; + } + + float unscaled_proportions[MAX_TENANTS] = {0.0}; + float unscaled_sum = 0.0; + + // Update power proportions for tenants. + + // First, get the percentage of their given power that each tenant used. + for (int i = 0; i < MAX_TENANTS; i++) { + unscaled_proportions[i] + = 1.0 - power_consumed[i] / (current_quantum.present_power * tenants[i].power_proportion); + unscaled_sum += unscaled_proportions[i]; + } + + // Multiply the total percentage by a scaling factor such that the sum of new proportions adds up to 1. + float scale_factor = 1.0 / unscaled_sum; + for (int i = 0; i < MAX_TENANTS; i++) { + tenants[i].power_proportion = unscaled_proportions[i] * scale_factor; + } + + LOG_D("Finished ending quantum %d", current_quantum.id); + + return 0; +} + +sunneed_worker_thread_result_t +sunneed_quantum_worker(__attribute__((unused)) void *args) { + int ret; + power_usage_evs = NULL; + while (true) { + if ((ret = sunneed_quantum_begin()) != 0) { + goto end; + } + +//cant have sleeps when trying to log power usage - will mess with clock() function +#ifndef LOG_PWR + usleep(QUANTUM_DURATION_MS * 1000); +#endif + +#ifdef LOG_PWR + clock_t delay_start = clock(); + + while(((double)(clock() - delay_start) / CLOCKS_PER_SEC) < QUANTUM_DURATION_MS); +#endif + + if ((ret = sunneed_quantum_end()) != 0) { + goto end; + } + } + +end: + LOG_E("Error with quantum; quantum thread stopping"); + return NULL; +} diff --git a/src/sunneed_power.h b/src/sunneed_power.h index 8bba5a6..a59ec88 100644 --- a/src/sunneed_power.h +++ b/src/sunneed_power.h @@ -1,53 +1,62 @@ -#ifndef _SUNNEED_POWER_H_ -#define _SUNNEED_POWER_H_ - -#include -#include -#include -#include -#include - -#include "log.h" -#include "shared/sunneed_pip_interface.h" -#include "sunneed.h" -#include "sunneed_device.h" -#include "sunneed_proc.h" - -#define MAX_DEVICES 64 - -#define QUANTUMS_RINGBUF_SZ 16 - -// TODO This is waaaaaaaaaaaaaaaaaaaaay too big. -#define QUANTUM_DURATION_MS 5000 - -struct sunneed_power_usage_event { - struct { - // The moment the power event occurred. - struct timespec timestamp; - - struct sunneed_tenant *tenant; - - // If NULL, then the power event is a CPU usage digest. - struct sunneed_device *device; - - // Unused for now, can configure parameters of the device interaction. - void *args; - } ev; - struct sunneed_power_usage_event *next; -}; - -int -sunneed_record_power_usage_event(struct sunneed_power_usage_event ev); -int -sunneed_quantum_begin(void); -int -sunneed_quantum_end(void); - -sunneed_worker_thread_result_t -sunneed_quantum_worker(void *args); - -struct sunneed_power_usage_event *power_usage_evs; - -struct sunneed_device devices[MAX_DEVICES]; - -#endif +#ifndef _SUNNEED_POWER_H_ +#define _SUNNEED_POWER_H_ + +#include +#include +#include + +#include "log.h" +#include "shared/sunneed_pip_interface.h" +#include "sunneed.h" +#include "sunneed_device.h" +#include "sunneed_proc.h" + +#define MAX_DEVICES 64 + +#define QUANTUMS_RINGBUF_SZ 16 + +#define PASSIVE_PWR_PER_SEC 0.07667 + +#define PASSIVE_PWR_PER_MIN PASSIVE_PWR_PER_SEC * 60 + +#ifdef LOG_PWR +#define REQS_PER_LOG 10 +int16_t last_read, curr_read, last_capacity, curr_capacity, reqs_since_last_log; +clock_t last_send, time_since_send; +#endif + + +// TODO This is waaaaaaaaaaaaaaaaaaaaay too big. +#define QUANTUM_DURATION_MS 5000 + +struct sunneed_power_usage_event { + struct { + // The moment the power event occurred. + struct timespec timestamp; + + struct sunneed_tenant *tenant; + + // If NULL, then the power event is a CPU usage digest. + struct sunneed_device *device; + + // Unused for now, can configure parameters of the device interaction. + void *args; + } ev; + struct sunneed_power_usage_event *next; +}; + +int +sunneed_record_power_usage_event(struct sunneed_power_usage_event ev); +int +sunneed_quantum_begin(void); +int +sunneed_quantum_end(void); + +sunneed_worker_thread_result_t +sunneed_quantum_worker(void *args); + +struct sunneed_power_usage_event *power_usage_evs; + +struct sunneed_device devices[MAX_DEVICES]; + +#endif diff --git a/src/sunneed_proc.c b/src/sunneed_proc.c index 879701b..0adf15b 100644 --- a/src/sunneed_proc.c +++ b/src/sunneed_proc.c @@ -1,226 +1,253 @@ -#include "sunneed_proc.h" - -struct sunneed_tenant tenants[MAX_TENANTS]; - -int -sunneed_init_tenants(void) { - for (int i = 0; i < MAX_TENANTS; i++) { - tenants[i] = (struct sunneed_tenant){.id = i, .pid = 0, .power_proportion = 0.0, .is_active = false}; - } - - return 0; -} - -// Find an unused spot for a tenant and register ourself there. -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid) { - struct sunneed_tenant *tenant = NULL; - for (int i = 0; i < MAX_TENANTS; i++) { - if (!tenants[i].is_active) { - tenant = &tenants[i]; - break; - } - } - - if (tenant == NULL) { - LOG_E("Sorry PID %d, can't spawn any more tenants!", pid); - return NULL; - } - - tenant->pid = pid; - tenant->is_active = true; - - return tenant; -} - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant) { - if (tenant == NULL || !tenant->is_active) { - LOG_W("Cannot deactivate an inactive tenant"); - return 1; - } - - tenant->is_active = false; - return 0; -} - -void -unregister_lost_tenant(struct sunneed_tenant* tenant) -{ - bool cleared = false; - for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { - if (tenant_pipes[i].tenant->id == tenant->id) { - LOG_D("Removing mapping from pipe %d to tenant %d", tenant_pipes[i].pipe.id, tenant->id); - tenant_pipes[i].tenant = NULL; - tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; - cleared = true; - break; - } - } - if (!cleared) { - LOG_E("No mapping cleared when unregistering tenant %d", tenant->id); - return; - } - if (sunneed_tenant_unregister(tenant) != 0) { - LOG_E("Failed to unregister tenant %d", tenant->id); - } -} - -unsigned int -sunneed_get_num_tenants(void) { - unsigned int num_tenants = 0; - - for (int i = 0; i < MAX_TENANTS; i++) { - if (tenants[i].is_active) - num_tenants++; - } - - return num_tenants; -} - -int -sunneed_update_tenant_cpu_usage(void) { - // TODO Verify that the pipe and PID match up. It is entirely possible for the tenant process to die and a new - // process with the same PID as the tenant's to start up. - FILE *file; - char filepath[FILENAME_MAX] = "/proc/stat"; - - file = fopen(filepath, "r"); - fscanf(file, "%*s %llu %llu %llu %llu", &cpu_usage.user, &cpu_usage.nice, &cpu_usage.sys, &cpu_usage.idle); - fclose(file); - - for (struct sunneed_tenant *tenant = tenants; tenant < tenants + MAX_TENANTS; tenant++) { - if (!tenant->is_active) - continue; - - snprintf(filepath, FILENAME_MAX, "/proc/%d/stat", tenant->pid); - if (access(filepath, F_OK) != 0) { - LOG_E("Unable to find procfs file for PID %d; most likely a tenant ended but forgot to tell us", - tenant->pid); - LOG_E("Unregistering tenant %d (PID %d)",tenant->id, tenant->pid); - unregister_lost_tenant(tenant); - continue; - } - - file = fopen(filepath, "r"); - // Read CPU consumption from this tenant's PID. - fscanf(file, - "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" // 13 things we don't care about - " %llu %llu" // usertime, systemtime - " %*d %*d %*d %*d %*d %*d %*u %*u", // 8 things we don't care about - &cpu_usage.tenants[tenant->id].user, &cpu_usage.tenants[tenant->id].sys); - fclose(file); - } - - return 0; -} - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id) { - if (!tenants[tenant_id].is_active) { - LOG_E("Attempt to get CPU usage of inactive tenant %d", tenant_id); - return -1; - } - - // TODO Shit - - return 0; -} - -sunneed_worker_thread_result_t -sunneed_proc_monitor(__attribute__((unused)) void *args) { - int ret; - while (true) { - LOG_D("Updating process CPU usage"); - if ((ret = sunneed_update_tenant_cpu_usage()) != 0) { - LOG_E("Error updating CPU usage; monitor thread stopping"); - return NULL; - } - - sleep(5); - } -} - -sunneed_worker_thread_result_t -sunneed_stepperMotor_driver(__attribute__((unused)) void *args) { - int status, stepper_driver_new_stdin; - const char *path = "/tmp/stepper"; - const char *executable_path = "ext/SunneeD_dev_drivers/StepperDriver/stepper_driver"; - - if ( (sunneed_stepper_driver_pid = fork()) == 0) { - if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { - /* check if pipe already exists */ - if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { - /* cound't create pipe & it doesn't already exist */ - LOG_E("Error creating pipe to stepper driver"); - exit(1); - } - } else { - if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { - /* pipe exists, couldn't open it */ - LOG_E("Error opening pipe to stepper driver"); - exit(1); - } - } - - if (close(0) == -1) { - LOG_E("error closing stdin for stepper driver"); - exit(1); - } - if (dup2(stepper_driver_new_stdin, 0) == -1) { - LOG_E("error duping new stdin for stepper driver"); - exit(1); - } - if (close(stepper_driver_new_stdin) == -1) { - LOG_E("error closing stepper driver's 2nd ref to new stdin after dup"); - exit(1); - } - execl(executable_path, executable_path, NULL); - /* should never get here -- stepper driver runs on loop */ - exit(1); - } else { - wait(&status); - LOG_E("Stepper driver exited - status=%d, errno=%s", status, strerror(errno)); - return NULL; - } -} - -sunneed_worker_thread_result_t -sunneed_camera_driver(__attribute__((unused)) void *args) { - int wait_status, cam_driver_new_stdin; - const char *path = "/tmp/camera"; - const char *executable_path = "run_PyDriver"; - - if ( (sunneed_camera_driver_pid = fork()) == 0) { - if ( (cam_driver_new_stdin = open(path, O_RDONLY)) == -1) { - if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { - LOG_E("Could not create camera device fd"); - exit(1); - } - if (chmod(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { - LOG_E("Could not set permissions for camera device fd"); - exit(1); - } - cam_driver_new_stdin = open(path, O_RDONLY); - } - if (close(0) == -1) { - LOG_E("Error closing camera driver stdin"); - exit(1); - } - if (dup2(cam_driver_new_stdin, 0) == -1) { - LOG_E("Error duping camera device fd to camera driver stdin"); - exit(1); - } - if (close(cam_driver_new_stdin) == -1) { - LOG_E("Error closing camera driver's unused reference to camera fd"); - exit(1); - } - execl(executable_path, executable_path, NULL); - - exit(1); - } else { - wait(&wait_status); - LOG_E("Error executing camera driver: status=%d\n",wait_status); - return NULL; - } -} \ No newline at end of file +#include "sunneed_proc.h" + + +struct sunneed_tenant tenants[MAX_TENANTS]; + +static enum battery_state battery_state; + + +int +sunneed_init_tenants(void) { + for (int i = 0; i < MAX_TENANTS; i++) { + tenants[i] = (struct sunneed_tenant){.id = i, .pid = 0, .power_proportion = 0.0, .is_active = false}; + } + + return 0; +} + +// Find an unused spot for a tenant and register ourself there. +struct sunneed_tenant * +sunneed_tenant_register(pid_t pid) { + struct sunneed_tenant *tenant = NULL; + for (int i = 0; i < MAX_TENANTS; i++) { + if (!tenants[i].is_active) { + tenant = &tenants[i]; + break; + } + } + + if (tenant == NULL) { + LOG_E("Sorry PID %d, can't spawn any more tenants!", pid); + return NULL; + } + + tenant->pid = pid; + tenant->is_active = true; + + return tenant; +} + +int +sunneed_tenant_unregister(struct sunneed_tenant *tenant) { + if (tenant == NULL || !tenant->is_active) { + LOG_W("Cannot deactivate an inactive tenant"); + return 1; + } + + tenant->is_active = false; + return 0; +} + +void +unregister_lost_tenant(struct sunneed_tenant* tenant) +{ + bool cleared = false; + for (int i = 0; i < SUNNEED_MAX_IPC_CLIENTS; i++) { + if (tenant_pipes[i].tenant->id == tenant->id) { + LOG_D("Removing mapping from pipe %d to tenant %d", tenant_pipes[i].pipe.id, tenant->id); + tenant_pipes[i].tenant = NULL; + tenant_pipes[i].pipe = (nng_pipe)NNG_PIPE_INITIALIZER; + cleared = true; + break; + } + } + if (!cleared) { + LOG_E("No mapping cleared when unregistering tenant %d", tenant->id); + return; + } + if (sunneed_tenant_unregister(tenant) != 0) { + LOG_E("Failed to unregister tenant %d", tenant->id); + } +} + +unsigned int +sunneed_get_num_tenants(void) { + unsigned int num_tenants = 0; + + for (int i = 0; i < MAX_TENANTS; i++) { + if (tenants[i].is_active) + num_tenants++; + } + + return num_tenants; +} + +int +sunneed_update_tenant_cpu_usage(void) { + // TODO Verify that the pipe and PID match up. It is entirely possible for the tenant process to die and a new + // process with the same PID as the tenant's to start up. + FILE *file; + char filepath[FILENAME_MAX] = "/proc/stat"; + + file = fopen(filepath, "r"); + fscanf(file, "%*s %llu %llu %llu %llu", &cpu_usage.user, &cpu_usage.nice, &cpu_usage.sys, &cpu_usage.idle); + fclose(file); + + for (struct sunneed_tenant *tenant = tenants; tenant < tenants + MAX_TENANTS; tenant++) { + if (!tenant->is_active) + continue; + + snprintf(filepath, FILENAME_MAX, "/proc/%d/stat", tenant->pid); + if (access(filepath, F_OK) != 0) { + LOG_E("Unable to find procfs file for PID %d; most likely a tenant ended but forgot to tell us", + tenant->pid); + LOG_E("Unregistering tenant %d (PID %d)",tenant->id, tenant->pid); + unregister_lost_tenant(tenant); + continue; + } + + file = fopen(filepath, "r"); + // Read CPU consumption from this tenant's PID. + fscanf(file, + "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u" // 13 things we don't care about + " %llu %llu" // usertime, systemtime + " %*d %*d %*d %*d %*d %*d %*u %*u", // 8 things we don't care about + &cpu_usage.tenants[tenant->id].user, &cpu_usage.tenants[tenant->id].sys); + fclose(file); + } + + return 0; +} + +int +sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id) { + if (!tenants[tenant_id].is_active) { + LOG_E("Attempt to get CPU usage of inactive tenant %d", tenant_id); + return -1; + } + + // TODO Shit + + return 0; +} + +sunneed_worker_thread_result_t +sunneed_proc_monitor(__attribute__((unused)) void *args) { + int ret, last_soc, curr_soc; + last_soc = bq27441_soc_unfiltered(); + while (true) { + LOG_D("Updating process CPU usage"); + if ((ret = sunneed_update_tenant_cpu_usage()) != 0) { + LOG_E("Error updating CPU usage; monitor thread stopping"); + return NULL; + } + + //check and update battery state + curr_soc = bq27441_soc_unfiltered(); + if(curr_soc > 90) + { + battery_state = FULL; + }else if(curr_soc > last_soc){ + battery_state = CHARGING; + }else if(curr_soc > 50){ + //51 - 90% charge using power + battery_state = DISCHARGING; + }else if(curr_soc >= 20){ + //20 - 49% charge + battery_state = HALF; + }else if(curr_soc >= 5){ + //5 - 19% charge + battery_state = LOW; + }else{ + //less than 5 % charge + battery_state = EMPTY; + } + LOG_D("battery state: %s\n", battery_state); + + sleep(5); + } +} + +sunneed_worker_thread_result_t +sunneed_stepperMotor_driver(__attribute__((unused)) void *args) { + int status, stepper_driver_new_stdin; + const char *path = "/tmp/stepper"; + const char *executable_path = "ext/SunneeD_dev_drivers/StepperDriver/stepper_driver"; + + if ( (sunneed_stepper_driver_pid = fork()) == 0) { + if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { + /* check if pipe already exists */ + if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { + /* cound't create pipe & it doesn't already exist */ + LOG_E("Error creating pipe to stepper driver"); + exit(1); + } + } else { + if ((stepper_driver_new_stdin = open(path, O_RDONLY)) == -1) { + /* pipe exists, couldn't open it */ + LOG_E("Error opening pipe to stepper driver"); + exit(1); + } + } + + if (close(0) == -1) { + LOG_E("error closing stdin for stepper driver"); + exit(1); + } + if (dup2(stepper_driver_new_stdin, 0) == -1) { + LOG_E("error duping new stdin for stepper driver"); + exit(1); + } + if (close(stepper_driver_new_stdin) == -1) { + LOG_E("error closing stepper driver's 2nd ref to new stdin after dup"); + exit(1); + } + execl(executable_path, executable_path, NULL); + /* should never get here -- stepper driver runs on loop */ + exit(1); + } else { + wait(&status); + LOG_E("Stepper driver exited - status=%d, errno=%s", status, strerror(errno)); + return NULL; + } +} + +sunneed_worker_thread_result_t +sunneed_camera_driver(__attribute__((unused)) void *args) { + int wait_status, cam_driver_new_stdin; + const char *path = "/tmp/camera"; + const char *executable_path = "run_PyDriver"; + + if ( (sunneed_camera_driver_pid = fork()) == 0) { + if ( (cam_driver_new_stdin = open(path, O_RDONLY)) == -1) { + if (mkfifo(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { + LOG_E("Could not create camera device fd"); + exit(1); + } + if (chmod(path, S_IRUSR | S_IWUSR | S_IWOTH) == -1) { + LOG_E("Could not set permissions for camera device fd"); + exit(1); + } + cam_driver_new_stdin = open(path, O_RDONLY); + } + if (close(0) == -1) { + LOG_E("Error closing camera driver stdin"); + exit(1); + } + if (dup2(cam_driver_new_stdin, 0) == -1) { + LOG_E("Error duping camera device fd to camera driver stdin"); + exit(1); + } + if (close(cam_driver_new_stdin) == -1) { + LOG_E("Error closing camera driver's unused reference to camera fd"); + exit(1); + } + execl(executable_path, executable_path, NULL); + + exit(1); + } else { + wait(&wait_status); + LOG_E("Error executing camera driver: status=%d\n",wait_status); + return NULL; + } +} diff --git a/src/sunneed_proc.h b/src/sunneed_proc.h index b5614bb..c39785f 100644 --- a/src/sunneed_proc.h +++ b/src/sunneed_proc.h @@ -1,66 +1,75 @@ -#ifndef _SUNNEED_PROC_H_ -#define _SUNNEED_PROC_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "sunneed.h" - -#define MAX_TENANTS 4 - -typedef unsigned int sunneed_tenant_id_t; - -// TODO Separate tenants from processes. -struct sunneed_tenant { - sunneed_tenant_id_t id; - pid_t pid; - float power_proportion; - bool is_active; -}; - -struct tenant_cpu_usage { - unsigned long long user, sys; -}; - -struct { - unsigned long long user, nice, sys, idle; - struct tenant_cpu_usage tenants[MAX_TENANTS]; -} cpu_usage; - -int sunneed_stepper_driver_pid, sunneed_camera_driver_pid; - -int -sunneed_update_tenant_cpu_usage(void); - -int -sunneed_init_tenants(void); - -struct sunneed_tenant * -sunneed_tenant_register(pid_t pid); - -int -sunneed_tenant_unregister(struct sunneed_tenant *tenant); - -unsigned int -sunneed_get_num_tenants(void); - -int -sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id); - -sunneed_worker_thread_result_t -sunneed_proc_monitor(void *args); - -sunneed_worker_thread_result_t -sunneed_stepperMotor_driver(void *args); - -sunneed_worker_thread_result_t -sunneed_camera_driver(void *args); - -#endif +#ifndef _SUNNEED_PROC_H_ +#define _SUNNEED_PROC_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "sunneed.h" + +#define MAX_TENANTS 4 + +typedef unsigned int sunneed_tenant_id_t; + +// TODO Separate tenants from processes. +struct sunneed_tenant { + sunneed_tenant_id_t id; + pid_t pid; + float power_proportion; + bool is_active; +}; + +struct tenant_cpu_usage { + unsigned long long user, sys; +}; + +struct { + unsigned long long user, nice, sys, idle; + struct tenant_cpu_usage tenants[MAX_TENANTS]; +} cpu_usage; + +enum battery_state { + FULL, + CHARGING, + DISCHARGING, + HALF, + LOW, + EMPTY +}; + +int sunneed_stepper_driver_pid, sunneed_camera_driver_pid; + +int +sunneed_update_tenant_cpu_usage(void); + +int +sunneed_init_tenants(void); + +struct sunneed_tenant * +sunneed_tenant_register(pid_t pid); + +int +sunneed_tenant_unregister(struct sunneed_tenant *tenant); + +unsigned int +sunneed_get_num_tenants(void); + +int +sunneed_get_tenant_cpu_usage(sunneed_tenant_id_t tenant_id); + +sunneed_worker_thread_result_t +sunneed_proc_monitor(void *args); + +sunneed_worker_thread_result_t +sunneed_stepperMotor_driver(void *args); + +sunneed_worker_thread_result_t +sunneed_camera_driver(void *args); + +#endif diff --git a/src/sunneed_runtime_test_collection.h b/src/sunneed_runtime_test_collection.h index 405714e..bf95377 100644 --- a/src/sunneed_runtime_test_collection.h +++ b/src/sunneed_runtime_test_collection.h @@ -1,10 +1,10 @@ -// This is here so `sunneed_core` can access it. -extern char sunneed_runtime_test_error[]; - -/* - ********************************************************************* - * Place your collection of tests to run in the RUNTIME_TESTS macro. * - ********************************************************************* - */ -#include "sunneed_loader.h" -#define RUNTIME_TESTS { SUNNEED_RUNTIME_TESTS_LOADER } +// This is here so `sunneed_core` can access it. +extern char sunneed_runtime_test_error[]; + +/* + ********************************************************************* + * Place your collection of tests to run in the RUNTIME_TESTS macro. * + ********************************************************************* + */ +#include "sunneed_loader.h" +#define RUNTIME_TESTS { SUNNEED_RUNTIME_TESTS_LOADER } diff --git a/src/sunneed_test.c b/src/sunneed_test.c index c796ad3..eba20a1 100644 --- a/src/sunneed_test.c +++ b/src/sunneed_test.c @@ -1,18 +1,18 @@ -#include "sunneed_test.h" - -char sunneed_runtime_test_error[SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH] = { '\0' }; - -int set_sunneed_error(int ret, const char *format, ...) { - if (sunneed_runtime_test_error[0] != '\0') { - fprintf(stderr, "The test failure message buffer has already been modified.\n" - "Something is messed up with the tests!\n"); - exit(1); - } - - va_list ap; - va_start(ap, format); - vsnprintf(sunneed_runtime_test_error, SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH, format, ap); - va_end(ap); - - return ret; -} +#include "sunneed_test.h" + +char sunneed_runtime_test_error[SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH] = { '\0' }; + +int set_sunneed_error(int ret, const char *format, ...) { + if (sunneed_runtime_test_error[0] != '\0') { + fprintf(stderr, "The test failure message buffer has already been modified.\n" + "Something is messed up with the tests!\n"); + exit(1); + } + + va_list ap; + va_start(ap, format); + vsnprintf(sunneed_runtime_test_error, SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH, format, ap); + va_end(ap); + + return ret; +} diff --git a/src/sunneed_test.h b/src/sunneed_test.h index 22841f1..6bd22c9 100644 --- a/src/sunneed_test.h +++ b/src/sunneed_test.h @@ -1,23 +1,23 @@ -#ifndef _SUNNEED_TEST_H_ -#define _SUNNEED_TEST_H_ - -#include -#include -#include - -// Imagine using non-"power of 2" numbers lmao. -#define SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH 1024 - -/** - * A convenience method for testing. - * Writes the given message to the error description buffer and returns the given value. - * - * Example usage: - * - * if (my_test_variable != 0) - * return set_sunneed_error(1, "test condition failed, variable has value %d", my_test_variable); - * - */ -int set_sunneed_error(int ret, const char *format, ...); - -#endif +#ifndef _SUNNEED_TEST_H_ +#define _SUNNEED_TEST_H_ + +#include +#include +#include + +// Imagine using non-"power of 2" numbers lmao. +#define SUNNEED_RUNTIME_TEST_ERROR_DESC_BUFFER_LENGTH 1024 + +/** + * A convenience method for testing. + * Writes the given message to the error description buffer and returns the given value. + * + * Example usage: + * + * if (my_test_variable != 0) + * return set_sunneed_error(1, "test condition failed, variable has value %d", my_test_variable); + * + */ +int set_sunneed_error(int ret, const char *format, ...); + +#endif diff --git a/src/util/overlay_tester.c b/src/util/overlay_tester.c index a30ccce..a5ec521 100644 --- a/src/util/overlay_tester.c +++ b/src/util/overlay_tester.c @@ -1,32 +1,32 @@ -// Example of a program that would run on a sunneed system. This program should be run with `LD_PRELOAD` overlaying -// our custom functions. This program doesn't reference sunneed in any way; the design is that communication -// should be automatic. - -#include -#include -#include -#include -#include - -// Just to get the CAMERA_PATH. -#include "../shared/sunneed_device_type.h" -#include "../shared/sunneed_testing.h" - -int -main(void) { - printf("Starting main overlay tester\n"); - - int fd = open("/tmp/test", O_CREAT | O_RDWR, 0666); - if (fd == -1) { - fprintf(stderr, "Failed to open file\n"); - return -1; - } - - write(fd, "foo", 3); - - close(fd); - - printf("Opened and closed file\n"); - - return 0; -} +// Example of a program that would run on a sunneed system. This program should be run with `LD_PRELOAD` overlaying +// our custom functions. This program doesn't reference sunneed in any way; the design is that communication +// should be automatic. + +#include +#include +#include +#include +#include + +// Just to get the CAMERA_PATH. +#include "../shared/sunneed_device_type.h" +#include "../shared/sunneed_testing.h" + +int +main(void) { + printf("Starting main overlay tester\n"); + + int fd = open("/tmp/test", O_CREAT | O_RDWR, 0666); + if (fd == -1) { + fprintf(stderr, "Failed to open file\n"); + return -1; + } + + write(fd, "foo", 3); + + close(fd); + + printf("Opened and closed file\n"); + + return 0; +} diff --git a/src/util/pi_send.c b/src/util/pi_send.c new file mode 100644 index 0000000..61f1579 --- /dev/null +++ b/src/util/pi_send.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include +#include + +#define PORT 9999 + +/* + * Send packets at various frequencies, allowing the network card to idle for varying times + * starts at 500 ms and increases by 500 ms every iteration + * + * Uses UDP sockets as written, follow the notes in comments below to change to TCP sockets + */ + +int +main(void) +{ + int remote_fd, read_val; + struct sockaddr_in remote_addr; + char *packets[8]; + + char packet_size_1 = 'A'; + packets[0] = &packet_size_1; + + char packet_size_4[4]; + memset(packet_size_4, 'a', 4); + packets[1] = packet_size_4; + + char packet_size_8[8]; + memset(packet_size_8, 'B', 8); + packets[2] = packet_size_8; + + char packet_size_16[16]; + memset(packet_size_16, 'b', 16); + packets[3] = packet_size_16; + + char packet_size_32[32]; + memset(packet_size_32, 'C', 32); + packets[4] = packet_size_32; + + char packet_size_64[64]; + memset(packet_size_64, 'c', 64); + packets[5] = packet_size_64; + + char packet_size_128[128]; + memset(packet_size_128, 'D', 128); + packets[6] = packet_size_128; + + char packet_size_256[256]; + memset(packet_size_256, 'd', 256); + packets[7] = packet_size_256; + + int size_index, time_index; + int packet_sizes[8] = {1, 4, 8, 16, 32, 64, 128, 256}; + double delay_times[14] = {0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 17.5, 20.0}; + + + //create UDP socket, change SOCK_DGRAM to SOCK_STREAM to create TCP sockets + + + //while(1); + if((remote_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + perror("socket failed\n"); + exit(0); + } + + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(PORT); + + if(inet_pton(AF_INET, "127.0.0.1", &remote_addr.sin_addr) <= 0) + { + perror("invalid address/failed to convert\n"); + exit(0); + } + + if(connect(remote_fd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) + { + perror("failed to connect\n"); + exit(0); + } + + + int i; + clock_t delay_start; + double curr_delay; + for (i = 0; i < 200; i++) + { + size_index = rand() % 8; + time_index = rand() % 14; + + printf("msg size: %d\n", packet_sizes[size_index]); + + send(remote_fd, packets[size_index], packet_sizes[size_index], 0); + + delay_start = clock(); + curr_delay = 0; + while(curr_delay < delay_times[time_index]) + { + curr_delay = (double)(clock() - delay_start) / CLOCKS_PER_SEC; + } + + } + +} diff --git a/src/util/stepper_test.c b/src/util/stepper_test.c index 4afc172..1b5afbe 100644 --- a/src/util/stepper_test.c +++ b/src/util/stepper_test.c @@ -1,22 +1,22 @@ -#include -#include -#include -#include -#include - -int -main(void) -{ - int fd = open("/dev/stepper", O_WRONLY); - if (fd == -1) { - fprintf(stderr, "couldn't open file\n"); - return -1; - } - int dir = 90; - write(fd, &dir, sizeof(dir)); - - close(fd); - - return 0; -} - +#include +#include +#include +#include +#include + +int +main(void) +{ + int fd = open("/dev/stepper", O_WRONLY); + if (fd == -1) { + fprintf(stderr, "couldn't open file\n"); + return -1; + } + int dir = 90; + write(fd, &dir, sizeof(dir)); + + close(fd); + + return 0; +} + diff --git a/stepper_pwr_log.txt b/stepper_pwr_log.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/.gitignore b/test/.gitignore index ae3b222..7269d7d 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,3 +1,3 @@ -*.h - -run-tests +*.h + +run-tests diff --git a/test/Makefile b/test/Makefile index 5f67ebe..a4208f0 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,19 +1,19 @@ -test_sources = $(wildcard *.c) munit/munit.c -suite_sources = $(filter-out test_main.c, $(wildcard *.c)) -suites = $(patsubst %.c,%,$(suite_sources)) - -glue_header = test.gen.h - -all: $(glue_header) - $(CC) $(CFLAGS) -o $(test_runner_name) $(test_sources) $(cflags_deps) - -$(glue_header): - ./scripts/generate-test-header $(suites) > $@ - -format: - $(SOURCE_FORMATTER) $(shell find . -not -path './munit/*' -type f -regex '.*\.c') - -clean: - rm -f $(glue_header) $(test_runner_name) - -.PHONY: all clean $(glue_header) +test_sources = $(wildcard *.c) munit/munit.c +suite_sources = $(filter-out test_main.c, $(wildcard *.c)) +suites = $(patsubst %.c,%,$(suite_sources)) + +glue_header = test.gen.h + +all: $(glue_header) + $(CC) $(CFLAGS) -o $(test_runner_name) $(test_sources) $(cflags_deps) + +$(glue_header): + ./scripts/generate-test-header $(suites) > $@ + +format: + $(SOURCE_FORMATTER) $(shell find . -not -path './munit/*' -type f -regex '.*\.c') + +clean: + rm -f $(glue_header) $(test_runner_name) + +.PHONY: all clean $(glue_header) diff --git a/test/nng.c b/test/nng.c index 9175cbc..0aeec28 100644 --- a/test/nng.c +++ b/test/nng.c @@ -1,43 +1,43 @@ -#include "../src/shared/sunneed_ipc.h" -#include "munit/munit.h" -#include - -#include - -static void -nng_fatal(const char *func, int rv) { - fprintf(stderr, "%s: %s\n", func, nng_strerror(rv)); -} - -MunitResult -test_nng_try_macro(const MunitParameter params[], void *data) { - const size_t msg_sz = 16; - - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); - - nng_msg *msg; - SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, msg_sz); - munit_assert_size(nng_msg_len(msg), ==, msg_sz); - - nng_msg_free(msg); - - return MUNIT_OK; -} - -MunitResult -test_nng_try_set_macro(const MunitParameter params[], void *data) { - const size_t msg_sz = 16; - - SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); - - nng_msg *msg; - int ret = 1; - SUNNEED_NNG_TRY_SET(nng_msg_alloc, ret, != 0, &msg, msg_sz); - munit_assert_size(nng_msg_len(msg), ==, msg_sz); - - munit_assert_int(ret, ==, 0); - - nng_msg_free(msg); - - return MUNIT_OK; -} +#include "../src/shared/sunneed_ipc.h" +#include "munit/munit.h" +#include + +#include + +static void +nng_fatal(const char *func, int rv) { + fprintf(stderr, "%s: %s\n", func, nng_strerror(rv)); +} + +MunitResult +test_nng_try_macro(const MunitParameter params[], void *data) { + const size_t msg_sz = 16; + + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); + + nng_msg *msg; + SUNNEED_NNG_TRY(nng_msg_alloc, != 0, &msg, msg_sz); + munit_assert_size(nng_msg_len(msg), ==, msg_sz); + + nng_msg_free(msg); + + return MUNIT_OK; +} + +MunitResult +test_nng_try_set_macro(const MunitParameter params[], void *data) { + const size_t msg_sz = 16; + + SUNNEED_NNG_SET_ERROR_REPORT_FUNC(nng_fatal); + + nng_msg *msg; + int ret = 1; + SUNNEED_NNG_TRY_SET(nng_msg_alloc, ret, != 0, &msg, msg_sz); + munit_assert_size(nng_msg_len(msg), ==, msg_sz); + + munit_assert_int(ret, ==, 0); + + nng_msg_free(msg); + + return MUNIT_OK; +} diff --git a/test/scripts/generate-test-header b/test/scripts/generate-test-header index 3a5177a..f5a315a 100755 --- a/test/scripts/generate-test-header +++ b/test/scripts/generate-test-header @@ -1,78 +1,78 @@ -#!/usr/bin/env bash -# -# generate-test-header: Outputs the contents of a header file which presents the user with a macro SUITES_ARRAY, which -# will in turn generate a static MunitSuite array containing each provided test file as a suite. The output of this -# should be redirected to a file to be included in the main test runner file. Just call `SUITES_ARRAY(arrayname)` -# and then pass `arrayname` as the `suites` field (the third field) in your main MunitSuite. -# Arguments: suite filenames - -err() { - echo "$(tput setaf 1)Error: $@$(tput sgr0)" >&2 -} - -# Generate the MunitSuite for a given test suite. -# Arguments: suite filename -suite_from_file() { - if [[ ! -f "$1" ]]; then - err "invalid test file '$1'" - return 1 - fi - - local filename=$1 - local testname=${1%.c} - local header_file=${testname}.h - - functions=$(grep -x 'MunitResult' -A1 "$filename" | grep -xv -- '--' | grep -xv 'MunitResult' | sed -r 's/^([[:alnum:]_]+)\(.*$/\1/') - - echo "// Generated from: ${filename}" - - for f in ${functions[@]}; do - echo "MunitResult ${f}(const MunitParameter params[], void *data);" - done - - echo "static MunitTest ${testname}_tests[] = {" - for f in ${functions[@]}; do - echo "{ (char*) (\"/${f}\"), $f, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }," - done - echo "{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };" - - echo "static const MunitSuite ${testname}_suite = { \"/$testname\", ${testname}_tests, NULL, 1, MUNIT_SUITE_OPTION_NONE };" -} - -# Generate a macro that allows the user to create an array containing all of our MunitSuites. -# Arguments: suite names -suites_macro() { - if [[ $# -lt 1 ]]; then - err "suites required" - return 1 - fi - - local suites=$@ - - echo -n "#define SUITES_ARRAY(NAME) static MunitSuite NAME[] = {" - for suite in ${suites[@]}; do - echo -n "${suite}_suite," - done - echo "{ NULL, NULL, NULL, 1, MUNIT_SUITE_OPTION_NONE }}" -} - -main() { - if [[ $# -lt 1 ]]; then - err "input files required" - return 1 - fi - - local files=${@:1} - local suites=${files[@]/.c/} - - echo "// Generated by ${0##*/}. DO NOT EDIT THIS FILE." - echo '#include "munit/munit.h"' - - for suite in ${suites[@]}; do - suite_from_file "${suite}.c" || return $? - done - - suites_macro ${suites[@]} -} - -main "$@" || exit $? +#!/usr/bin/env bash +# +# generate-test-header: Outputs the contents of a header file which presents the user with a macro SUITES_ARRAY, which +# will in turn generate a static MunitSuite array containing each provided test file as a suite. The output of this +# should be redirected to a file to be included in the main test runner file. Just call `SUITES_ARRAY(arrayname)` +# and then pass `arrayname` as the `suites` field (the third field) in your main MunitSuite. +# Arguments: suite filenames + +err() { + echo "$(tput setaf 1)Error: $@$(tput sgr0)" >&2 +} + +# Generate the MunitSuite for a given test suite. +# Arguments: suite filename +suite_from_file() { + if [[ ! -f "$1" ]]; then + err "invalid test file '$1'" + return 1 + fi + + local filename=$1 + local testname=${1%.c} + local header_file=${testname}.h + + functions=$(grep -x 'MunitResult' -A1 "$filename" | grep -xv -- '--' | grep -xv 'MunitResult' | sed -r 's/^([[:alnum:]_]+)\(.*$/\1/') + + echo "// Generated from: ${filename}" + + for f in ${functions[@]}; do + echo "MunitResult ${f}(const MunitParameter params[], void *data);" + done + + echo "static MunitTest ${testname}_tests[] = {" + for f in ${functions[@]}; do + echo "{ (char*) (\"/${f}\"), $f, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }," + done + echo "{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };" + + echo "static const MunitSuite ${testname}_suite = { \"/$testname\", ${testname}_tests, NULL, 1, MUNIT_SUITE_OPTION_NONE };" +} + +# Generate a macro that allows the user to create an array containing all of our MunitSuites. +# Arguments: suite names +suites_macro() { + if [[ $# -lt 1 ]]; then + err "suites required" + return 1 + fi + + local suites=$@ + + echo -n "#define SUITES_ARRAY(NAME) static MunitSuite NAME[] = {" + for suite in ${suites[@]}; do + echo -n "${suite}_suite," + done + echo "{ NULL, NULL, NULL, 1, MUNIT_SUITE_OPTION_NONE }}" +} + +main() { + if [[ $# -lt 1 ]]; then + err "input files required" + return 1 + fi + + local files=${@:1} + local suites=${files[@]/.c/} + + echo "// Generated by ${0##*/}. DO NOT EDIT THIS FILE." + echo '#include "munit/munit.h"' + + for suite in ${suites[@]}; do + suite_from_file "${suite}.c" || return $? + done + + suites_macro ${suites[@]} +} + +main "$@" || exit $? diff --git a/test/test_main.c b/test/test_main.c index 8e3dab7..778e85e 100644 --- a/test/test_main.c +++ b/test/test_main.c @@ -1,14 +1,14 @@ -#include "munit/munit.h" -#include "test.gen.h" - -SUITES_ARRAY(sub_suites); - -static const MunitSuite main_suite = {(char *)"/sunneed", NULL, sub_suites, 1, MUNIT_SUITE_OPTION_NONE}; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" -int -main(int argc, const char *argv[]) { - return munit_suite_main(&main_suite, NULL, argc, argv); -} -#pragma GCC diagnostic pop +#include "munit/munit.h" +#include "test.gen.h" + +SUITES_ARRAY(sub_suites); + +static const MunitSuite main_suite = {(char *)"/sunneed", NULL, sub_suites, 1, MUNIT_SUITE_OPTION_NONE}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" +int +main(int argc, const char *argv[]) { + return munit_suite_main(&main_suite, NULL, argc, argv); +} +#pragma GCC diagnostic pop