pax_global_header00006660000000000000000000000064122655357450014530gustar00rootroot0000000000000052 comment=a74ccf3d3606d2047b107d015e5bdf0615c11d6c bti-034/000077500000000000000000000000001226553574500121565ustar00rootroot00000000000000bti-034/.gitignore000066400000000000000000000003741226553574500141520ustar00rootroot00000000000000.deps bti *.o *.swp *.gz *.bz2 INSTALL Makefile Makefile.in TAGS aclocal.m4 autom4te.cache check-news.in compile config.guess config.h config.h.in config.log config.status config.sub configure depcomp install-sh libtool ltmain.sh missing stamp-h1 tags bti-034/COPYING000066400000000000000000000431031226553574500132120ustar00rootroot00000000000000 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. bti-034/ChangeLog000066400000000000000000000403061226553574500137330ustar00rootroot00000000000000Summary of changes from v033 to v034 ============================================ Colin Guthrie (1): Build against the new JSON-C library name. Dirk Eddelbuettel (1): change remaining http:// to https:// Greg Kroah-Hartman (5): use -EIO instead of -EREMOTEIO dry-run shouldn't error out. remove identi.ca support. Remove unused xml parsing function Merge pull request #32 from eddelbuettel/master Summary of changes from v032 to v033 ============================================ Greg Kroah-Hartman (9): Merge pull request #24 from pferor/master Fix build issue rework finding the config file. Minor coding style cleanups. Remove the FSF's address, as we don't care about that. Error out if the command line option for a config file fails. Merge pull request #23 from maneulyori/master coding style cleanups fix up coding style differences generated in the json parsing functions Paul Salanitri (3): Add direct message capability. Also adds rudimentry error checking on both updates and direct messages. Update the man page to show direct message ability. Add direct message action. Pferor (1): Add XDG compilance (~/.config/bti) support maneulyori (3): increased size of char endpoint to 2KByte. It will fix bti crashes while tweeting long unicode string. use snprintf instead of sprintf. It will prevent BOF. Stop tweeting when tweet length is over 140 character. paulrho (7): Merge https://github.com/gregkh/bti Better error checking Use twitter to detect >140 chars Typo fix write status update and direct message with json url read json error messages include JSON library guard against a pre 0.10 json library bug another json pre10 issue to work around Enable showing timeline: friends, user (public does not work) Summary of changes from v031 to v032 ============================================ Amir Mohammad Saied (1): s/http/https/g Fabian Groffen (1): bti: allow compilation on non-GNU platforms Galen Charlton (1): teach bti how to do OAuth for custom StatusNet installations Greg Kroah-Hartman (4): bti: Handle NULL HOME environment variables bti.xml: docsheet url cleanup update bti.1 with latest docbook template goodness fix up some compiler warnings Jonathan Neuschäfer (4): fix common misspellings get_string*: fix a memory leak HACKING: this is bti, not smugbatch send_request: don't leak the curl stuff gregor herrmann (7): Die if no host is provided. Name correct hosts in prompts. Add documentation for new shrink_host feature in bti-shrink-urls. Document the new --retweet feature in bti.xml and add it to bash completion. Active --dry-run for OAuth. laconi.ca was renamed to StatusNet Output actually used config file name instead of hardcoded ~/.bti in request_access_token(). siers (1): Output can be evened like a table with --column 20. Summary of changes from v030 to v031 ============================================ Diego Elio Pettenò (1): In non-background execution, check whether the server reports success. Greg Kroah-Hartman (2): code formatting cleanups config: fix possible access of non-allocated memory Michel Alexandre Salim (2): Only treat # as a comment marker if it's at the beginning of line or is preceded by a whitespace character Keep searching for '#' comment marker if previous occurence was a false positive Summary of changes from v029 to v030 ============================================ Greg Kroah-Hartman (9): turn on some better compiler warnings. move debug to bti.h fix compiler warning with bti_output_line rewrite the config parser Revert "turn on some better compiler warnings." config.c: fix up some compiler warnings config.c: remove config debug lines. bti.c: fix another compiler warning about global functions. send_request needs to return a negative error value. L. Alberto Giménez (4): Fix trivial compiler warnings Added tags files to .gitignore Fix retweet batch operation Fix a segfault when specifying an invalid host. Summary of changes from v028 to v029 ============================================ Amir Mohammad Saied (2): Update my copyright date. Fixing Identi.ca's OAuth request_token URI Dwi Sasongko S (1): Support proper 'reply_to' and 'retweet' Greg Kroah-Hartman (9): updated man pages generated from the docbook source Merge https://github.com/leif81/bti into main tree Merge testing branch into master Merge branch 'oauth-readme' of https://github.com/agimenez/bti into agimenez-oauth-readme reformat README to have a bit shorter lines move verbose flag into the session structure create bti.h add config.c move output logic to one function Justin Forest (1): Support shrinking with bit.ly and j.mp. L. Alberto Giménez (1): Document how to configure OAuth. Leif Gruenwoldt (1): user and public actions may not require auth gregor herrmann (3): Add documentation for new shrink_host feature in bti-shrink-urls. Document the new --retweet feature in bti.xml and add it to bash completion. Active --dry-run for OAuth. Summary of changes from v027 to v028 ============================================ Diego Elio 'Flameeyes' Pettenò (7): build system: use consistently pkg-config to check for dependencies build system: use AC_SEARCH_LIBS rather than assuming -ldl is needed. Remove autoscan-generated bad tests. Use arrays rather than pointers for static strings. Typo fix. Split --background from --bash, and make the latter imply the former. Collapse more fprintf() calls. Greg Kroah-Hartman (7): Fixup typo in Makefile.am found by @applehq new version of checkpatch.pl from 2.6.35-rc1 kernel whitespace cleanups for bti.c more formatting cleanups long line cleanups Update my copyright date. Reduce the number of calls to fprintf. Summary of changes from v026 to v027 ============================================ Amir Mohammad Saied (4): OAuth for both twitter, and identi.ca HTTP basic auth for StatusNet instances Leftovers from last commit, accepting account and, password from options - Cosmetics - Escaping tweet before POSTing it Daniel Cordero (4): Verbose mode shows status IDs Support for replying to notices Add a replyto configuration option Fix a warning: unused variable message Greg Kroah-Hartman (5): add oath.keys from twitter.com Merge branch 'master' into oath error out if liboauth is not present Merge branch 'master' into oath-test fix up the autoconf checking for liboauth and other libraries Michel Alexandre Salim (1): Fix Implicit linking against -ldl Summary of changes from v025 to v026 ============================================ Greg Kroah-Hartman (7): minor coding style cleanup. updated version of checkpatch.pl another minor codingstyle fix update bti.1 from the xml changes init the readline function after parsing the command line options add a configfile option to the core data structure Add ability to override default config file option Pete Zaitcev (2): bti: suppress prompt if fed from a pipe bti: fix a crash for echo -n|bti Reuben Thomas (1): Typo fixes for bti(1) Summary of changes from v024 to v025 ============================================ Amir Mohammad Saied (3): If there's no host specified, the session host is already pointing to twitter, but hosturl is empty Removing explicit mentions of twitter Merge remote branch 'gregkh/master' Greg Kroah-Hartman (1): Remove link-time dependancy on readline gregor herrmann (1): Name correct hosts in prompts. Summary of changes from v023 to v024 ============================================ Ben Boeckel (1): Disable echo when reading the password Greg Kroah-Hartman (7): fix compiler warning about fprintf clean up some trailing whitespace more coding style cleanups don't free an array on the stack fix crash when --debug is set fix up some compiler warnings in read_password use "quiet" mode of building if present. Marvin Vek (1): Add laconica group support gregor herrmann (1): BTI should not display password while user types it Summary of changes from v022 to v023 ============================================ Amir Mohammad Saied (5): https for twitter Verbose mode for bti verbose option for bash completion script Updating documents, both for the new verbose mode, and custom laconi.ca installations. Updating AUTHORs and copyright section Summary of changes from v021 to v022 ============================================ Amir Mohammad Saied (2): Support for custom bti installations Updating config example file Greg Kroah-Hartman (4): add the script to the tarball. fix build warning from time patch. Merge branch 'master' of gregkh@master.kernel.org:/pub/scm/linux/kernel/git/gregkh/bti Merge branch 'master' of git://github.com/amir/bti J.R. Mauro (1): bti: show timestamp in statuses gregor herrmann (1): bti: use ssl for identi.ca Summary of changes from v020 to v021 ============================================ Greg Kroah-Hartman (3): fix build of man pages Install bti-shrink-urls properly bump version to 021 Summary of changes from v019 to v020 ============================================ Greg Kroah-Hartman (3): fix two compiler warnings. bump version number add more forgotten files to Makefile.am Summary of changes from v018 to v019 ============================================ Greg Kroah-Hartman (1): Makefile.am and configure.ac tweaks to get the release correct Summary of changes from v017 to v018 ============================================ Alexander Færøy (1): Use '#' as prefix for our tweets if we're uid 0; otherwise '$'. Greg Kroah-Hartman (8): add "bti:" to the beginning of all debug messages show version when starting up with debug enabled added bti-shrink-urls man page added bti-shrink-urls.1 to Makefile added AUTHOR section to bti-shrink-urls.xml add bti-shrink-urls.1 to tree convert to use autotools more autoconf fun Gregor Herrmann (1): add missing arguments to getopt_long_only call and add --dry-run to --help Summary of changes from v016 to v017 ============================================ Bart Trojanowski (1): fix a bug with shrink_urls() when ran on text that contains no urls Dave Barry (1): Add support for api page numbers in read mode Greg Kroah-Hartman (5): Lots of checkpatch cleanups to the code Merge branch 'master' of git://github.com/psykoyiko/bti document the --page option add --page to bti-bashcompletion update bti.1 Summary of changes from v015 to v016 ============================================ Bart Trojanowski (6): add make install target add bti --shrink-urls add bti --dry-run option bti.xml and bti.example updates fix an array overrun in shrink_urls() bti-shrink-urls - properly handle urls with : Greg Kroah-Hartman (3): fix up some compiler warnings due to new patches add new options to bash completion script update bti.1 with new info from xml file Summary of changes from v014 to v015 ============================================ Amir Mohammad Saied (11): Actions support for bti libxml2 libs and cflags action and values for bash completion Added action option in example bti config file User action Replies action Updating bashcompletion and example config file for new actions logging more Updating documents Handling unknown actions Fixing an assignment issue Greg Kroah-Hartman (2): Change the formatting of --action output fix readline mess when in --bash mode Summary of changes from v013 to v014 ============================================ Greg Kroah-Hartman (1): Fix readline bug(s) Summary of changes from v012 to v013 ============================================ Greg Kroah-Hartman (3): coding style cleanups fix readline support don't duplicate string on password or account prompt Summary of changes from v011 to v012 ============================================ Amir Mohammad Saied (2): Source parameter added Add bash completion script Greg Kroah-Hartman (2): add --logfile option document the logfile option SanjayKumar J (1): bti: incorrect version fix Summary of changes from v010 to v011 ============================================ Amir Mohammad Saied (1): Readline support for bti Greg Kroah-Hartman (3): add initial ~/.bti.log support to log what is going on Add documentation about how to send me patches disable log support until I can add some configurations for it Summary of changes from v009 to v010 ============================================ Greg Kroah-Hartman (3): add --proxy support to command line check in changes to bti.1 from previous xml changes document the order that config options are handled gregor herrmann (2): Fix up documentation of proxy option use the http_proxy environment variable if it is present Summary of changes from v008 to v009 ============================================ Greg Kroah-Hartman (3): initial cut at adding http proxy support document proxy support add example proxy value to the bti.example file Summary of changes from v007 to v008 ============================================ Greg Kroah-Hartman (1): Works properly now with twitter due to december twitter server change Summary of changes from v006 to v007 ============================================ Greg Kroah-Hartman (4): fix git command in Makefile change README to show that we handle identi.ca also now more README updates Merge branch 'master' of greg@mail.kroah.net:git/bti Olivier Blin (1): fix segfault when piping to bti and user/password are not set Summary of changes from v005 to v006 ============================================ Greg Kroah-Hartman (2): added github url to the README add identi.ca support to bti Summary of changes from v004 to v005 ============================================ Greg Kroah-Hartman (2): remove the pwd from the bash output, it's messy expand the tweet size, it was only taking 100 chars Summary of changes from v003 to v004 ============================================ Greg Kroah-Hartman (3): if --bash is set, don't spit out error messages, we don't care. if --bash is enabled, fork and handle the send in the background update the man page about --bash options Summary of changes from v002 to v003 ============================================ Greg Kroah-Hartman (4): added script bump version number change --clean be the default and add --bash to duplicate old method removed unneeded script Summary of changes from v001 to v002 ============================================ Greg Kroah-Hartman (9): increment version number add .gz and .bz2 to .gitignore remove ability to make .bz2 from Makefile add --clean option to bti.c add --version to bti.c update the man page to reflect the new options renamed HOWTO to README and added real information there added bti.example as an example ~/.bti file lots of manpage updates trying to document how to use bti better Summary of changes from v000 to v001 ============================================ Greg Kroah-Hartman (8): add .gitignore initial commit of bti, everything's working now add the '$ ' to the beginning of the line. added HOWTO meta files added (COPYING, ChangeLog, RELEASE_NOTES) added fork functionality to get rid of annoying command line mess added PWD to twit more messing with directory format bti-034/HACKING000066400000000000000000000020411226553574500131420ustar00rootroot00000000000000Patches are gladly accepted for bti code, please send them to greg@kroah.com, or you can use github to send a pull request. The code tree for bti is located at: http://github.com/gregkh/bti and can be cloned using git from: git://github.com/gregkh/bti.git If you have never used git before, there are some useful tutorials on the github.com web site to help you through the learning process. I am trying to follow the Linux kernel coding style with the code wherever possible. This style is documented in the Linux kernel source tree in the file Documentation/CodingStyle. Included in the bti code tree is a script called checkpatch.pl that can automatically check if your changes are compliant with the guidelines. To test this, using git, generate a patch: git diff > my_patch Then run checkpatch.pl against this patch: ./scripts/checkpatch.pl --no-tree my_patch Please run your modifications through valgrind if possible. It is safe to ignore the valgrind errors in the curl and ssl libraries, we can't do anything about those at this time. bti-034/Makefile.am000066400000000000000000000026461226553574500142220ustar00rootroot00000000000000bin_PROGRAMS = \ bti bin_SCRIPTS = \ bti-shrink-urls bti_SOURCES = \ bti.h \ config.c \ bti.c bti_CFLAGS = \ $(LIBCURL_CFLAGS) \ $(XML_CFLAGS) \ $(JSON_CFLAGS) \ $(LIBPCRE_CFLAGS) \ $(LIBOAUTH_CFLAGS) bti_LDADD = \ $(LIBCURL_LIBS) \ $(XML_LIBS) \ $(JSON_LIBS) \ $(LIBPCRE_LIBS) \ $(LIBOAUTH_LIBS) dist_man_MANS = \ bti.1 \ bti-shrink-urls.1 EXTRA_DIST = \ bti.xml \ bti-shrink-urls.xml \ bti.example \ bti-bashcompletion \ RELEASE-NOTES \ bti-shrink-urls %.1: %.xml $(XSLTPROC) -nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< MAINTAINERCLEANFILES = \ $(dist_man_MANS) git-clean: rm -f Makefile.in distclean-local: rm -rf autom4te.cache PREVIOUS_VERSION = 0`expr $(VERSION) - 1` changelog: @ head -1 ChangeLog | grep -q "to v$(PREVIOUS_VERSION)" @ mv ChangeLog ChangeLog.tmp @ echo "Summary of changes from v$(PREVIOUS_VERSION) to v$(VERSION)" >> ChangeLog @ echo "============================================" >> ChangeLog @ echo >> ChangeLog @ git log --pretty=short $(PREVIOUS_VERSION)..HEAD | git shortlog >> ChangeLog @ echo >> ChangeLog @ cat ChangeLog @ cat ChangeLog.tmp >> ChangeLog @ rm ChangeLog.tmp git-release: head -1 ChangeLog | grep -q "to v$(VERSION)" head -1 RELEASE-NOTES | grep -q "bti $(VERSION)" git commit -a -m "release $(VERSION)" cat .git/refs/heads/master > .git/refs/tags/$(VERSION) git gc git prune AUTOMAKE_OPTIONS = foreign bti-034/README000066400000000000000000000025441226553574500130430ustar00rootroot00000000000000bti - bash twitter/identi.ca ididocy Allows you to pipe your bash input to twitter or identi.ca in an easy and fast manner to annoy the whole world. See the man page bti.1 for how to use it and more information. Be careful if you use this, it is quite easy to end up sending sensitive data to the world with it. USE AT YOUR OWN RISK! Any questions, contact Greg Kroah-Hartman or @gregkh on twitter or identi.ca. bti is developed using git and the tree can be found at: git://github.com/gregkh/bti.git and browsed at: http://github.com/gregkh/bti/ About OAuth configuration If you want to use an OAuth-enabled service (like twitter), you should configure bti to use the consumer key and secret shipped with the source code (check the oath.keys file). For example, you should add the following two lines to your ~/.bti configuration file: ---8<------------------- # Consumer key consumer_key=cZy8DdioswAfu3LJYg6E2w # Consumer secret consumer_secret=fnIGGU0T12mMWKjmThUdSeKN32NLWfmnwapwubVQ ---8<------------------- The next time that you run bti, you will be told to visit an URL that will provide you a PIN number. You should then input that number in the bti prompt, and you will be given two new configuration values (access_token_secret and access_token_key) that you need to add to your bti configuration file to authenticate requests from bti. bti-034/RELEASE-NOTES000066400000000000000000000070461226553574500140560ustar00rootroot00000000000000bti 033 ============= - fixes due to Twitter turning off http support (everything is https now.) thanks to Dirk Eddelbuettel - BSD build fixes - JSON-C build fixes thanks to Colin Guthrie bti 033 ============= - fixes due to Twitter turning off old XML interface thanks to Paul Salanitri - direct message capability thanks to Paul Salanitri - XDG compliance for configuration file - other minor bugfixes bti 032 ============= - oauth for custom StatusNet installations now works properly. - non-GNU platform support - --retweet support - much better error handling if servers are down - other minor bugfixes bti 031 ============= - Check for identi.ca server success or not in non-background mode thanks to Flameeyes - Handle '#' in a password in the config file properly thanks to Michel Alexandre Salim bti 030 ============= More minor bugfixes bti 029 ============= Bugfixes for a variety of little things lots better oauth documentation. bti 028 ============= Build fixes thanks to Flameeyes and other minor tweaks. bti 027 ============= OAUTH support bti 026 ============= added ability to specify config file from command line some readline fixups from Pete bti 025 ============= remove readline link-time dependency to make the distro's lives easier bugfixes for minor things by Amir and Gregor bti 024 ============= laconica group support from Marvin Vek password handling changes from Gregor Herrmann and Ben Boeckel bti 023 ============= verbose mode for timestamps documentation for custom identi.ca Properly credit Amir for all of his great work. bti 022 ============= custom identi.ca instances supported timestamps on messages read. autoconf fixes. bti 021 ============= yet more autoconf fixes bti 020 ============= more autoconf fixes bti 019 ============= autoconf fixes bti 018 ============= man page added for helper script now using autoconf now showing all options with --help bti 017 ============= url shortner bugfix from Bart Trojanowski --page option from Dave Barry documentation updates bti 016 ============= --dry-run option thanks to Bart Trojanowski url shortner script and option thanks to Bart Trojanowski make install target thanks to Bart Trojanowski bti 015 ============= --action support, bti now can output data from the twitter and identica servers! Thanks to Amir Saied for the work he put into this. minor bugfixes for bash mode, bah, readline is a pain at times. bti 014 ============= Fix reported readline bugs bti 013 ============= Fix some readline prompt issues bti 012 ============= Logfile support added to track status of tweets bash completion script added thanks to Amir Saied proper "application name" added thanks to Amir Saied bti 011 ============= Readline support thanks to Amir Saied bti 010 ============= Fix up HTTP proxy support, thanks to gregor herrmann: - command line option now works - http_proxy environment variable is now handled bti 009 ============= Initial attempt for HTTP proxy support. bti 008 ============= Fix for twitter, due to their server not liking one of the http headers anymore. bti 007 ============= segfault fix from Oliver Blin bti 006 ============= added identi.ca support bti 005 ============= pwd was removed from bash mode bug with tweets crashing over 100 characters fixed bti 004 ============= --bash fork and quiet modes enabled bti 003 ============= --bash emulates a command line, otherwise the message goes through "clean" bti 002 ============= better man page example ~/.bti file ability to send "clean" files to twitter (no path or $ prefix) bti 001 ============= initial release bti-034/autogen.sh000077500000000000000000000011601226553574500141550ustar00rootroot00000000000000#!/bin/sh -e autoreconf --install --symlink CFLAGS="-g -Wall \ -Wmissing-declarations -Wmissing-prototypes \ -Wnested-externs -Wpointer-arith \ -Wpointer-arith -Wsign-compare -Wchar-subscripts \ -Wstrict-prototypes -Wshadow \ -Wformat=2 -Wtype-limits" case "$1" in *install|"") export CFLAGS="$CFLAGS -O2" echo " configure: $args" echo ./configure $args ;; *devel) export CFLAGS="$CFLAGS -O0" echo " configure: $args" echo ./configure $args ;; *clean) ./configure make maintainer-clean git clean -f -X exit 0 ;; *) echo "Usage: $0 [--install|--devel|--clean]" exit 1 ;; esac bti-034/bti-bashcompletion000066400000000000000000000012151226553574500156630ustar00rootroot00000000000000_bti() { local cur prev COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ "${cur}" == -* ]] ; then COMPREPLY=( $(compgen -W "-a -A -p -P -H -b -d -v -s -n -g -h -r --account --action --password --proxy --host --bash \ --user --debug --dry-run --shrink-urls --page --version --verbose \ --help --replyto --retweet" -- ${cur}) ) fi if [[ "${prev}" == "--host" ]] ; then COMPREPLY=( $(compgen -W "twitter identica" -- ${cur} ) ) fi if [[ "${prev}" == "--action" ]] ; then COMPREPLY=( $(compgen -W "friends public update user replies direct" -- ${cur} ) ) fi return 0 } complete -F _bti bti bti-034/bti-shrink-urls000077500000000000000000000075541226553574500151540ustar00rootroot00000000000000#!/bin/bash # Copyright (C) 2009 Bart Trojanowski # # 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 version 2 of the License. # # 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. needs_escape=true shrink_host=2tu.us test -f ~/.bti && source ~/.bti || test -f ~/.config/bti && source ~/.config/bti while test -n "$1" ; do word="$1" shift case "$word" in --escaped) needs_escape= ;; --help|-h) cat <] Currently supported: 2tu.us (default), bit.ly, j.mp. END exit 0 ;; *) URL=$word ;; esac done function convert_url() { local url=$1 test -n "$url" || return 1 test "${url%%:*}" = 'http' || return 1 local urllen="${#url}" # http://en.wikipedia.org/wiki/Percent-encoding if test -n "$needs_escape" ; then url=$(echo "$url" | sed -e 's/\%/%25/g' \ -e 's/!/%21/g' \ -e 's/*/%2A/g' \ -e "s/'/%27/g" \ -e 's/(/%28/g' \ -e 's/)/%29/g' \ -e 's/;/%3B/g' \ -e 's/:/%3A/g' \ -e 's/@/%40/g' \ -e 's/&/%26/g' \ -e 's/=/%3D/g' \ -e 's/+/%2B/g' \ -e 's/\$/%24/g' \ -e 's/,/%2C/g' \ -e 's,/,%2F,g' \ -e 's/?/%3F/g' \ -e 's/#/%23/g' \ -e 's/\[/%5B/g' \ -e 's/]/%5D/g') fi case $shrink_host in 2tu.us) local submit="http://2tu.us/?save=y&url=$url" local res=$(wget -q -O - "$submit" | awk -F"'" '/Your tight URL is:/ { print $2 }') ;; bit.ly|j.mp) if [ -z "$shrink_bitly_login" -o -z "$shrink_bitly_key" ]; then echo "To use $shrink_host you must set 'shrink_bitly_login' and 'shrink_bitly_key' in ~/.bti" >&2 exit 1 fi local submit="http://api.bit.ly/v3/shorten?format=txt&login=$shrink_bitly_login&apiKey=$shrink_bitly_key&domain=$shrink_host&longUrl=$url" local res=$(wget -q -O - "$submit") ;; *) echo "Shrinking with $shrink_host is not supported." >&2 exit 1 ;; esac if test "${res%%:*}" = 'http' -a "${#res}" -lt "$urllen" ; then echo $res return 0 fi return 1 } function die() { echo >&2 $@ exit 1 } if test -n "$URL" ; then convert_url "$URL" || die "Failed to shrink '$URL'" exit $? fi test -t 0 && echo >&2 "Type in some urls and I'll try to shrink them for you..." while read line ; do convert_url "$line" || echo $line done bti-034/bti-shrink-urls.1000066400000000000000000000044761226553574500153100ustar00rootroot00000000000000'\" t .\" Title: bti-shrink-urls .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 .\" Date: March 2009 .\" Manual: bti-shrink-urls .\" Source: bti-shrink-urls .\" Language: English .\" .TH "BTI\-SHRINK\-URLS" "1" "March 2009" "bti-shrink-urls" "bti-shrink-urls" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" bti-shrink-urls \- convert URLs to a shorter form using a web service .SH "SYNOPSIS" .HP \w'\fBbti\fR\ 'u \fBbti\fR [\fB\-\-escaped\fR] [\fB\-\-help\fR] [\fBURL\fR] .SH "DESCRIPTION" .PP bti\-shrink\-urls converts URLs to a shorter form using a web service\&. .PP Currently http://2tu\&.us/ (default) and http://bit\&.ly / http://j\&.mp are supported\&. .SH "OPTIONS" .PP \fB\-\-escaped\fR .RS 4 Don\'t escape special characters in the URL any more, they are already percent encoded\&. .RE .PP \fB\-\-help\fR .RS 4 Print help text\&. .RE .PP \fBURL\fR .RS 4 Specify the URL to be converted\&. If no URL is given bti\-shrink\-urls waits for input on stdin\&. .RE .SH "CONFIGURATION" .PP bti\-shrink\-urls is configured by setting some values in ~/\&.bti: .PP \fBshrink_host\fR .RS 4 Possible values: 2tu\&.us (default), bit\&.ly, j\&.mp .RE .PP \fBshrink_bitly_login\fR .RS 4 API login for bit\&.ly, j\&.mp, required if shrink_host is set to bit\&.ly or j\&.mp\&. See https://code\&.google\&.com/p/bitly\-api/wiki/ApiDocumentation .RE .PP \fBshrink_bitly_key\fR .RS 4 API key for bit\&.ly, j\&.mp, required if shrink_host is set to bit\&.ly or j\&.mp\&. See https://code\&.google\&.com/p/bitly\-api/wiki/ApiDocumentation .RE .SH "AUTHOR" .PP Written by Bart Trojanowski bart@jukie\&.net\&. .SH "COPYRIGHT AND LICENSE" .PP Copyright (C) 2009 Bart Trojanowski bart@jukie\&.net\&. .PP 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 version 2 of the License\&. bti-034/bti-shrink-urls.xml000066400000000000000000000076611226553574500157470ustar00rootroot00000000000000
bti-shrink-urls bti-shrink-urls March 2009 bti-shrink-urls bti-shrink-urls 1 bti-shrink-urls convert URLs to a shorter form using a web service bti DESCRIPTION bti-shrink-urls converts URLs to a shorter form using a web service. Currently http://2tu.us/ (default) and http://bit.ly / http://j.mp are supported. OPTIONS Don't escape special characters in the URL any more, they are already percent encoded. Print help text. Specify the URL to be converted. If no URL is given bti-shrink-urls waits for input on stdin. CONFIGURATION bti-shrink-urls is configured by setting some values in ~/.bti: Possible values: 2tu.us (default), bit.ly, j.mp API login for bit.ly, j.mp, required if shrink_host is set to bit.ly or j.mp. See https://code.google.com/p/bitly-api/wiki/ApiDocumentation API key for bit.ly, j.mp, required if shrink_host is set to bit.ly or j.mp. See https://code.google.com/p/bitly-api/wiki/ApiDocumentation AUTHOR Written by Bart Trojanowski bart@jukie.net. COPYRIGHT AND LICENSE Copyright (C) 2009 Bart Trojanowski bart@jukie.net. 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 version 2 of the License.
bti-034/bti.1000066400000000000000000000215641226553574500130260ustar00rootroot00000000000000'\" t .\" Title: bti .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 .\" Date: May 2008 .\" Manual: bti .\" Source: bti .\" Language: English .\" .TH "BTI" "1" "May 2008" "bti" "bti" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" bti \- send a tweet to twitter\&.com from the command line .SH "SYNOPSIS" .HP \w'\fBbti\fR\ 'u \fBbti\fR [\fB\-\-account\ account\fR] [\fB\-\-password\ password\fR] [\fB\-\-action\ action\fR] [\fB\-\-user\ screenname\fR] [\fB\-\-host\ HOST_NAME\fR] [\fB\-\-proxy\ PROXY:PORT\fR] [\fB\-\-logfile\ LOGFILE\fR] [\fB\-\-config\ CONFIGFILE\fR] [\fB\-\-replyto\ ID\fR] [\fB\-\-retweet\ ID\fR] [\fB\-\-page\ PAGENUMBER\fR] [\fB\-\-bash\fR] [\fB\-\-shrink\-urls\fR] [\fB\-\-debug\fR] [\fB\-\-dry\-run\fR] [\fB\-\-verbose\fR] [\fB\-\-version\fR] [\fB\-\-help\fR] .SH "DESCRIPTION" .PP bti sends a tweet message to twitter\&.com\&. .SH "OPTIONS" .PP \fB\-\-account account\fR .RS 4 Specify the twitter\&.com account name\&. .RE .PP \fB\-\-password password\fR .RS 4 Specify the password of your twitter\&.com account\&. .RE .PP \fB\-\-action action\fR .RS 4 Specify the action which you want to perform\&. Valid options are "update" to send a message, "friends" to see your friends timeline, "public" to track public timeline, "replies" to see replies to your messages, "user" to see a specific user\*(Aqs timeline and "direct" to send a direct message to a friend\&. Default is "update"\&. .RE .PP \fB\-\-user screenname\fR .RS 4 Specify the user whose messages you want to see when the action is "user", and the reciever of the direct message when the action is "direct" (the sender must be following the receiver)\&. .RE .PP \fB\-\-host HOST_NAME\fR .RS 4 Specify the host which you want to send your message to\&. Valid options are "twitter" to send to twitter\&.com\&. .sp If no host is specified, the default is to send to twitter\&.com\&. .RE .PP \fB\-\-proxy PROXY:PORT\fR .RS 4 Specify a http proxy value\&. This is not a required option, and only needed by systems that are behind a http proxy\&. .sp If \-\-proxy is not specified but the environment variable \*(Aqhttp_proxy\*(Aq is set the latter will be used\&. .RE .PP \fB\-\-logfile LOGFILE\fR .RS 4 Specify a logfile for bti to write status messages to\&. LOGFILE is in relation to the user\*(Aqs home directory, not an absolute path to a file\&. .RE .PP \fB\-\-config CONFIGFILE\fR .RS 4 Specify a config file for bti to read from\&. By default, bti looks in the ~/\&.bti file for config values\&. This default location can be overridden by setting a specific file with this option\&. .RE .PP \fB\-\-replyto ID\fR .RS 4 Status ID of a single post to which you want to create a threaded reply to\&. .sp For twitter, this is ignored unless the message starts with the @name of the owner of the post with the status ID\&. .sp For status\&.net, this can link any two messages into context with each other\&. Status\&.net will also link a message that contains an @name without this without regard to context\&. .RE .PP \fB\-\-retweet ID\fR .RS 4 Status ID of a single post which you want to retweet\&. .RE .PP \fB\-\-shrink\-urls\fR .RS 4 Scans the tweet text for valid URL patterns and passes each through the supplied bti\-shrink\-urls script\&. The script will pass the URL to a web service that shrinks the URLs, making it more suitable for micro\-blogging\&. .sp The following URL shrinking services are available: http://2tu\&.us/ (default) and http://bit\&.ly / http://j\&.mp .sp See the documentation for bti\-shrink\-urls for the configuration options\&. .RE .PP \fB\-\-debug\fR .RS 4 Print a whole bunch of debugging messages to stdout\&. .RE .PP \fB\-\-page PAGENUMBER\fR .RS 4 When the action is to retrieve updates, it usually retrieves only one page\&. If this option is used, the page number can be specified\&. .RE .PP \fB\-\-dry\-run\fR .RS 4 Performs all steps that would normally be done for a given action, but will not connect to the service to post or retrieve data\&. .RE .PP \fB\-\-verbose\fR .RS 4 Verbose mode\&. Print status IDs and timestamps\&. .RE .PP \fB\-\-bash\fR .RS 4 Add the working directory and a \*(Aq$\*(Aq in the tweet message to help specify it is coming from a command line\&. Don\*(Aqt put the working directory and the \*(Aq$\*(Aq in the tweet message\&. .sp This option implies \fB\-\-background\fR\&. .RE .PP \fB\-\-background\fR .RS 4 Do not report back any errors that might have happened when sending the message, and send it in the background, returning immediately, allowing the user to continue on\&. .RE .PP \fB\-\-version\fR .RS 4 Print version number\&. .RE .PP \fB\-\-help\fR .RS 4 Print help text\&. .RE .SH "DESCRIPTION" .PP bti provides an easy way to send tweet messages direct from the command line or any script\&. It reads the message on standard input and uses the account and password settings either from the command line options, or from a config file, to send the message out\&. .PP Its primary focus is to allow you to log everything that you type into a bash shell, in a crazy, "this is what I\*(Aqm doing right now!" type of way, letting the world follow along with you constant moving between directories and refreshing your email queue to see if there\*(Aqs anything interesting going on\&. .PP To hook bti up to your bash shell, export the following variable: .PP PROMPT_COMMAND=\*(Aqhistory 1 | sed \-e "s/^\es*[0\-9]*\es*//" | bti \-\-bash\*(Aq .PP This example assumes that you have the ~/\&.bti set up with your account and password information already in it, otherwise you can specify them as an option\&. .SH "CONFIGURATION" .PP The account and password can be stored in a configuration file in the users home directory in a file named \&.bti\&. The structure of this file is as follows: .PP \fBaccount\fR .RS 4 The twitter\&.com account name you wish to use to send this message with\&. .RE .PP \fBpassword\fR .RS 4 The twitter\&.com password for the account you wish to use to send this message with\&. .RE .PP \fB\-\-action action\fR .RS 4 Specify the action which you want to perform\&. Valid options are "update" to send a message, "friends" to see your friends timeline, "public" to track public timeline, "replies" to see replies to your messages and "user" to see a specific user\*(Aqs timeline\&. .RE .PP \fB\-\-user screenname\fR .RS 4 Specify the user you want to see his/her messages while the action is "user"\&. .RE .PP \fBhost\fR .RS 4 The host you want to use to send the message to\&. Valid options is "twitter" or "custom" to specify your own server\&. .RE .PP \fBproxy\fR .RS 4 The http proxy needed to send data out to the Internet\&. .RE .PP \fBlogfile\fR .RS 4 The logfile name for bti to write what happened to\&. This file is relative to the user\*(Aqs home directory\&. If this file is not specified here or on the command line, no logging will be written to the disk\&. .RE .PP \fBreplyto\fR .RS 4 The status ID to which all notices will be linked to\&. .sp There is no sane reason for a need to have this set in a config file\&. One such reason is to have all your messages as children to a particular status\&. .RE .PP \fBshrink\-urls\fR .RS 4 Setting this variable to \*(Aqtrue\*(Aq or \*(Aqyes\*(Aq will enable the URL shrinking feature\&. This is equivalent to using the \-\-shrink\-urls option\&. .RE .PP \fBverbose\fR .RS 4 Setting this variable to \*(Aqtrue\*(Aq or \*(Aqyes\*(Aq will enable the verbose mode\&. .RE .PP There is an example config file called bti\&.example in the source tree that shows the structure of the file if you need an example to work off of\&. .PP Configuration options have the following priority: .PP .RS 4 command line option .RE .PP .RS 4 config file option .RE .PP .RS 4 environment variables .RE .PP For example, command line options always override any config file option, or any environment variables\&. Unless a config file is specified by the command line\&. At that point, the new config file is read, and any previous options set by a command line option, would be overridden\&. .SH "AUTHOR" .PP Written by Greg Kroah\-Hartman <> and Amir Mohammad Saied <>\&. bti-034/bti.c000066400000000000000000001331471226553574500131110ustar00rootroot00000000000000/* * Copyright (C) 2008-2011 Greg Kroah-Hartman * Copyright (C) 2009 Bart Trojanowski * Copyright (C) 2009-2010 Amir Mohammad Saied * * 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 version 2 of the License. * * 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. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bti.h" #define zalloc(size) calloc(size, 1) #define dbg(format, arg...) \ do { \ if (debug) \ fprintf(stdout, "bti: %s: " format , __func__ , \ ## arg); \ } while (0) int debug; static void display_help(void) { fprintf(stdout, "bti - send tweet to twitter or identi.ca\n" "Version: %s\n" "Usage:\n" " bti [options]\n" "options are:\n" " --account accountname\n" " --password password\n" " --action action\n" " ('update', 'friends', 'public', 'replies', 'user', or 'direct')\n" " --user screenname\n" " --group groupname\n" " --proxy PROXY:PORT\n" " --host HOST\n" " --logfile logfile\n" " --config configfile\n" " --replyto ID\n" " --retweet ID\n" " --shrink-urls\n" " --page PAGENUMBER\n" " --column COLUMNWIDTH\n" " --bash\n" " --background\n" " --debug\n" " --verbose\n" " --dry-run\n" " --version\n" " --help\n", VERSION); } static int strlen_utf8(char *s) { int i = 0, j = 0; while (s[i]) { if ((s[i] & 0xc0) != 0x80) j++; i++; } return j; } static void display_version(void) { fprintf(stdout, "bti - version %s\n", VERSION); } static char *get_string(const char *name) { char *temp; char *string; string = zalloc(1000); if (!string) exit(1); if (name != NULL) fprintf(stdout, "%s", name); if (!fgets(string, 999, stdin)) { free(string); return NULL; } temp = strchr(string, '\n'); if (temp) *temp = '\0'; return string; } /* * Try to get a handle to a readline function from a variety of different * libraries. If nothing is present on the system, then fall back to an * internal one. * * Logic originally based off of code in the e2fsutils package in the * lib/ss/get_readline.c file, which is licensed under the MIT license. * * This keeps us from having to relicense the bti codebase if readline * ever changes its license, as there is no link-time dependency. * It is a run-time thing only, and we handle any readline-like library * in the same manner, making bti not be a derivative work of any * other program. */ static void session_readline_init(struct session *session) { /* Libraries we will try to use for readline/editline functionality */ const char *libpath = "libreadline.so.6:libreadline.so.5:" "libreadline.so.4:libreadline.so:libedit.so.2:" "libedit.so:libeditline.so.0:libeditline.so"; void *handle = NULL; char *tmp, *cp, *next; int (*bind_key)(int, void *); void (*insert)(void); /* default to internal function if we can't or won't find anything */ session->readline = get_string; if (!isatty(0)) return; session->interactive = 1; tmp = malloc(strlen(libpath)+1); if (!tmp) return; strcpy(tmp, libpath); for (cp = tmp; cp; cp = next) { next = strchr(cp, ':'); if (next) *next++ = 0; if (*cp == 0) continue; handle = dlopen(cp, RTLD_NOW); if (handle) { dbg("Using %s for readline library\n", cp); break; } } free(tmp); if (!handle) { dbg("No readline library found.\n"); return; } session->readline_handle = handle; session->readline = (char *(*)(const char *))dlsym(handle, "readline"); if (session->readline == NULL) { /* something odd happened, default back to internal stuff */ session->readline_handle = NULL; session->readline = get_string; return; } /* * If we found a library, turn off filename expansion * as that makes no sense from within bti. */ bind_key = (int (*)(int, void *))dlsym(handle, "rl_bind_key"); insert = (void (*)(void))dlsym(handle, "rl_insert"); if (bind_key && insert) bind_key('\t', insert); } static void session_readline_cleanup(struct session *session) { if (session->readline_handle) dlclose(session->readline_handle); } static struct session *session_alloc(void) { struct session *session; session = zalloc(sizeof(*session)); if (!session) return NULL; return session; } static void session_free(struct session *session) { if (!session) return; free(session->retweet); free(session->replyto); free(session->password); free(session->account); free(session->consumer_key); free(session->consumer_secret); free(session->access_token_key); free(session->access_token_secret); free(session->tweet); free(session->proxy); free(session->time); free(session->homedir); free(session->user); free(session->group); free(session->hosturl); free(session->hostname); free(session->configfile); free(session); } static struct bti_curl_buffer *bti_curl_buffer_alloc(enum action action) { struct bti_curl_buffer *buffer; buffer = zalloc(sizeof(*buffer)); if (!buffer) return NULL; /* start out with a data buffer of 1 byte to * make the buffer fill logic simpler */ buffer->data = zalloc(1); if (!buffer->data) { free(buffer); return NULL; } buffer->length = 0; buffer->action = action; return buffer; } static void bti_curl_buffer_free(struct bti_curl_buffer *buffer) { if (!buffer) return; free(buffer->data); free(buffer); } const char twitter_host[] = "https://api.twitter.com/1.1/statuses"; const char twitter_host_stream[] = "https://stream.twitter.com/1.1/statuses"; /*this is not reset, and doesnt work */ const char twitter_host_simple[] = "https://api.twitter.com/1.1"; const char twitter_name[] = "twitter"; static const char twitter_request_token_uri[] = "https://twitter.com/oauth/request_token"; static const char twitter_access_token_uri[] = "https://twitter.com/oauth/access_token"; static const char twitter_authorize_uri[] = "https://twitter.com/oauth/authorize?oauth_token="; static const char custom_request_token_uri[] = "/../oauth/request_token?oauth_callback=oob"; static const char custom_access_token_uri[] = "/../oauth/access_token"; static const char custom_authorize_uri[] = "/../oauth/authorize?oauth_token="; static const char user_uri[] = "/user_timeline.json"; static const char update_uri[] = "/update.json"; static const char public_uri[] = "/sample.json"; static const char friends_uri[] = "/home_timeline.json"; static const char mentions_uri[] = "/mentions_timeline.json"; static const char replies_uri[] = "/replies.xml"; static const char retweet_uri[] = "/retweet/"; static const char group_uri[] = "/../statusnet/groups/timeline/"; /*static const char direct_uri[] = "/direct_messages/new.xml";*/ static const char direct_uri[] = "/direct_messages/new.json"; static const char config_default[] = "/etc/bti"; static const char config_xdg_default[] = ".config/bti"; static const char config_user_default[] = ".bti"; static CURL *curl_init(void) { CURL *curl; curl = curl_easy_init(); if (!curl) { fprintf(stderr, "Can not init CURL!\n"); return NULL; } /* some ssl sanity checks on the connection we are making */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); return curl; } static void find_config_file(struct session *session) { struct stat s; char *home; char *file; int homedir_size; /* * Get the home directory so we can try to find a config file. * If we have no home dir set up, look in /etc/bti */ home = getenv("HOME"); if (!home) { /* No home dir, so take the defaults and get out of here */ session->homedir = strdup(""); session->configfile = strdup(config_default); return; } /* We have a home dir, so this might be a user */ session->homedir = strdup(home); homedir_size = strlen(session->homedir); /* * Try to find a config file, we do so in this order: * ~/.bti old-school config file * ~/.config/bti new-school config file */ file = zalloc(homedir_size + strlen(config_user_default) + 7); sprintf(file, "%s/%s", home, config_user_default); if (stat(file, &s) == 0) { /* Found the config file at ~/.bti */ session->configfile = strdup(file); free(file); return; } free(file); file = zalloc(homedir_size + strlen(config_xdg_default) + 7); sprintf(file, "%s/%s", home, config_xdg_default); if (stat(file, &s) == 0) { /* config file is at ~/.config/bti */ session->configfile = strdup(file); free(file); return; } /* No idea where the config file is, so punt */ free(file); session->configfile = strdup(""); } /* The final place data is sent to the screen/pty/tty */ static void bti_output_line(struct session *session, xmlChar *user, xmlChar *id, xmlChar *created, xmlChar *text) { if (session->verbose) printf("[%*s] {%s} (%.16s) %s\n", -session->column_output, user, id, created, text); else printf("[%*s] %s\n", -session->column_output, user, text); } static void parse_statuses(struct session *session, xmlDocPtr doc, xmlNodePtr current) { xmlChar *text = NULL; xmlChar *user = NULL; xmlChar *created = NULL; xmlChar *id = NULL; xmlNodePtr userinfo; current = current->xmlChildrenNode; while (current != NULL) { if (current->type == XML_ELEMENT_NODE) { if (!xmlStrcmp(current->name, (const xmlChar *)"created_at")) created = xmlNodeListGetString(doc, current->xmlChildrenNode, 1); if (!xmlStrcmp(current->name, (const xmlChar *)"text")) text = xmlNodeListGetString(doc, current->xmlChildrenNode, 1); if (!xmlStrcmp(current->name, (const xmlChar *)"id")) id = xmlNodeListGetString(doc, current->xmlChildrenNode, 1); if (!xmlStrcmp(current->name, (const xmlChar *)"user")) { userinfo = current->xmlChildrenNode; while (userinfo != NULL) { if ((!xmlStrcmp(userinfo->name, (const xmlChar *)"screen_name"))) { if (user) xmlFree(user); user = xmlNodeListGetString(doc, userinfo->xmlChildrenNode, 1); } userinfo = userinfo->next; } } if (user && text && created && id) { bti_output_line(session, user, id, created, text); xmlFree(user); xmlFree(text); xmlFree(created); xmlFree(id); user = NULL; text = NULL; created = NULL; id = NULL; } } current = current->next; } return; } static void parse_timeline(char *document, struct session *session) { xmlDocPtr doc; xmlNodePtr current; doc = xmlReadMemory(document, strlen(document), "timeline.xml", NULL, XML_PARSE_NOERROR); if (doc == NULL) return; current = xmlDocGetRootElement(doc); if (current == NULL) { fprintf(stderr, "empty document\n"); xmlFreeDoc(doc); return; } if (xmlStrcmp(current->name, (const xmlChar *) "statuses")) { fprintf(stderr, "unexpected document type\n"); xmlFreeDoc(doc); return; } current = current->xmlChildrenNode; while (current != NULL) { if ((!xmlStrcmp(current->name, (const xmlChar *)"status"))) parse_statuses(session, doc, current); current = current->next; } xmlFreeDoc(doc); return; } /* avoids the c99 option */ #define json_object_object_foreach_alt(obj,key,val) \ char *key; \ struct json_object *val; \ struct lh_entry *entry; \ for (entry = json_object_get_object(obj)->head; \ ({ if(entry && !is_error(entry)) { \ key = (char*)entry->k; \ val = (struct json_object*)entry->v; \ } ; entry; }); \ entry = entry->next ) /* Forward Declaration */ static void json_parse(json_object * jobj, int nestlevel); static void print_json_value(json_object *jobj, int nestlevel) { enum json_type type; type = json_object_get_type(jobj); switch (type) { case json_type_boolean: printf("boolean "); printf("value: %s\n", json_object_get_boolean(jobj)? "true": "false"); break; case json_type_double: printf("double "); printf("value: %lf\n", json_object_get_double(jobj)); break; case json_type_int: printf("int "); printf("value: %d\n", json_object_get_int(jobj)); break; case json_type_string: printf("string "); printf("value: %s\n", json_object_get_string(jobj)); break; default: break; } } #define MAXKEYSTACK 20 char *keystack[MAXKEYSTACK]; static void json_parse_array(json_object *jobj, char *key, int nestlevel) { enum json_type type; nestlevel++; /* Simply get the array */ json_object *jarray = jobj; if (key) { /* Get the array if it is a key value pair */ jarray = json_object_object_get(jobj, key); } /* Get the length of the array */ int arraylen = json_object_array_length(jarray); if (debug) printf("Array Length: %d\n",arraylen); int i; json_object *jvalue; for (i = 0; i < arraylen; i++) { if (debug) { int j; for (j=0; j < nestlevel; ++j) printf(" "); printf("element[%d]\n",i); } /* Get the array element at position i */ jvalue = json_object_array_get_idx(jarray, i); type = json_object_get_type(jvalue); if (type == json_type_array) { json_parse_array(jvalue, NULL, nestlevel); } else if (type != json_type_object) { if (debug) { printf("value[%d]: ", i); print_json_value(jvalue,nestlevel); } } else { /* printf("obj: "); */ keystack[nestlevel%MAXKEYSTACK]="[]"; json_parse(jvalue,nestlevel); } } } struct results { int code; char *message; } results; struct session *store_session; struct tweetdetail { char *id; char *text; char *screen_name; char *created_at; } tweetdetail; static void json_interpret(json_object *jobj, int nestlevel) { if (nestlevel == 3 && strcmp(keystack[1], "errors") == 0 && strcmp(keystack[2], "[]") == 0) { if (strcmp(keystack[3], "message") == 0) { if (json_object_get_type(jobj) == json_type_string) results.message = (char *)json_object_get_string(jobj); } if (strcmp(keystack[3], "code") == 0) { if (json_object_get_type(jobj) == json_type_int) results.code = json_object_get_int(jobj); } } if (nestlevel >= 2 && strcmp(keystack[1],"[]") == 0) { if (strcmp(keystack[2], "created_at") == 0) { if (debug) printf("%s : %s\n", keystack[2], json_object_get_string(jobj)); tweetdetail.created_at = (char *)json_object_get_string(jobj); } if (strcmp(keystack[2], "text") == 0) { if (debug) printf("%s : %s\n", keystack[2], json_object_get_string(jobj)); tweetdetail.text = (char *)json_object_get_string(jobj); } if (strcmp(keystack[2], "id") == 0) { if (debug) printf("%s : %s\n", keystack[2], json_object_get_string(jobj)); tweetdetail.id = (char *)json_object_get_string(jobj); } if (nestlevel >= 3 && strcmp(keystack[2], "user") == 0) { if (strcmp(keystack[3], "screen_name") == 0) { if (debug) printf("%s->%s : %s\n", keystack[2], keystack[3], json_object_get_string(jobj)); tweetdetail.screen_name=(char *)json_object_get_string(jobj); bti_output_line(store_session, (xmlChar *)tweetdetail.screen_name, (xmlChar *)tweetdetail.id, (xmlChar *)tweetdetail.created_at, (xmlChar *)tweetdetail.text); } } } } /* Parsing the json object */ static void json_parse(json_object * jobj, int nestlevel) { int i; if (jobj==NULL) { fprintf(stderr,"jobj null\n"); return; } nestlevel++; enum json_type type; json_object_object_foreach_alt(jobj, key, val) { /* work around pre10 */ if (val) type = json_object_get_type(val); else type=json_type_null; if (debug) for (i = 0; i < nestlevel; ++i) printf(" "); if (debug) printf("key %-34s ", key); if (debug) for (i = 0; i < 8 - nestlevel; ++i) printf(" "); switch (type) { case json_type_boolean: case json_type_double: case json_type_int: case json_type_string: if (debug) print_json_value(val,nestlevel); if (debug) for (i=0; i",keystack[i]); } if (debug) printf("%s)\n",key); keystack[nestlevel%MAXKEYSTACK] = key; json_interpret(val,nestlevel); break; case json_type_object: if (debug) printf("json_type_object\n"); keystack[nestlevel%MAXKEYSTACK] = key; json_parse(json_object_object_get(jobj, key), nestlevel); break; case json_type_array: if (debug) printf("json_type_array, "); keystack[nestlevel%MAXKEYSTACK] = key; json_parse_array(jobj, key, nestlevel); break; case json_type_null: if (debug) printf("null\n"); break; default: if (debug) printf("\n"); break; } } } static int parse_response_json(char *document, struct session *session) { dbg("Got this json response:\n"); dbg("%s\n",document); results.code=0; results.message=NULL; json_object *jobj = json_tokener_parse(document); /* make global for now */ store_session = session; if (!is_error(jobj)) { /* guards against a json pre 0.10 bug */ json_parse(jobj,0); } if (results.code && results.message != NULL) { if (debug) printf("Got an error code:\n code=%d\n message=%s\n", results.code, results.message); fprintf(stderr, "error condition detected: %d = %s\n", results.code, results.message); return -EIO; } return 0; } static void parse_timeline_json(char *document, struct session *session) { dbg("Got this json response:\n"); dbg("%s\n",document); results.code = 0; results.message = NULL; json_object *jobj = json_tokener_parse(document); /* make global for now */ store_session = session; if (!is_error(jobj)) { /* guards against a json pre 0.10 bug */ if (json_object_get_type(jobj)==json_type_array) { json_parse_array(jobj, NULL, 0); } else { json_parse(jobj,0); } } if (results.code && results.message != NULL) { if (debug) printf("Got an error code:\n code=%d\n message=%s\n", results.code, results.message); fprintf(stderr, "error condition detected: %d = %s\n", results.code, results.message); } } static size_t curl_callback(void *buffer, size_t size, size_t nmemb, void *userp) { struct bti_curl_buffer *curl_buf = userp; size_t buffer_size = size * nmemb; char *temp; if ((!buffer) || (!buffer_size) || (!curl_buf)) return -EINVAL; /* add to the data we already have */ temp = zalloc(curl_buf->length + buffer_size + 1); if (!temp) return -ENOMEM; memcpy(temp, curl_buf->data, curl_buf->length); free(curl_buf->data); curl_buf->data = temp; memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size); curl_buf->length += buffer_size; if (curl_buf->action) parse_timeline(curl_buf->data, curl_buf->session); dbg("%s\n", curl_buf->data); return buffer_size; } static int parse_osp_reply(const char *reply, char **token, char **secret) { int rc; int retval = 1; char **rv = NULL; rc = oauth_split_url_parameters(reply, &rv); qsort(rv, rc, sizeof(char *), oauth_cmpstringp); if (rc == 2 || rc == 4) { if (!strncmp(rv[0], "oauth_token=", 11) && !strncmp(rv[1], "oauth_token_secret=", 18)) { if (token) *token = strdup(&(rv[0][12])); if (secret) *secret = strdup(&(rv[1][19])); retval = 0; } } else if (rc == 3) { if (!strncmp(rv[1], "oauth_token=", 11) && !strncmp(rv[2], "oauth_token_secret=", 18)) { if (token) *token = strdup(&(rv[1][12])); if (secret) *secret = strdup(&(rv[2][19])); retval = 0; } } dbg("token: %s\n", *token); dbg("secret: %s\n", *secret); if (rv) free(rv); return retval; } static int request_access_token(struct session *session) { char *post_params = NULL; char *request_url = NULL; char *reply = NULL; char *at_key = NULL; char *at_secret = NULL; char *verifier = NULL; char at_uri[90]; char token_uri[90]; if (!session) return -EINVAL; if (session->host == HOST_TWITTER) request_url = oauth_sign_url2( twitter_request_token_uri, NULL, OA_HMAC, NULL, session->consumer_key, session->consumer_secret, NULL, NULL); else { sprintf(token_uri, "%s%s", session->hosturl, custom_request_token_uri); request_url = oauth_sign_url2( token_uri, NULL, OA_HMAC, NULL, session->consumer_key, session->consumer_secret, NULL, NULL); } reply = oauth_http_get(request_url, post_params); if (request_url) free(request_url); if (post_params) free(post_params); if (!reply) return 1; if (parse_osp_reply(reply, &at_key, &at_secret)) return 1; free(reply); fprintf(stdout, "Please open the following link in your browser, and " "allow 'bti' to access your account. Then paste " "back the provided PIN in here.\n"); if (session->host == HOST_TWITTER) { fprintf(stdout, "%s%s\nPIN: ", twitter_authorize_uri, at_key); verifier = session->readline(NULL); sprintf(at_uri, "%s?oauth_verifier=%s", twitter_access_token_uri, verifier); } else { fprintf(stdout, "%s%s%s\nPIN: ", session->hosturl, custom_authorize_uri, at_key); verifier = session->readline(NULL); sprintf(at_uri, "%s%s?oauth_verifier=%s", session->hosturl, custom_access_token_uri, verifier); } request_url = oauth_sign_url2(at_uri, NULL, OA_HMAC, NULL, session->consumer_key, session->consumer_secret, at_key, at_secret); reply = oauth_http_get(request_url, post_params); if (!reply) return 1; if (parse_osp_reply(reply, &at_key, &at_secret)) return 1; free(reply); fprintf(stdout, "Please put these two lines in your bti " "configuration file (%s):\n" "access_token_key=%s\n" "access_token_secret=%s\n", session->configfile, at_key, at_secret); return 0; } static int send_request(struct session *session) { const int endpoint_size = 2000; char endpoint[endpoint_size]; char user_password[500]; char data[500]; struct bti_curl_buffer *curl_buf; CURL *curl = NULL; CURLcode res; struct curl_httppost *formpost = NULL; struct curl_httppost *lastptr = NULL; struct curl_slist *slist = NULL; char *req_url = NULL; char *reply = NULL; char *postarg = NULL; char *escaped_tweet = NULL; int is_post = 0; if (!session) return -EINVAL; if (!session->hosturl) session->hosturl = strdup(twitter_host); if (session->no_oauth || session->guest) { curl_buf = bti_curl_buffer_alloc(session->action); if (!curl_buf) return -ENOMEM; curl_buf->session = session; curl = curl_init(); if (!curl) { bti_curl_buffer_free(curl_buf); return -EINVAL; } if (!session->hosturl) session->hosturl = strdup(twitter_host); switch (session->action) { case ACTION_UPDATE: snprintf(user_password, sizeof(user_password), "%s:%s", session->account, session->password); snprintf(data, sizeof(data), "status=\"%s\"", session->tweet); curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "status", CURLFORM_COPYCONTENTS, session->tweet, CURLFORM_END); curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "source", CURLFORM_COPYCONTENTS, "bti", CURLFORM_END); if (session->replyto) curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "in_reply_to_status_id", CURLFORM_COPYCONTENTS, session->replyto, CURLFORM_END); curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); slist = curl_slist_append(slist, "Expect:"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); snprintf(endpoint, endpoint_size, "%s%s", session->hosturl, update_uri); curl_easy_setopt(curl, CURLOPT_URL, endpoint); curl_easy_setopt(curl, CURLOPT_USERPWD, user_password); break; case ACTION_FRIENDS: snprintf(user_password, sizeof(user_password), "%s:%s", session->account, session->password); snprintf(endpoint, endpoint_size, "%s%s?page=%d", session->hosturl, friends_uri, session->page); curl_easy_setopt(curl, CURLOPT_URL, endpoint); curl_easy_setopt(curl, CURLOPT_USERPWD, user_password); break; case ACTION_USER: snprintf(endpoint, endpoint_size, "%s%s%s.xml?page=%d", session->hosturl, user_uri, session->user, session->page); curl_easy_setopt(curl, CURLOPT_URL, endpoint); break; case ACTION_REPLIES: snprintf(user_password, sizeof(user_password), "%s:%s", session->account, session->password); snprintf(endpoint, endpoint_size, "%s%s?page=%d", session->hosturl, replies_uri, session->page); curl_easy_setopt(curl, CURLOPT_URL, endpoint); curl_easy_setopt(curl, CURLOPT_USERPWD, user_password); break; case ACTION_PUBLIC: /*snprintf(endpoint, endpoint_size, "%s%s?page=%d", session->hosturl,*/ snprintf(endpoint, endpoint_size, "%s%s", twitter_host_stream, public_uri); curl_easy_setopt(curl, CURLOPT_URL, endpoint); break; case ACTION_GROUP: snprintf(endpoint, endpoint_size, "%s%s%s.xml?page=%d", session->hosturl, group_uri, session->group, session->page); curl_easy_setopt(curl, CURLOPT_URL, endpoint); break; case ACTION_DIRECT: /* NOT IMPLEMENTED - twitter requires authentication anyway */ break; default: break; } if (session->proxy) curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy); if (debug) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); dbg("user_password = %s\n", user_password); dbg("data = %s\n", data); dbg("proxy = %s\n", session->proxy); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf); if (!session->dry_run) { res = curl_easy_perform(curl); if (!session->background) { xmlDocPtr doc; xmlNodePtr current; if (res) { fprintf(stderr, "error(%d) trying to perform operation\n", res); curl_easy_cleanup(curl); if (session->action == ACTION_UPDATE) curl_formfree(formpost); bti_curl_buffer_free(curl_buf); return -EINVAL; } doc = xmlReadMemory(curl_buf->data, curl_buf->length, "response.xml", NULL, XML_PARSE_NOERROR); if (doc == NULL) { curl_easy_cleanup(curl); if (session->action == ACTION_UPDATE) curl_formfree(formpost); bti_curl_buffer_free(curl_buf); return -EINVAL; } current = xmlDocGetRootElement(doc); if (current == NULL) { fprintf(stderr, "empty document\n"); xmlFreeDoc(doc); curl_easy_cleanup(curl); if (session->action == ACTION_UPDATE) curl_formfree(formpost); bti_curl_buffer_free(curl_buf); return -EINVAL; } if (xmlStrcmp(current->name, (const xmlChar *)"status")) { fprintf(stderr, "unexpected document type\n"); xmlFreeDoc(doc); curl_easy_cleanup(curl); if (session->action == ACTION_UPDATE) curl_formfree(formpost); bti_curl_buffer_free(curl_buf); return -EINVAL; } xmlFreeDoc(doc); } } curl_easy_cleanup(curl); if (session->action == ACTION_UPDATE) curl_formfree(formpost); bti_curl_buffer_free(curl_buf); } else { switch (session->action) { case ACTION_UPDATE: /* dont test it here, let twitter return an error that we show */ if (strlen_utf8(session->tweet) > 140 + 1000 ) { printf("E: tweet is too long!\n"); goto skip_tweet; } /* TODO: add tweet crunching function. */ escaped_tweet = oauth_url_escape(session->tweet); if (session->replyto) { sprintf(endpoint, "%s%s?status=%s&in_reply_to_status_id=%s", session->hosturl, update_uri, escaped_tweet, session->replyto); } else { sprintf(endpoint, "%s%s?status=%s", session->hosturl, update_uri, escaped_tweet); } is_post = 1; break; case ACTION_USER: sprintf(endpoint, "%s%s?screen_name=%s&page=%d", session->hosturl, user_uri, session->user, session->page); break; case ACTION_REPLIES: sprintf(endpoint, "%s%s?page=%d", session->hosturl, mentions_uri, session->page); break; case ACTION_PUBLIC: sprintf(endpoint, "%s%s", twitter_host_stream, public_uri); break; case ACTION_GROUP: sprintf(endpoint, "%s%s%s.xml?page=%d", session->hosturl, group_uri, session->group, session->page); break; case ACTION_FRIENDS: sprintf(endpoint, "%s%s?page=%d", session->hosturl, friends_uri, session->page); break; case ACTION_RETWEET: sprintf(endpoint, "%s%s%s.xml", session->hosturl, retweet_uri, session->retweet); is_post = 1; break; case ACTION_DIRECT: escaped_tweet = oauth_url_escape(session->tweet); sprintf(endpoint, "%s%s?user=%s&text=%s", twitter_host_simple, direct_uri, session->user, escaped_tweet); is_post = 1; break; default: break; } dbg("%s\n", endpoint); if (!session->dry_run) { if (is_post) { req_url = oauth_sign_url2(endpoint, &postarg, OA_HMAC, NULL, session->consumer_key, session->consumer_secret, session->access_token_key, session->access_token_secret); reply = oauth_http_post(req_url, postarg); } else { req_url = oauth_sign_url2(endpoint, NULL, OA_HMAC, NULL, session->consumer_key, session->consumer_secret, session->access_token_key, session->access_token_secret); reply = oauth_http_get(req_url, postarg); } dbg("req_url:%s\n", req_url); dbg("reply:%s\n", reply); if (req_url) free(req_url); if (!reply) { fprintf(stderr, "Error retrieving from URL (%s)\n", endpoint); return -EIO; } if ((session->action != ACTION_UPDATE) && (session->action != ACTION_RETWEET) && (session->action != ACTION_DIRECT)) parse_timeline_json(reply, session); if ((session->action == ACTION_UPDATE) || (session->action == ACTION_DIRECT)) /*return parse_response_xml(reply, session);*/ return parse_response_json(reply, session); } skip_tweet: ; } return 0; } static void log_session(struct session *session, int retval) { FILE *log_file; char *filename; /* Only log something if we have a log file set */ if (!session->logfile) return; filename = alloca(strlen(session->homedir) + strlen(session->logfile) + 3); sprintf(filename, "%s/%s", session->homedir, session->logfile); log_file = fopen(filename, "a+"); if (log_file == NULL) return; switch (session->action) { case ACTION_UPDATE: if (retval) fprintf(log_file, "%s: host=%s tweet failed\n", session->time, session->hostname); else fprintf(log_file, "%s: host=%s tweet=%s\n", session->time, session->hostname, session->tweet); break; case ACTION_FRIENDS: fprintf(log_file, "%s: host=%s retrieving friends timeline\n", session->time, session->hostname); break; case ACTION_USER: fprintf(log_file, "%s: host=%s retrieving %s's timeline\n", session->time, session->hostname, session->user); break; case ACTION_REPLIES: fprintf(log_file, "%s: host=%s retrieving replies\n", session->time, session->hostname); break; case ACTION_PUBLIC: fprintf(log_file, "%s: host=%s retrieving public timeline\n", session->time, session->hostname); break; case ACTION_GROUP: fprintf(log_file, "%s: host=%s retrieving group timeline\n", session->time, session->hostname); break; case ACTION_DIRECT: if (retval) fprintf(log_file, "%s: host=%s tweet failed\n", session->time, session->hostname); else fprintf(log_file, "%s: host=%s tweet=%s\n", session->time, session->hostname, session->tweet); break; default: break; } fclose(log_file); } static char *get_string_from_stdin(void) { char *temp; char *string; string = zalloc(1000); if (!string) return NULL; if (!fgets(string, 999, stdin)) { free(string); return NULL; } temp = strchr(string, '\n'); if (temp) *temp = '\0'; return string; } static void read_password(char *buf, size_t len, char *host) { char pwd[80]; struct termios old; struct termios tp; tcgetattr(0, &tp); old = tp; tp.c_lflag &= (~ECHO); tcsetattr(0, TCSANOW, &tp); fprintf(stdout, "Enter password for %s: ", host); fflush(stdout); tcflow(0, TCOOFF); /* * I'd like to do something with the return value here, but really, * what can be done? */ (void)scanf("%79s", pwd); tcflow(0, TCOON); fprintf(stdout, "\n"); tcsetattr(0, TCSANOW, &old); strncpy(buf, pwd, len); buf[len-1] = '\0'; } static int find_urls(const char *tweet, int **pranges) { /* * magic obtained from * http://www.geekpedia.com/KB65_How-to-validate-an-URL-using-RegEx-in-Csharp.html */ static const char *re_magic = "(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)/{1,3}" "[0-9a-zA-Z;/~?:@&=+$\\.\\-_'()%]+)" "(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?"; pcre *re; const char *errptr; int erroffset; int ovector[10] = {0,}; const size_t ovsize = sizeof(ovector)/sizeof(*ovector); int startoffset, tweetlen; int i, rc; int rbound = 10; int rcount = 0; int *ranges = malloc(sizeof(int) * rbound); re = pcre_compile(re_magic, PCRE_NO_AUTO_CAPTURE, &errptr, &erroffset, NULL); if (!re) { fprintf(stderr, "pcre_compile @%u: %s\n", erroffset, errptr); exit(1); } tweetlen = strlen(tweet); for (startoffset = 0; startoffset < tweetlen; ) { rc = pcre_exec(re, NULL, tweet, strlen(tweet), startoffset, 0, ovector, ovsize); if (rc == PCRE_ERROR_NOMATCH) break; if (rc < 0) { fprintf(stderr, "pcre_exec @%u: %s\n", erroffset, errptr); exit(1); } for (i = 0; i < rc; i += 2) { if ((rcount+2) == rbound) { rbound *= 2; ranges = realloc(ranges, sizeof(int) * rbound); } ranges[rcount++] = ovector[i]; ranges[rcount++] = ovector[i+1]; } startoffset = ovector[1]; } pcre_free(re); *pranges = ranges; return rcount; } /** * bidirectional popen() call * * @param rwepipe - int array of size three * @param exe - program to run * @param argv - argument list * @return pid or -1 on error * * The caller passes in an array of three integers (rwepipe), on successful * execution it can then write to element 0 (stdin of exe), and read from * element 1 (stdout) and 2 (stderr). */ static int popenRWE(int *rwepipe, const char *exe, const char *const argv[]) { int in[2]; int out[2]; int err[2]; int pid; int rc; rc = pipe(in); if (rc < 0) goto error_in; rc = pipe(out); if (rc < 0) goto error_out; rc = pipe(err); if (rc < 0) goto error_err; pid = fork(); if (pid > 0) { /* parent */ close(in[0]); close(out[1]); close(err[1]); rwepipe[0] = in[1]; rwepipe[1] = out[0]; rwepipe[2] = err[0]; return pid; } else if (pid == 0) { /* child */ close(in[1]); close(out[0]); close(err[0]); close(0); rc = dup(in[0]); close(1); rc = dup(out[1]); close(2); rc = dup(err[1]); execvp(exe, (char **)argv); exit(1); } else goto error_fork; return pid; error_fork: close(err[0]); close(err[1]); error_err: close(out[0]); close(out[1]); error_out: close(in[0]); close(in[1]); error_in: return -1; } static int pcloseRWE(int pid, int *rwepipe) { int status; close(rwepipe[0]); close(rwepipe[1]); close(rwepipe[2]); (void)waitpid(pid, &status, 0); return status; } static char *shrink_one_url(int *rwepipe, char *big) { int biglen = strlen(big); char *small; int smalllen; int rc; rc = dprintf(rwepipe[0], "%s\n", big); if (rc < 0) return big; smalllen = biglen + 128; small = malloc(smalllen); if (!small) return big; rc = read(rwepipe[1], small, smalllen); if (rc < 0 || rc > biglen) goto error_free_small; if (strncmp(small, "http://", 7)) goto error_free_small; smalllen = rc; while (smalllen && isspace(small[smalllen-1])) small[--smalllen] = 0; free(big); return small; error_free_small: free(small); return big; } static char *shrink_urls(char *text) { int *ranges; int rcount; int i; int inofs = 0; int outofs = 0; const char *const shrink_args[] = { "bti-shrink-urls", NULL }; int shrink_pid; int shrink_pipe[3]; int inlen = strlen(text); dbg("before len=%u\n", inlen); shrink_pid = popenRWE(shrink_pipe, shrink_args[0], shrink_args); if (shrink_pid < 0) return text; rcount = find_urls(text, &ranges); if (!rcount) return text; for (i = 0; i < rcount; i += 2) { int url_start = ranges[i]; int url_end = ranges[i+1]; int long_url_len = url_end - url_start; char *url = strndup(text + url_start, long_url_len); int short_url_len; int not_url_len = url_start - inofs; dbg("long url[%u]: %s\n", long_url_len, url); url = shrink_one_url(shrink_pipe, url); short_url_len = url ? strlen(url) : 0; dbg("short url[%u]: %s\n", short_url_len, url); if (!url || short_url_len >= long_url_len) { /* The short url ended up being too long * or unavailable */ if (inofs) { strncpy(text + outofs, text + inofs, not_url_len + long_url_len); } inofs += not_url_len + long_url_len; outofs += not_url_len + long_url_len; } else { /* copy the unmodified block */ strncpy(text + outofs, text + inofs, not_url_len); inofs += not_url_len; outofs += not_url_len; /* copy the new url */ strncpy(text + outofs, url, short_url_len); inofs += long_url_len; outofs += short_url_len; } free(url); } /* copy the last block after the last match */ if (inofs) { int tail = inlen - inofs; if (tail) { strncpy(text + outofs, text + inofs, tail); outofs += tail; } } free(ranges); (void)pcloseRWE(shrink_pid, shrink_pipe); text[outofs] = 0; dbg("after len=%u\n", outofs); return text; } int main(int argc, char *argv[], char *envp[]) { static const struct option options[] = { { "debug", 0, NULL, 'd' }, { "verbose", 0, NULL, 'V' }, { "account", 1, NULL, 'a' }, { "password", 1, NULL, 'p' }, { "host", 1, NULL, 'H' }, { "proxy", 1, NULL, 'P' }, { "action", 1, NULL, 'A' }, { "user", 1, NULL, 'u' }, { "group", 1, NULL, 'G' }, { "logfile", 1, NULL, 'L' }, { "shrink-urls", 0, NULL, 's' }, { "help", 0, NULL, 'h' }, { "bash", 0, NULL, 'b' }, { "background", 0, NULL, 'B' }, { "dry-run", 0, NULL, 'n' }, { "page", 1, NULL, 'g' }, { "column", 1, NULL, 'o' }, { "version", 0, NULL, 'v' }, { "config", 1, NULL, 'c' }, { "replyto", 1, NULL, 'r' }, { "retweet", 1, NULL, 'w' }, { } }; struct stat s; struct session *session; pid_t child; char *tweet; static char password[80]; int retval = 0; int option; char *http_proxy; time_t t; int page_nr; debug = 0; session = session_alloc(); if (!session) { fprintf(stderr, "no more memory...\n"); return -1; } /* get the current time so that we can log it later */ time(&t); session->time = strdup(ctime(&t)); session->time[strlen(session->time)-1] = 0x00; find_config_file(session); /* Set environment variables first, before reading command line options * or config file values. */ http_proxy = getenv("http_proxy"); if (http_proxy) { if (session->proxy) free(session->proxy); session->proxy = strdup(http_proxy); dbg("http_proxy = %s\n", session->proxy); } bti_parse_configfile(session); while (1) { option = getopt_long_only(argc, argv, "dp:P:H:a:A:u:c:hg:o:G:sr:nVvw:", options, NULL); if (option == -1) break; switch (option) { case 'd': debug = 1; break; case 'V': session->verbose = 1; break; case 'a': if (session->account) free(session->account); session->account = strdup(optarg); dbg("account = %s\n", session->account); break; case 'g': page_nr = atoi(optarg); dbg("page = %d\n", page_nr); session->page = page_nr; break; case 'o': session->column_output = atoi(optarg); dbg("column_output = %d\n", session->column_output); break; case 'r': session->replyto = strdup(optarg); dbg("in_reply_to_status_id = %s\n", session->replyto); break; case 'w': session->retweet = strdup(optarg); dbg("Retweet ID = %s\n", session->retweet); break; case 'p': if (session->password) free(session->password); session->password = strdup(optarg); dbg("password = %s\n", session->password); break; case 'P': if (session->proxy) free(session->proxy); session->proxy = strdup(optarg); dbg("proxy = %s\n", session->proxy); break; case 'A': if (strcasecmp(optarg, "update") == 0) session->action = ACTION_UPDATE; else if (strcasecmp(optarg, "friends") == 0) session->action = ACTION_FRIENDS; else if (strcasecmp(optarg, "user") == 0) session->action = ACTION_USER; else if (strcasecmp(optarg, "replies") == 0) session->action = ACTION_REPLIES; else if (strcasecmp(optarg, "public") == 0) session->action = ACTION_PUBLIC; else if (strcasecmp(optarg, "group") == 0) session->action = ACTION_GROUP; else if (strcasecmp(optarg, "retweet") == 0) session->action = ACTION_RETWEET; else if (strcasecmp(optarg, "direct") == 0) session->action = ACTION_DIRECT; else session->action = ACTION_UNKNOWN; dbg("action = %d\n", session->action); break; case 'u': if (session->user) free(session->user); session->user = strdup(optarg); dbg("user = %s\n", session->user); break; case 'G': if (session->group) free(session->group); session->group = strdup(optarg); dbg("group = %s\n", session->group); break; case 'L': if (session->logfile) free(session->logfile); session->logfile = strdup(optarg); dbg("logfile = %s\n", session->logfile); break; case 's': session->shrink_urls = 1; break; case 'H': if (session->hosturl) free(session->hosturl); if (session->hostname) free(session->hostname); if (strcasecmp(optarg, "twitter") == 0) { session->host = HOST_TWITTER; session->hosturl = strdup(twitter_host); session->hostname = strdup(twitter_name); } else { session->host = HOST_CUSTOM; session->hosturl = strdup(optarg); session->hostname = strdup(optarg); } dbg("host = %d\n", session->host); break; case 'b': session->bash = 1; /* fall-through intended */ case 'B': session->background = 1; break; case 'c': if (session->configfile) free(session->configfile); session->configfile = strdup(optarg); dbg("configfile = %s\n", session->configfile); if (stat(session->configfile, &s) == -1) { fprintf(stderr, "Config file '%s' is not found.\n", session->configfile); goto exit; } /* * read the config file now. Yes, this could override * previously set options from the command line, but * the user asked for it... */ bti_parse_configfile(session); break; case 'h': display_help(); goto exit; case 'n': session->dry_run = 1; break; case 'v': display_version(); goto exit; default: display_help(); goto exit; } } session_readline_init(session); /* * Show the version to make it easier to determine what * is going on here */ if (debug) display_version(); if (session->host == HOST_TWITTER) { if (!session->consumer_key || !session->consumer_secret) { if (session->action == ACTION_USER || session->action == ACTION_PUBLIC) { /* * Some actions may still work without * authentication */ session->guest = 1; } else { fprintf(stderr, "Twitter no longer supports HTTP basic authentication.\n" "Both consumer key, and consumer secret are required" " for bti in order to behave as an OAuth consumer.\n"); goto exit; } } if (session->action == ACTION_GROUP) { fprintf(stderr, "Groups only work in Identi.ca.\n"); goto exit; } } else { if (!session->consumer_key || !session->consumer_secret) session->no_oauth = 1; } if (session->no_oauth) { if (!session->account) { fprintf(stdout, "Enter account for %s: ", session->hostname); session->account = session->readline(NULL); } if (!session->password) { read_password(password, sizeof(password), session->hostname); session->password = strdup(password); } } else if (!session->guest) { if (!session->access_token_key || !session->access_token_secret) { request_access_token(session); goto exit; } } if (session->action == ACTION_UNKNOWN) { fprintf(stderr, "Unknown action, valid actions are:\n" "'update', 'friends', 'public', 'replies', 'group', 'user' or 'direct'.\n"); goto exit; } if (session->action == ACTION_GROUP && !session->group) { fprintf(stdout, "Enter group name: "); session->group = session->readline(NULL); } if (session->action == ACTION_RETWEET) { if (!session->retweet) { char *rtid; fprintf(stdout, "Status ID to retweet: "); rtid = get_string_from_stdin(); session->retweet = zalloc(strlen(rtid) + 10); sprintf(session->retweet, "%s", rtid); free(rtid); } if (!session->retweet || strlen(session->retweet) == 0) { dbg("no retweet?\n"); return -1; } dbg("retweet ID = %s\n", session->retweet); } if (session->action == ACTION_UPDATE || session->action == ACTION_DIRECT) { if (session->background || !session->interactive) tweet = get_string_from_stdin(); else tweet = session->readline("tweet: "); if (!tweet || strlen(tweet) == 0) { dbg("no tweet?\n"); return -1; } if (session->shrink_urls) tweet = shrink_urls(tweet); session->tweet = zalloc(strlen(tweet) + 10); if (session->bash) sprintf(session->tweet, "%c %s", getuid() ? '$' : '#', tweet); else sprintf(session->tweet, "%s", tweet); free(tweet); dbg("tweet = %s\n", session->tweet); } if (session->page == 0) session->page = 1; dbg("config file = %s\n", session->configfile); dbg("host = %d\n", session->host); dbg("action = %d\n", session->action); /* fork ourself so that the main shell can get on * with it's life as we try to connect and handle everything */ if (session->background) { child = fork(); if (child) { dbg("child is %d\n", child); exit(0); } } retval = send_request(session); if (retval && !session->background) fprintf(stderr, "operation failed\n"); log_session(session, retval); exit: session_readline_cleanup(session); session_free(session); return retval; } bti-034/bti.example000066400000000000000000000010651226553574500143130ustar00rootroot00000000000000# comments are allowed in the bti config file # rename this to ~/.bti so that you do not need # to constantly enter your account name and/or # password on the command line every time you send # a message. account=twitmaster password=icanhascheezburger host=identica # Example of a custom StatusNet installation #host=http://army.twit.tv/api/statuses logfile=.bti.log #action=update #user=gregkh #proxy=http://localhost:8080 #shrink-urls=yes # Example of using bit.ly in bti-shrink-urls #shrink_host=bit.ly #shrink_bitly_login=bitlyuser #shrink_bitly_key=R_deadbeef bti-034/bti.h000066400000000000000000000036271226553574500131150ustar00rootroot00000000000000/* * Copyright (C) 2008-2011 Greg Kroah-Hartman * Copyright (C) 2009 Bart Trojanowski * Copyright (C) 2009-2010 Amir Mohammad Saied * * 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 version 2 of the License. * * 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. */ #ifndef __BTI_H #define __BTI_H enum host { HOST_TWITTER = 0, HOST_CUSTOM = 2 }; enum action { ACTION_UPDATE = 0, ACTION_FRIENDS = 1, ACTION_USER = 2, ACTION_REPLIES = 4, ACTION_PUBLIC = 8, ACTION_GROUP = 16, ACTION_RETWEET = 32, ACTION_DIRECT = 64, ACTION_UNKNOWN = 128 }; struct session { char *password; char *account; char *consumer_key; char *consumer_secret; char *access_token_key; char *access_token_secret; char *tweet; char *proxy; char *time; char *homedir; char *logfile; char *user; char *group; char *hosturl; char *hostname; char *configfile; char *replyto; char *retweet; int bash; int background; int interactive; int shrink_urls; int dry_run; int page; int no_oauth; int guest; int verbose; int column_output; enum host host; enum action action; void *readline_handle; char *(*readline)(const char *); }; struct bti_curl_buffer { char *data; struct session *session; enum action action; int length; }; extern const char twitter_host[]; extern const char twitter_name[]; extern int debug; extern void bti_parse_configfile(struct session *session); #define dbg(format, arg...) \ do { \ if (debug) \ fprintf(stdout, "bti: %s: " format , __func__ , \ ## arg); \ } while (0) #endif bti-034/bti.xml000066400000000000000000000367061226553574500134720ustar00rootroot00000000000000
bti bti May 2008 bti bti 1 bti send a tweet to twitter.com from the command line bti DESCRIPTION bti sends a tweet message to twitter.com. OPTIONS Specify the twitter.com account name. Specify the password of your twitter.com account. Specify the action which you want to perform. Valid options are "update" to send a message, "friends" to see your friends timeline, "public" to track public timeline, "replies" to see replies to your messages, "user" to see a specific user's timeline and "direct" to send a direct message to a friend. Default is "update". Specify the user whose messages you want to see when the action is "user", and the reciever of the direct message when the action is "direct" (the sender must be following the receiver). Specify the host which you want to send your message to. Valid options are "twitter" to send to twitter.com. If no host is specified, the default is to send to twitter.com. Specify a http proxy value. This is not a required option, and only needed by systems that are behind a http proxy. If --proxy is not specified but the environment variable 'http_proxy' is set the latter will be used. Specify a logfile for bti to write status messages to. LOGFILE is in relation to the user's home directory, not an absolute path to a file. Specify a config file for bti to read from. By default, bti looks in the ~/.bti file for config values. This default location can be overridden by setting a specific file with this option. Status ID of a single post to which you want to create a threaded reply to. For twitter, this is ignored unless the message starts with the @name of the owner of the post with the status ID. For status.net, this can link any two messages into context with each other. Status.net will also link a message that contains an @name without this without regard to context. Status ID of a single post which you want to retweet. Scans the tweet text for valid URL patterns and passes each through the supplied bti-shrink-urls script. The script will pass the URL to a web service that shrinks the URLs, making it more suitable for micro-blogging. The following URL shrinking services are available: http://2tu.us/ (default) and http://bit.ly / http://j.mp See the documentation for bti-shrink-urls for the configuration options. Print a whole bunch of debugging messages to stdout. When the action is to retrieve updates, it usually retrieves only one page. If this option is used, the page number can be specified. Performs all steps that would normally be done for a given action, but will not connect to the service to post or retrieve data. Verbose mode. Print status IDs and timestamps. Add the working directory and a '$' in the tweet message to help specify it is coming from a command line. Don't put the working directory and the '$' in the tweet message. This option implies . Do not report back any errors that might have happened when sending the message, and send it in the background, returning immediately, allowing the user to continue on. Print version number. Print help text. DESCRIPTION bti provides an easy way to send tweet messages direct from the command line or any script. It reads the message on standard input and uses the account and password settings either from the command line options, or from a config file, to send the message out. Its primary focus is to allow you to log everything that you type into a bash shell, in a crazy, "this is what I'm doing right now!" type of way, letting the world follow along with you constant moving between directories and refreshing your email queue to see if there's anything interesting going on. To hook bti up to your bash shell, export the following variable: PROMPT_COMMAND='history 1 | sed -e "s/^\s*[0-9]*\s*//" | bti --bash' This example assumes that you have the ~/.bti set up with your account and password information already in it, otherwise you can specify them as an option. CONFIGURATION The account and password can be stored in a configuration file in the users home directory in a file named .bti. The structure of this file is as follows: The twitter.com account name you wish to use to send this message with. The twitter.com password for the account you wish to use to send this message with. Specify the action which you want to perform. Valid options are "update" to send a message, "friends" to see your friends timeline, "public" to track public timeline, "replies" to see replies to your messages and "user" to see a specific user's timeline. Specify the user you want to see his/her messages while the action is "user". The host you want to use to send the message to. Valid options is "twitter" or "custom" to specify your own server. The http proxy needed to send data out to the Internet. The logfile name for bti to write what happened to. This file is relative to the user's home directory. If this file is not specified here or on the command line, no logging will be written to the disk. The status ID to which all notices will be linked to. There is no sane reason for a need to have this set in a config file. One such reason is to have all your messages as children to a particular status. Setting this variable to 'true' or 'yes' will enable the URL shrinking feature. This is equivalent to using the --shrink-urls option. Setting this variable to 'true' or 'yes' will enable the verbose mode. There is an example config file called bti.example in the source tree that shows the structure of the file if you need an example to work off of. Configuration options have the following priority: command line option config file option environment variables For example, command line options always override any config file option, or any environment variables. Unless a config file is specified by the command line. At that point, the new config file is read, and any previous options set by a command line option, would be overridden. AUTHOR Written by Greg Kroah-Hartman <greg@kroah.com> and Amir Mohammad Saied <amirsaied@gmail.com>.
bti-034/config.c000066400000000000000000000214641226553574500135760ustar00rootroot00000000000000/* * Copyright (C) 2008-2011 Greg Kroah-Hartman * Copyright (C) 2009 Bart Trojanowski * Copyright (C) 2009-2010 Amir Mohammad Saied * * 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 version 2 of the License. * * 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. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bti.h" typedef int (*config_function_callback)(struct session *session, char *value); struct config_function { const char *key; config_function_callback callback; }; /* * get_key function * * Read a line from the config file and assign it a key and a value. * * This logic taken almost identically from taken from udev's rule file parsing * logic in the file udev-rules.c, written by Kay Sievers and licensed under * the GPLv2+. I hate writing parsers, so it makes sense to borrow working * logic from those smarter than I... */ static int get_key(struct session *session, char *line, char **key, char **value) { char *linepos; char *temp; char terminator; linepos = line; if (linepos == NULL || linepos[0] == '\0') return -1; /* skip whitespace */ while (isspace(linepos[0]) || linepos[0] == ',') linepos++; if (linepos[0] == '\0') return -1; *key = linepos; for (;;) { linepos++; if (linepos[0] == '\0') return -1; if (isspace(linepos[0])) break; if (linepos[0] == '=') break; } /* remember the end of the key */ temp = linepos; /* skip whitespace after key */ while (isspace(linepos[0])) linepos++; if (linepos[0] == '\0') return -1; /* make sure this is a = operation */ /* * udev likes to check for += and == and lots of other complex * assignments that we don't care about. */ if (linepos[0] == '=') linepos++; else return -1; /* terminate key */ temp[0] = '\0'; /* skip whitespace after opearator */ while (isspace(linepos[0])) linepos++; if (linepos[0] == '\0') return -1; /* * if the value is quoted, then terminate on a ", otherwise space is * the terminator. * */ if (linepos[0] == '"') { terminator = '"'; linepos++; } else terminator = ' '; /* get the value */ *value = linepos; /* terminate */ temp = strchr(linepos, terminator); if (temp) { temp[0] = '\0'; temp++; } else { /* * perhaps we just hit the end of the line, so there would not * be a terminator, so just use the whole rest of the string as * the value. */ } /* printf("%s = %s\n", *key, *value); */ return 0; } static int session_string(char **field, char *value) { char *string; string = strdup(value); if (string) { if (*field) free(*field); *field = string; return 0; } return -1; } static int session_bool(int *field, char *value) { if ((strncasecmp(value, "true", 4) == 0) || strncasecmp(value, "yes", 3) == 0) *field = 1; return 0; } static int account_callback(struct session *session, char *value) { return session_string(&session->account, value); } static int password_callback(struct session *session, char *value) { return session_string(&session->password, value); } static int proxy_callback(struct session *session, char *value) { return session_string(&session->proxy, value); } static int user_callback(struct session *session, char *value) { return session_string(&session->user, value); } static int consumer_key_callback(struct session *session, char *value) { return session_string(&session->consumer_key, value); } static int consumer_secret_callback(struct session *session, char *value) { return session_string(&session->consumer_secret, value); } static int access_token_key_callback(struct session *session, char *value) { return session_string(&session->access_token_key, value); } static int access_token_secret_callback(struct session *session, char *value) { return session_string(&session->access_token_secret, value); } static int logfile_callback(struct session *session, char *value) { return session_string(&session->logfile, value); } static int replyto_callback(struct session *session, char *value) { return session_string(&session->replyto, value); } static int retweet_callback(struct session *session, char *value) { return session_string(&session->retweet, value); } static int host_callback(struct session *session, char *value) { if (strcasecmp(value, "twitter") == 0) { session->host = HOST_TWITTER; session->hosturl = strdup(twitter_host); session->hostname = strdup(twitter_name); } else { session->host = HOST_CUSTOM; session->hosturl = strdup(value); session->hostname = strdup(value); } return 0; } static int action_callback(struct session *session, char *value) { if (strcasecmp(value, "update") == 0) session->action = ACTION_UPDATE; else if (strcasecmp(value, "friends") == 0) session->action = ACTION_FRIENDS; else if (strcasecmp(value, "user") == 0) session->action = ACTION_USER; else if (strcasecmp(value, "replies") == 0) session->action = ACTION_REPLIES; else if (strcasecmp(value, "public") == 0) session->action = ACTION_PUBLIC; else if (strcasecmp(value, "group") == 0) session->action = ACTION_GROUP; else session->action = ACTION_UNKNOWN; return 0; } static int verbose_callback(struct session *session, char *value) { return session_bool(&session->verbose, value); } static int shrink_urls_callback(struct session *session, char *value) { return session_bool(&session->shrink_urls, value); } /* * List of all of the config file options. * * To add a new option, just add a string for the key name, and the callback * function that will be called with the value read from the config file. * * Make sure the table is NULL terminated, otherwise bad things will happen. */ static struct config_function config_table[] = { { "account", account_callback }, { "password", password_callback }, { "proxy", proxy_callback }, { "user", user_callback }, { "consumer_key", consumer_key_callback }, { "consumer_secret", consumer_secret_callback }, { "access_token_key", access_token_key_callback }, { "access_token_secret", access_token_secret_callback }, { "logfile", logfile_callback }, { "replyto", replyto_callback }, { "retweet", retweet_callback }, { "host", host_callback }, { "action", action_callback }, { "verbose", verbose_callback }, { "shrink-urls", shrink_urls_callback }, { NULL, NULL } }; static void process_line(struct session *session, char *key, char *value) { struct config_function *item; int result; if (key == NULL || value == NULL) return; item = &config_table[0]; for (;;) { if (item->key == NULL || item->callback == NULL) break; if (strncasecmp(item->key, key, strlen(item->key)) == 0) { /* * printf("calling %p, for key = '%s' and value = * '%s'\n", * item->callback, key, value); */ result = item->callback(session, value); if (!result) return; } item++; } } void bti_parse_configfile(struct session *session) { FILE *config_file; char *line = NULL; char *key = NULL; char *value = NULL; char *hashmarker; size_t len = 0; ssize_t n; char *c; config_file = fopen(session->configfile, "r"); /* No error if file does not exist or is unreadable. */ if (config_file == NULL) return; do { n = getline(&line, &len, config_file); if (n < 0) break; if (line[n - 1] == '\n') line[n - 1] = '\0'; /* * '#' is comment markers, like bash style but it is a valid * character in some fields, so only treat it as a comment * marker if it occurs at the beginning of the line, or after * whitespace */ hashmarker = strchr(line, '#'); if (line == hashmarker) line[0] = '\0'; else { while (hashmarker != NULL) { --hashmarker; if (isblank(hashmarker[0])) { hashmarker[0] = '\0'; break; } else { /* * false positive; '#' occured * within a string */ hashmarker = strchr(hashmarker+2, '#'); } } } c = line; while (isspace(*c)) c++; /* Ignore blank lines. */ if (c[0] == '\0') continue; /* parse the line into a key and value pair */ get_key(session, c, &key, &value); process_line(session, key, value); } while (!feof(config_file)); /* Free buffer and close file. */ free(line); fclose(config_file); } bti-034/configure.ac000066400000000000000000000014411226553574500144440ustar00rootroot00000000000000AC_INIT([bti], [034], [greg@kroah.com]) AC_PREREQ(2.60) AM_INIT_AUTOMAKE(bti, 034) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AC_PROG_CC AC_PROG_INSTALL AC_CONFIG_MACRO_DIR([m4]) AC_PATH_PROG([XSLTPROC], [xsltproc]) PKG_PROG_PKG_CONFIG() PKG_CHECK_MODULES(LIBOAUTH, oauth) PKG_CHECK_MODULES(LIBPCRE, libpcre) PKG_CHECK_MODULES([LIBCURL], [libcurl]) PKG_CHECK_MODULES([XML], [libxml-2.0]) PKG_CHECK_MODULES([JSON], [json-c]) AC_SEARCH_LIBS([dlopen], [dl]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT echo " bti $VERSION ======= prefix: ${prefix} datarootdir: ${datarootdir} mandir: ${mandir} compiler: ${CC} cflags: ${CFLAGS} ldflags: ${LDFLAGS} xsltproc: ${XSLTPROC} " bti-034/oath.keys000066400000000000000000000004161226553574500140070ustar00rootroot00000000000000# Consumer key cZy8DdioswAfu3LJYg6E2w # Consumer secret fnIGGU0T12mMWKjmThUdSeKN32NLWfmnwapwubVQ # Request token URL http://twitter.com/oauth/request_token # Access token URL http://twitter.com/oauth/access_token # Authorize URL http://twitter.com/oauth/authorize bti-034/scripts/000077500000000000000000000000001226553574500136455ustar00rootroot00000000000000bti-034/scripts/checkpatch.pl000077500000000000000000002270321226553574500163100ustar00rootroot00000000000000#!/usr/bin/perl -w # (c) 2001, Dave Jones. (the file handling bit) # (c) 2005, Joel Schopp (the ugly bit) # (c) 2007,2008, Andy Whitcroft (new conditions, test suite) # (c) 2008,2009, Andy Whitcroft # Licensed under the terms of the GNU GPL License version 2 use strict; my $P = $0; $P =~ s@.*/@@g; my $V = '0.30'; use Getopt::Long qw(:config no_auto_abbrev); my $quiet = 0; my $tree = 1; my $chk_signoff = 1; my $chk_patch = 1; my $tst_only; my $emacs = 0; my $terse = 0; my $file = 0; my $check = 0; my $summary = 1; my $mailback = 0; my $summary_file = 0; my $root; my %debug; my $help = 0; sub help { my ($exitcode) = @_; print << "EOM"; Usage: $P [OPTION]... [FILE]... Version: $V Options: -q, --quiet quiet --no-tree run without a kernel tree --no-signoff do not check for 'Signed-off-by' line --patch treat FILE as patchfile (default) --emacs emacs compile window format --terse one line per report -f, --file treat FILE as regular source file --subjective, --strict enable more subjective tests --root=PATH PATH to the kernel tree root --no-summary suppress the per-file summary --mailback only produce a report in case of warnings/errors --summary-file include the filename in summary --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of 'values', 'possible', 'type', and 'attr' (default is all off) --test-only=WORD report only warnings/errors containing WORD literally -h, --help, --version display this help and exit When FILE is - read standard input. EOM exit($exitcode); } GetOptions( 'q|quiet+' => \$quiet, 'tree!' => \$tree, 'signoff!' => \$chk_signoff, 'patch!' => \$chk_patch, 'emacs!' => \$emacs, 'terse!' => \$terse, 'f|file!' => \$file, 'subjective!' => \$check, 'strict!' => \$check, 'root=s' => \$root, 'summary!' => \$summary, 'mailback!' => \$mailback, 'summary-file!' => \$summary_file, 'debug=s' => \%debug, 'test-only=s' => \$tst_only, 'h|help' => \$help, 'version' => \$help ) or help(1); help(0) if ($help); my $exit = 0; if ($#ARGV < 0) { print "$P: no input files\n"; exit(1); } my $dbg_values = 0; my $dbg_possible = 0; my $dbg_type = 0; my $dbg_attr = 0; for my $key (keys %debug) { ## no critic eval "\${dbg_$key} = '$debug{$key}';"; die "$@" if ($@); } if ($terse) { $emacs = 1; $quiet++; } if ($tree) { if (defined $root) { if (!top_of_kernel_tree($root)) { die "$P: $root: --root does not point at a valid tree\n"; } } else { if (top_of_kernel_tree('.')) { $root = '.'; } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && top_of_kernel_tree($1)) { $root = $1; } } if (!defined $root) { print "Must be run from the top-level dir. of a kernel tree\n"; exit(2); } } my $emitted_corrupt = 0; our $Ident = qr{ [A-Za-z_][A-Za-z\d_]* (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* }x; our $Storage = qr{extern|static|asmlinkage}; our $Sparse = qr{ __user| __kernel| __force| __iomem| __must_check| __init_refok| __kprobes| __ref }x; # Notes to $Attribute: # We need \b after 'init' otherwise 'initconst' will cause a false positive in a check our $Attribute = qr{ const| __read_mostly| __kprobes| __(?:mem|cpu|dev|)(?:initdata|initconst|init\b)| ____cacheline_aligned| ____cacheline_aligned_in_smp| ____cacheline_internodealigned_in_smp| __weak }x; our $Modifier; our $Inline = qr{inline|__always_inline|noinline}; our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; our $Lval = qr{$Ident(?:$Member)*}; our $Constant = qr{(?:[0-9]+|0x[0-9a-fA-F]+)[UL]*}; our $Assignment = qr{(?:\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=)}; our $Compare = qr{<=|>=|==|!=|<|>}; our $Operators = qr{ <=|>=|==|!=| =>|->|<<|>>|<|>|!|~| &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|% }x; our $NonptrType; our $Type; our $Declare; our $UTF8 = qr { [\x09\x0A\x0D\x20-\x7E] # ASCII | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 }x; our $typeTypedefs = qr{(?x: (?:__)?(?:u|s|be|le)(?:8|16|32|64)| atomic_t )}; our $logFunctions = qr{(?x: printk| pr_(debug|dbg|vdbg|devel|info|warning|err|notice|alert|crit|emerg|cont)| (dev|netdev|netif)_(printk|dbg|vdbg|info|warn|err|notice|alert|crit|emerg|WARN)| WARN| panic )}; our @typeList = ( qr{void}, qr{(?:unsigned\s+)?char}, qr{(?:unsigned\s+)?short}, qr{(?:unsigned\s+)?int}, qr{(?:unsigned\s+)?long}, qr{(?:unsigned\s+)?long\s+int}, qr{(?:unsigned\s+)?long\s+long}, qr{(?:unsigned\s+)?long\s+long\s+int}, qr{unsigned}, qr{float}, qr{double}, qr{bool}, qr{struct\s+$Ident}, qr{union\s+$Ident}, qr{enum\s+$Ident}, qr{${Ident}_t}, qr{${Ident}_handler}, qr{${Ident}_handler_fn}, ); our @modifierList = ( qr{fastcall}, ); our $allowed_asm_includes = qr{(?x: irq| memory )}; # memory.h: ARM has a custom one sub build_types { my $mods = "(?x: \n" . join("|\n ", @modifierList) . "\n)"; my $all = "(?x: \n" . join("|\n ", @typeList) . "\n)"; $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; $NonptrType = qr{ (?:$Modifier\s+|const\s+)* (?: (?:typeof|__typeof__)\s*\(\s*\**\s*$Ident\s*\)| (?:$typeTypedefs\b)| (?:${all}\b) ) (?:\s+$Modifier|\s+const)* }x; $Type = qr{ $NonptrType (?:[\s\*]+\s*const|[\s\*]+|(?:\s*\[\s*\])+)? (?:\s+$Inline|\s+$Modifier)* }x; $Declare = qr{(?:$Storage\s+)?$Type}; } build_types(); $chk_signoff = 0 if ($file); my @dep_includes = (); my @dep_functions = (); my $removal = "Documentation/feature-removal-schedule.txt"; if ($tree && -f "$root/$removal") { open(my $REMOVE, '<', "$root/$removal") || die "$P: $removal: open failed - $!\n"; while (<$REMOVE>) { if (/^Check:\s+(.*\S)/) { for my $entry (split(/[, ]+/, $1)) { if ($entry =~ m@include/(.*)@) { push(@dep_includes, $1); } elsif ($entry !~ m@/@) { push(@dep_functions, $entry); } } } } close($REMOVE); } my @rawlines = (); my @lines = (); my $vname; for my $filename (@ARGV) { my $FILE; if ($file) { open($FILE, '-|', "diff -u /dev/null $filename") || die "$P: $filename: diff failed - $!\n"; } elsif ($filename eq '-') { open($FILE, '<&STDIN'); } else { open($FILE, '<', "$filename") || die "$P: $filename: open failed - $!\n"; } if ($filename eq '-') { $vname = 'Your patch'; } else { $vname = $filename; } while (<$FILE>) { chomp; push(@rawlines, $_); } close($FILE); if (!process($filename)) { $exit = 1; } @rawlines = (); @lines = (); } exit($exit); sub top_of_kernel_tree { my ($root) = @_; my @tree_check = ( "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", "README", "Documentation", "arch", "include", "drivers", "fs", "init", "ipc", "kernel", "lib", "scripts", ); foreach my $check (@tree_check) { if (! -e $root . '/' . $check) { return 0; } } return 1; } sub expand_tabs { my ($str) = @_; my $res = ''; my $n = 0; for my $c (split(//, $str)) { if ($c eq "\t") { $res .= ' '; $n++; for (; ($n % 8) != 0; $n++) { $res .= ' '; } next; } $res .= $c; $n++; } return $res; } sub copy_spacing { (my $res = shift) =~ tr/\t/ /c; return $res; } sub line_stats { my ($line) = @_; # Drop the diff line leader and expand tabs $line =~ s/^.//; $line = expand_tabs($line); # Pick the indent from the front of the line. my ($white) = ($line =~ /^(\s*)/); return (length($line), length($white)); } my $sanitise_quote = ''; sub sanitise_line_reset { my ($in_comment) = @_; if ($in_comment) { $sanitise_quote = '*/'; } else { $sanitise_quote = ''; } } sub sanitise_line { my ($line) = @_; my $res = ''; my $l = ''; my $qlen = 0; my $off = 0; my $c; # Always copy over the diff marker. $res = substr($line, 0, 1); for ($off = 1; $off < length($line); $off++) { $c = substr($line, $off, 1); # Comments we are wacking completly including the begin # and end, all to $;. if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { $sanitise_quote = '*/'; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { $sanitise_quote = ''; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { $sanitise_quote = '//'; substr($res, $off, 2, $sanitise_quote); $off++; next; } # A \ in a string means ignore the next character. if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && $c eq "\\") { substr($res, $off, 2, 'XX'); $off++; next; } # Regular quotes. if ($c eq "'" || $c eq '"') { if ($sanitise_quote eq '') { $sanitise_quote = $c; substr($res, $off, 1, $c); next; } elsif ($sanitise_quote eq $c) { $sanitise_quote = ''; } } #print "c<$c> SQ<$sanitise_quote>\n"; if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { substr($res, $off, 1, 'X'); } else { substr($res, $off, 1, $c); } } if ($sanitise_quote eq '//') { $sanitise_quote = ''; } # The pathname on a #include may be surrounded by '<' and '>'. if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { my $clean = 'X' x length($1); $res =~ s@\<.*\>@<$clean>@; # The whole of a #error is a string. } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { my $clean = 'X' x length($1); $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; } return $res; } sub ctx_statement_block { my ($linenr, $remain, $off) = @_; my $line = $linenr - 1; my $blk = ''; my $soff = $off; my $coff = $off - 1; my $coff_set = 0; my $loff = 0; my $type = ''; my $level = 0; my @stack = (); my $p; my $c; my $len = 0; my $remainder; while (1) { @stack = (['', 0]) if ($#stack == -1); #warn "CSB: blk<$blk> remain<$remain>\n"; # If we are about to drop off the end, pull in more # context. if ($off >= $len) { for (; $remain > 0; $line++) { last if (!defined $lines[$line]); next if ($lines[$line] =~ /^-/); $remain--; $loff = $len; $blk .= $lines[$line] . "\n"; $len = length($blk); $line++; last; } # Bail if there is no further context. #warn "CSB: blk<$blk> off<$off> len<$len>\n"; if ($off >= $len) { last; } } $p = $c; $c = substr($blk, $off, 1); $remainder = substr($blk, $off); #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; # Handle nested #if/#else. if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, [ $type, $level ]); } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { ($type, $level) = @{$stack[$#stack - 1]}; } elsif ($remainder =~ /^#\s*endif\b/) { ($type, $level) = @{pop(@stack)}; } # Statement ends at the ';' or a close '}' at the # outermost level. if ($level == 0 && $c eq ';') { last; } # An else is really a conditional as long as its not else if if ($level == 0 && $coff_set == 0 && (!defined($p) || $p =~ /(?:\s|\}|\+)/) && $remainder =~ /^(else)(?:\s|{)/ && $remainder !~ /^else\s+if\b/) { $coff = $off + length($1) - 1; $coff_set = 1; #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; } if (($type eq '' || $type eq '(') && $c eq '(') { $level++; $type = '('; } if ($type eq '(' && $c eq ')') { $level--; $type = ($level != 0)? '(' : ''; if ($level == 0 && $coff < $soff) { $coff = $off; $coff_set = 1; #warn "CSB: mark coff<$coff>\n"; } } if (($type eq '' || $type eq '{') && $c eq '{') { $level++; $type = '{'; } if ($type eq '{' && $c eq '}') { $level--; $type = ($level != 0)? '{' : ''; if ($level == 0) { if (substr($blk, $off + 1, 1) eq ';') { $off++; } last; } } $off++; } # We are truly at the end, so shuffle to the next line. if ($off == $len) { $loff = $len + 1; $line++; $remain--; } my $statement = substr($blk, $soff, $off - $soff + 1); my $condition = substr($blk, $soff, $coff - $soff + 1); #warn "STATEMENT<$statement>\n"; #warn "CONDITION<$condition>\n"; #print "coff<$coff> soff<$off> loff<$loff>\n"; return ($statement, $condition, $line, $remain + 1, $off - $loff + 1, $level); } sub statement_lines { my ($stmt) = @_; # Strip the diff line prefixes and rip blank lines at start and end. $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_rawlines { my ($stmt) = @_; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_block_size { my ($stmt) = @_; $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*{//; $stmt =~ s/}\s*$//; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); my @stmt_statements = ($stmt =~ /;/g); my $stmt_lines = $#stmt_lines + 2; my $stmt_statements = $#stmt_statements + 1; if ($stmt_lines > $stmt_statements) { return $stmt_lines; } else { return $stmt_statements; } } sub ctx_statement_full { my ($linenr, $remain, $off) = @_; my ($statement, $condition, $level); my (@chunks); # Grab the first conditional/block pair. ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "F: c<$condition> s<$statement> remain<$remain>\n"; push(@chunks, [ $condition, $statement ]); if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { return ($level, $linenr, @chunks); } # Pull in the following conditional/block pairs and see if they # could continue the statement. for (;;) { ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "C: c<$condition> s<$statement> remain<$remain>\n"; last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); #print "C: push\n"; push(@chunks, [ $condition, $statement ]); } return ($level, $linenr, @chunks); } sub ctx_block_get { my ($linenr, $remain, $outer, $open, $close, $off) = @_; my $line; my $start = $linenr - 1; my $blk = ''; my @o; my @c; my @res = (); my $level = 0; my @stack = ($level); for ($line = $start; $remain > 0; $line++) { next if ($rawlines[$line] =~ /^-/); $remain--; $blk .= $rawlines[$line]; # Handle nested #if/#else. if ($rawlines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, $level); } elsif ($rawlines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { $level = $stack[$#stack - 1]; } elsif ($rawlines[$line] =~ /^.\s*#\s*endif\b/) { $level = pop(@stack); } foreach my $c (split(//, $rawlines[$line])) { ##print "C<$c>L<$level><$open$close>O<$off>\n"; if ($off > 0) { $off--; next; } if ($c eq $close && $level > 0) { $level--; last if ($level == 0); } elsif ($c eq $open) { $level++; } } if (!$outer || $level <= 1) { push(@res, $rawlines[$line]); } last if ($level == 0); } return ($level, @res); } sub ctx_block_outer { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); return @r; } sub ctx_block { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); return @r; } sub ctx_statement { my ($linenr, $remain, $off) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); return @r; } sub ctx_block_level { my ($linenr, $remain) = @_; return ctx_block_get($linenr, $remain, 0, '{', '}', 0); } sub ctx_statement_level { my ($linenr, $remain, $off) = @_; return ctx_block_get($linenr, $remain, 0, '(', ')', $off); } sub ctx_locate_comment { my ($first_line, $end_line) = @_; # Catch a comment on the end of the line itself. my ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); return $current_comment if (defined $current_comment); # Look through the context and try and figure out if there is a # comment. my $in_comment = 0; $current_comment = ''; for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { my $line = $rawlines[$linenr - 1]; #warn " $line\n"; if ($linenr == $first_line and $line =~ m@^.\s*\*@) { $in_comment = 1; } if ($line =~ m@/\*@) { $in_comment = 1; } if (!$in_comment && $current_comment ne '') { $current_comment = ''; } $current_comment .= $line . "\n" if ($in_comment); if ($line =~ m@\*/@) { $in_comment = 0; } } chomp($current_comment); return($current_comment); } sub ctx_has_comment { my ($first_line, $end_line) = @_; my $cmt = ctx_locate_comment($first_line, $end_line); ##print "LINE: $rawlines[$end_line - 1 ]\n"; ##print "CMMT: $cmt\n"; return ($cmt ne ''); } sub raw_line { my ($linenr, $cnt) = @_; my $offset = $linenr - 1; $cnt++; my $line; while ($cnt) { $line = $rawlines[$offset++]; next if (defined($line) && $line =~ /^-/); $cnt--; } return $line; } sub cat_vet { my ($vet) = @_; my ($res, $coded); $res = ''; while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { $res .= $1; if ($2 ne '') { $coded = sprintf("^%c", unpack('C', $2) + 64); $res .= $coded; } } $res =~ s/$/\$/; return $res; } my $av_preprocessor = 0; my $av_pending; my @av_paren_type; my $av_pend_colon; sub annotate_reset { $av_preprocessor = 0; $av_pending = '_'; @av_paren_type = ('E'); $av_pend_colon = 'O'; } sub annotate_values { my ($stream, $type) = @_; my $res; my $var = '_' x length($stream); my $cur = $stream; print "$stream\n" if ($dbg_values > 1); while (length($cur)) { @av_paren_type = ('E') if ($#av_paren_type < 0); print " <" . join('', @av_paren_type) . "> <$type> <$av_pending>" if ($dbg_values > 1); if ($cur =~ /^(\s+)/o) { print "WS($1)\n" if ($dbg_values > 1); if ($1 =~ /\n/ && $av_preprocessor) { $type = pop(@av_paren_type); $av_preprocessor = 0; } } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\()/) { print "DECLARE($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^($Modifier)\s*/) { print "MODIFIER($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { print "DEFINE($1,$2)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); if ($2 ne '') { $av_pending = 'N'; } $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { print "UNDEF($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { print "PRE_START($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { print "PRE_RESTART($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $av_paren_type[$#av_paren_type]); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:endif))/o) { print "PRE_END($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; # Assume all arms of the conditional end as this # one does, and continue as if the #endif was not here. pop(@av_paren_type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\\\n)/o) { print "PRECONT($1)\n" if ($dbg_values > 1); } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { print "ATTR($1)\n" if ($dbg_values > 1); $av_pending = $type; $type = 'N'; } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { print "SIZEOF($1)\n" if ($dbg_values > 1); if (defined $2) { $av_pending = 'V'; } $type = 'N'; } elsif ($cur =~ /^(if|while|for)\b/o) { print "COND($1)\n" if ($dbg_values > 1); $av_pending = 'E'; $type = 'N'; } elsif ($cur =~/^(case)/o) { print "CASE($1)\n" if ($dbg_values > 1); $av_pend_colon = 'C'; $type = 'N'; } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { print "KEYWORD($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(\()/o) { print "PAREN('$1')\n" if ($dbg_values > 1); push(@av_paren_type, $av_pending); $av_pending = '_'; $type = 'N'; } elsif ($cur =~ /^(\))/o) { my $new_type = pop(@av_paren_type); if ($new_type ne '_') { $type = $new_type; print "PAREN('$1') -> $type\n" if ($dbg_values > 1); } else { print "PAREN('$1')\n" if ($dbg_values > 1); } } elsif ($cur =~ /^($Ident)\s*\(/o) { print "FUNC($1)\n" if ($dbg_values > 1); $type = 'V'; $av_pending = 'V'; } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { if (defined $2 && $type eq 'C' || $type eq 'T') { $av_pend_colon = 'B'; } elsif ($type eq 'E') { $av_pend_colon = 'L'; } print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Ident|$Constant)/o) { print "IDENT($1)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Assignment)/o) { print "ASSIGN($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~/^(;|{|})/) { print "END($1)\n" if ($dbg_values > 1); $type = 'E'; $av_pend_colon = 'O'; } elsif ($cur =~/^(,)/) { print "COMMA($1)\n" if ($dbg_values > 1); $type = 'C'; } elsif ($cur =~ /^(\?)/o) { print "QUESTION($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(:)/o) { print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); substr($var, length($res), 1, $av_pend_colon); if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { $type = 'E'; } else { $type = 'N'; } $av_pend_colon = 'O'; } elsif ($cur =~ /^(\[)/o) { print "CLOSE($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { my $variant; print "OPV($1)\n" if ($dbg_values > 1); if ($type eq 'V') { $variant = 'B'; } else { $variant = 'U'; } substr($var, length($res), 1, $variant); $type = 'N'; } elsif ($cur =~ /^($Operators)/o) { print "OP($1)\n" if ($dbg_values > 1); if ($1 ne '++' && $1 ne '--') { $type = 'N'; } } elsif ($cur =~ /(^.)/o) { print "C($1)\n" if ($dbg_values > 1); } if (defined $1) { $cur = substr($cur, length($1)); $res .= $type x length($1); } } return ($res, $var); } sub possible { my ($possible, $line) = @_; my $notPermitted = qr{(?: ^(?: $Modifier| $Storage| $Type| DEFINE_\S+ )$| ^(?: goto| return| case| else| asm|__asm__| do )(?:\s|$)| ^(?:typedef|struct|enum)\b )}x; warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); if ($possible !~ $notPermitted) { # Check for modifiers. $possible =~ s/\s*$Storage\s*//g; $possible =~ s/\s*$Sparse\s*//g; if ($possible =~ /^\s*$/) { } elsif ($possible =~ /\s/) { $possible =~ s/\s*$Type\s*//g; for my $modifier (split(' ', $possible)) { if ($modifier !~ $notPermitted) { warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); push(@modifierList, $modifier); } } } else { warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); push(@typeList, $possible); } build_types(); } else { warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); } } my $prefix = ''; sub report { if (defined $tst_only && $_[0] !~ /\Q$tst_only\E/) { return 0; } my $line = $prefix . $_[0]; $line = (split('\n', $line))[0] . "\n" if ($terse); push(our @report, $line); return 1; } sub report_dump { our @report; } sub ERROR { if (report("ERROR: $_[0]\n")) { our $clean = 0; our $cnt_error++; } } sub WARN { if (report("WARNING: $_[0]\n")) { our $clean = 0; our $cnt_warn++; } } sub CHK { if ($check && report("CHECK: $_[0]\n")) { our $clean = 0; our $cnt_chk++; } } sub check_absolute_file { my ($absolute, $herecurr) = @_; my $file = $absolute; ##print "absolute<$absolute>\n"; # See if any suffix of this path is a path within the tree. while ($file =~ s@^[^/]*/@@) { if (-f "$root/$file") { ##print "file<$file>\n"; last; } } if (! -f _) { return 0; } # It is, so see if the prefix is acceptable. my $prefix = $absolute; substr($prefix, -length($file)) = ''; ##print "prefix<$prefix>\n"; if ($prefix ne ".../") { WARN("use relative pathname instead of absolute in changelog text\n" . $herecurr); } } sub process { my $filename = shift; my $linenr=0; my $prevline=""; my $prevrawline=""; my $stashline=""; my $stashrawline=""; my $length; my $indent; my $previndent=0; my $stashindent=0; our $clean = 1; my $signoff = 0; my $is_patch = 0; our @report = (); our $cnt_lines = 0; our $cnt_error = 0; our $cnt_warn = 0; our $cnt_chk = 0; # Trace the real file/line as we go. my $realfile = ''; my $realline = 0; my $realcnt = 0; my $here = ''; my $in_comment = 0; my $comment_edge = 0; my $first_line = 0; my $p1_prefix = ''; my $prev_values = 'E'; # suppression flags my %suppress_ifbraces; my %suppress_whiletrailers; my %suppress_export; # Pre-scan the patch sanitizing the lines. # Pre-scan the patch looking for any __setup documentation. # my @setup_docs = (); my $setup_docs = 0; sanitise_line_reset(); my $line; foreach my $rawline (@rawlines) { $linenr++; $line = $rawline; if ($rawline=~/^\+\+\+\s+(\S+)/) { $setup_docs = 0; if ($1 =~ m@Documentation/kernel-parameters.txt$@) { $setup_docs = 1; } #next; } if ($rawline=~/^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } $in_comment = 0; # Guestimate if this is a continuing comment. Run # the context looking for a comment "edge". If this # edge is a close comment then we must be in a comment # at context start. my $edge; my $cnt = $realcnt; for (my $ln = $linenr + 1; $cnt > 0; $ln++) { next if (defined $rawlines[$ln - 1] && $rawlines[$ln - 1] =~ /^-/); $cnt--; #print "RAW<$rawlines[$ln - 1]>\n"; last if (!defined $rawlines[$ln - 1]); if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { ($edge) = $1; last; } } if (defined $edge && $edge eq '*/') { $in_comment = 1; } # Guestimate if this is a continuing comment. If this # is the start of a diff block and this line starts # ' *' then it is very likely a comment. if (!defined $edge && $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) { $in_comment = 1; } ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; sanitise_line_reset($in_comment); } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { # Standardise the strings and chars within the input to # simplify matching -- only bother with positive lines. $line = sanitise_line($rawline); } push(@lines, $line); if ($realcnt > 1) { $realcnt-- if ($line =~ /^(?:\+| |$)/); } else { $realcnt = 0; } #print "==>$rawline\n"; #print "-->$line\n"; if ($setup_docs && $line =~ /^\+/) { push(@setup_docs, $line); } } $prefix = ''; $realcnt = 0; $linenr = 0; foreach my $line (@lines) { $linenr++; my $rawline = $rawlines[$linenr - 1]; #extract the line range in the file after the patch is applied if ($line=~/^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { $is_patch = 1; $first_line = $linenr + 1; $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } annotate_reset(); $prev_values = 'E'; %suppress_ifbraces = (); %suppress_whiletrailers = (); %suppress_export = (); next; # track the line number as we move through the hunk, note that # new versions of GNU diff omit the leading space on completely # blank context lines so we need to count that too. } elsif ($line =~ /^( |\+|$)/) { $realline++; $realcnt-- if ($realcnt != 0); # Measure the line length and indent. ($length, $indent) = line_stats($rawline); # Track the previous line. ($prevline, $stashline) = ($stashline, $line); ($previndent, $stashindent) = ($stashindent, $indent); ($prevrawline, $stashrawline) = ($stashrawline, $rawline); #warn "line<$line>\n"; } elsif ($realcnt == 1) { $realcnt--; } my $hunk_line = ($realcnt != 0); #make up the handle for any error we report on this line $prefix = "$filename:$realline: " if ($emacs && $file); $prefix = "$filename:$linenr: " if ($emacs && !$file); $here = "#$linenr: " if (!$file); $here = "#$realline: " if ($file); # extract the filename as it passes if ($line=~/^\+\+\+\s+(\S+)/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@; $p1_prefix = $1; if (!$file && $tree && $p1_prefix ne '' && -e "$root/$p1_prefix") { WARN("patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); } if ($realfile =~ m@^include/asm/@) { ERROR("do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); } next; } $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); my $hereline = "$here\n$rawline\n"; my $herecurr = "$here\n$rawline\n"; my $hereprev = "$here\n$prevrawline\n$rawline\n"; $cnt_lines++ if ($realcnt != 0); #check the patch for a signoff: if ($line =~ /^\s*signed-off-by:/i) { # This is a signoff, if ugly, so do not double report. $signoff++; if (!($line =~ /^\s*Signed-off-by:/)) { WARN("Signed-off-by: is the preferred form\n" . $herecurr); } if ($line =~ /^\s*signed-off-by:\S/i) { WARN("space required after Signed-off-by:\n" . $herecurr); } } # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("patch seems to be corrupt (line wrapped?)\n" . $herecurr) if (!$emitted_corrupt++); } # Check for absolute kernel paths. if ($tree) { while ($line =~ m{(?:^|\s)(/\S*)}g) { my $file = $1; if ($file =~ m{^(.*?)(?::\d+)+:?$} && check_absolute_file($1, $herecurr)) { # } else { check_absolute_file($file, $herecurr); } } } # UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php if (($realfile =~ /^$/ || $line =~ /^\+/) && $rawline !~ m/^$UTF8*$/) { my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); my $blank = copy_spacing($rawline); my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; my $hereptr = "$hereline$ptr\n"; ERROR("Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); } # ignore non-hunk lines and lines being removed next if (!$hunk_line || $line =~ /^-/); #trailing whitespace if ($line =~ /^\+.*\015/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; ERROR("DOS line endings\n" . $herevet); } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; ERROR("trailing whitespace\n" . $herevet); } # check for Kconfig help text having a real description if ($realfile =~ /Kconfig/ && $line =~ /\+?\s*(---)?help(---)?$/) { my $length = 0; for (my $l = $linenr; defined($lines[$l]); $l++) { my $f = $lines[$l]; $f =~ s/#.*//; $f =~ s/^\s+//; next if ($f =~ /^$/); last if ($f =~ /^\s*config\s/); $length++; } WARN("please write a paragraph that describes the config symbol fully\n" . $herecurr) if ($length < 4); } # check we are in a valid source file if not then ignore this hunk next if ($realfile !~ /\.(h|c|s|S|pl|sh)$/); #80 column limit if ($line =~ /^\+/ && $prevrawline !~ /\/\*\*/ && $rawline !~ /^.\s*\*\s*\@$Ident\s/ && !($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(KERN_\S+\s*|[^"]*))?"[X\t]*"\s*(?:,|\)\s*;)\s*$/ || $line =~ /^\+\s*"[^"]*"\s*(?:\s*|,|\)\s*;)\s*$/) && $length > 80) { WARN("line over 80 characters\n" . $herecurr); } # check for spaces before a quoted newline if ($rawline =~ /^.*\".*\s\\n/) { WARN("unnecessary whitespace before a quoted newline\n" . $herecurr); } # check for adding lines without a newline. if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { WARN("adding a line without newline at end of file\n" . $herecurr); } # Blackfin: use hi/lo macros if ($realfile =~ m@arch/blackfin/.*\.S$@) { if ($line =~ /\.[lL][[:space:]]*=.*&[[:space:]]*0x[fF][fF][fF][fF]/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("use the LO() macro, not (... & 0xFFFF)\n" . $herevet); } if ($line =~ /\.[hH][[:space:]]*=.*>>[[:space:]]*16/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("use the HI() macro, not (... >> 16)\n" . $herevet); } } # check we are in a valid source file C or perl if not then ignore this hunk next if ($realfile !~ /\.(h|c|pl)$/); # at the beginning of a line any tabs must come first and anything # more than 8 must use tabs. if ($rawline =~ /^\+\s* \t\s*\S/ || $rawline =~ /^\+\s* \s*/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; ERROR("code indent should use tabs where possible\n" . $herevet); } # check for space before tabs. if ($rawline =~ /^\+/ && $rawline =~ / \t/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; WARN("please, no space before tabs\n" . $herevet); } # check for spaces at the beginning of a line. if ($rawline =~ /^\+ / && $rawline !~ /\+ +\*/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; WARN("please, no space for starting a line, \ excluding comments\n" . $herevet); } # check we are in a valid C source file if not then ignore this hunk next if ($realfile !~ /\.(h|c)$/); # check for RCS/CVS revision markers if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { WARN("CVS style keyword markers, these will _not_ be updated\n". $herecurr); } # Blackfin: don't use __builtin_bfin_[cs]sync if ($line =~ /__builtin_bfin_csync/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("use the CSYNC() macro in asm/blackfin.h\n" . $herevet); } if ($line =~ /__builtin_bfin_ssync/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("use the SSYNC() macro in asm/blackfin.h\n" . $herevet); } # Check for potential 'bare' types my ($stat, $cond, $line_nr_next, $remain_next, $off_next, $realline_next); if ($realcnt && $line =~ /.\s*\S/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0); $stat =~ s/\n./\n /g; $cond =~ s/\n./\n /g; # Find the real next line. $realline_next = $line_nr_next; if (defined $realline_next && (!defined $lines[$realline_next - 1] || substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { $realline_next++; } my $s = $stat; $s =~ s/{.*$//s; # Ignore goto labels. if ($s =~ /$Ident:\*$/s) { # Ignore functions being called } elsif ($s =~ /^.\s*$Ident\s*\(/s) { } elsif ($s =~ /^.\s*else\b/s) { # declarations always start with types } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { my $type = $1; $type =~ s/\s+/ /g; possible($type, "A:" . $s); # definitions in global scope can only start with types } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { possible($1, "B:" . $s); } # any (foo ... *) is a pointer cast, and foo is a type while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { possible($1, "C:" . $s); } # Check for any sort of function declaration. # int foo(something bar, other baz); # void (*store_gdt)(x86_descr_ptr *); if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { my ($name_len) = length($1); my $ctx = $s; substr($ctx, 0, $name_len + 1, ''); $ctx =~ s/\)[^\)]*$//; for my $arg (split(/\s*,\s*/, $ctx)) { if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { possible($1, "D:" . $s); } } } } # # Checks which may be anchored in the context. # # Check for switch () and associated case and default # statements should be at the same indent. if ($line=~/\bswitch\s*\(.*\)/) { my $err = ''; my $sep = ''; my @ctx = ctx_block_outer($linenr, $realcnt); shift(@ctx); for my $ctx (@ctx) { my ($clen, $cindent) = line_stats($ctx); if ($ctx =~ /^\+\s*(case\s+|default:)/ && $indent != $cindent) { $err .= "$sep$ctx\n"; $sep = ''; } else { $sep = "[...]\n"; } } if ($err ne '') { ERROR("switch and case should be at the same indent\n$hereline$err"); } } # if/while/etc brace do not go on next line, unless defining a do while loop, # or if that brace on the next line is for something else if ($line =~ /(.*)\b((?:if|while|for|switch)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { my $pre_ctx = "$1$2"; my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); my $ctx_cnt = $realcnt - $#ctx - 1; my $ctx = join("\n", @ctx); my $ctx_ln = $linenr; my $ctx_skip = $realcnt; while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && defined $lines[$ctx_ln - 1] && $lines[$ctx_ln - 1] =~ /^-/)) { ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); $ctx_ln++; } #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln -1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { ERROR("that open brace { should be on the previous line\n" . "$here\n$ctx\n$lines[$ctx_ln - 1]\n"); } if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && $ctx =~ /\)\s*\;\s*$/ && defined $lines[$ctx_ln - 1]) { my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); if ($nindent > $indent) { WARN("trailing semicolon indicates no statements, indent implies otherwise\n" . "$here\n$ctx\n$lines[$ctx_ln - 1]\n"); } } } # Check relative indent for conditionals and blocks. if ($line =~ /\b(?:(?:if|while|for)\s*\(|do\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { my ($s, $c) = ($stat, $cond); substr($s, 0, length($c), ''); # Make sure we remove the line prefixes as we have # none on the first line, and are going to readd them # where necessary. $s =~ s/\n./\n/gs; # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; # We want to check the first line inside the block # starting at the end of the conditional, so remove: # 1) any blank line termination # 2) any opening brace { on end of the line # 3) any do (...) { my $continuation = 0; my $check = 0; $s =~ s/^.*\bdo\b//; $s =~ s/^\s*{//; if ($s =~ s/^\s*\\//) { $continuation = 1; } if ($s =~ s/^\s*?\n//) { $check = 1; $cond_lines++; } # Also ignore a loop construct at the end of a # preprocessor statement. if (($prevline =~ /^.\s*#\s*define\s/ || $prevline =~ /\\\s*$/) && $continuation == 0) { $check = 0; } my $cond_ptr = -1; $continuation = 0; while ($cond_ptr != $cond_lines) { $cond_ptr = $cond_lines; # If we see an #else/#elif then the code # is not linear. if ($s =~ /^\s*\#\s*(?:else|elif)/) { $check = 0; } # Ignore: # 1) blank lines, they should be at 0, # 2) preprocessor lines, and # 3) labels. if ($continuation || $s =~ /^\s*?\n/ || $s =~ /^\s*#\s*?/ || $s =~ /^\s*$Ident\s*:/) { $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; if ($s =~ s/^.*?\n//) { $cond_lines++; } } } my (undef, $sindent) = line_stats("+" . $s); my $stat_real = raw_line($linenr, $cond_lines); # Check if either of these lines are modified, else # this is not this patch's fault. if (!defined($stat_real) || $stat !~ /^\+/ && $stat_real !~ /^\+/) { $check = 0; } if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; if ($check && (($sindent % 8) != 0 || ($sindent <= $indent && $s ne ''))) { WARN("suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); } } # Track the 'values' across context and added lines. my $opline = $line; $opline =~ s/^./ /; my ($curr_values, $curr_vars) = annotate_values($opline . "\n", $prev_values); $curr_values = $prev_values . $curr_values; if ($dbg_values) { my $outline = $opline; $outline =~ s/\t/ /g; print "$linenr > .$outline\n"; print "$linenr > $curr_values\n"; print "$linenr > $curr_vars\n"; } $prev_values = substr($curr_values, -1); #ignore lines not being added if ($line=~/^[^\+]/) {next;} # TEST: allow direct testing of the type matcher. if ($dbg_type) { if ($line =~ /^.\s*$Declare\s*$/) { ERROR("TEST: is type\n" . $herecurr); } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { ERROR("TEST: is not type ($1 is)\n". $herecurr); } next; } # TEST: allow direct testing of the attribute matcher. if ($dbg_attr) { if ($line =~ /^.\s*$Modifier\s*$/) { ERROR("TEST: is attr\n" . $herecurr); } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { ERROR("TEST: is not attr ($1 is)\n". $herecurr); } next; } # check for initialisation to aggregates open brace on the next line if ($line =~ /^.\s*{/ && $prevline =~ /(?:^|[^=])=\s*$/) { ERROR("that open brace { should be on the previous line\n" . $hereprev); } # # Checks which are anchored on the added line. # # check for malformed paths in #include statements (uses RAW line) if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { my $path = $1; if ($path =~ m{//}) { ERROR("malformed #include filename\n" . $herecurr); } } # no C99 // comments if ($line =~ m{//}) { ERROR("do not use C99 // comments\n" . $herecurr); } # Remove C99 comments. $line =~ s@//.*@@; $opline =~ s@//.*@@; # EXPORT_SYMBOL should immediately follow the thing it is exporting, consider # the whole statement. #print "APW <$lines[$realline_next - 1]>\n"; if (defined $realline_next && exists $lines[$realline_next - 1] && !defined $suppress_export{$realline_next} && ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/ || $lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { my $name = $1; if ($stat !~ /(?: \n.}\s*$| ^.DEFINE_$Ident\(\Q$name\E\)| ^.DECLARE_$Ident\(\Q$name\E\)| ^.LIST_HEAD\(\Q$name\E\)| ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() )/x) { #print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; $suppress_export{$realline_next} = 2; } else { $suppress_export{$realline_next} = 1; } } if (!defined $suppress_export{$linenr} && $prevline =~ /^.\s*$/ && ($line =~ /EXPORT_SYMBOL.*\((.*)\)/ || $line =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { #print "FOO B <$lines[$linenr - 1]>\n"; $suppress_export{$linenr} = 2; } if (defined $suppress_export{$linenr} && $suppress_export{$linenr} == 2) { WARN("EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); } # check for global initialisers. if ($line =~ /^.$Type\s*$Ident\s*(?:\s+$Modifier)*\s*=\s*(0|NULL|false)\s*;/) { ERROR("do not initialise globals to 0 or NULL\n" . $herecurr); } # check for static initialisers. if ($line =~ /\bstatic\s.*=\s*(0|NULL|false)\s*;/) { ERROR("do not initialise statics to 0 or NULL\n" . $herecurr); } # check for new typedefs, only function parameters and sparse annotations # make sense. if ($line =~ /\btypedef\s/ && $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && $line !~ /\b$typeTypedefs\b/ && $line !~ /\b__bitwise(?:__|)\b/) { WARN("do not add new typedefs\n" . $herecurr); } # * goes on variable not on type # (char*[ const]) if ($line =~ m{\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\)}) { my ($from, $to) = ($1, $1); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } #print "from<$from> to<$to>\n"; if ($from ne $to) { ERROR("\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr); } } elsif ($line =~ m{\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident)}) { my ($from, $to, $ident) = ($1, $1, $2); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } # Modifiers should have spaces. $to =~ s/(\b$Modifier$)/$1 /; #print "from<$from> to<$to> ident<$ident>\n"; if ($from ne $to && $ident !~ /^$Modifier$/) { ERROR("\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr); } } # # no BUG() or BUG_ON() # if ($line =~ /\b(BUG|BUG_ON)\b/) { # print "Try to use WARN_ON & Recovery code rather than BUG() or BUG_ON()\n"; # print "$herecurr"; # $clean = 0; # } if ($line =~ /\bLINUX_VERSION_CODE\b/) { WARN("LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); } # printk should use KERN_* levels. Note that follow on printk's on the # same line do not need a level, so we use the current block context # to try and find and validate the current printk. In summary the current # printk includes all preceeding printk's which have no newline on the end. # we assume the first bad printk is the one to report. if ($line =~ /\bprintk\((?!KERN_)\s*"/) { my $ok = 0; for (my $ln = $linenr - 1; $ln >= $first_line; $ln--) { #print "CHECK<$lines[$ln - 1]\n"; # we have a preceeding printk if it ends # with "\n" ignore it, else it is to blame if ($lines[$ln - 1] =~ m{\bprintk\(}) { if ($rawlines[$ln - 1] !~ m{\\n"}) { $ok = 1; } last; } } if ($ok == 0) { WARN("printk() should include KERN_ facility level\n" . $herecurr); } } # function brace can't be on same line, except for #defines of do while, # or if closed on same line if (($line=~/$Type\s*$Ident\(.*\).*\s{/) and !($line=~/\#\s*define.*do\s{/) and !($line=~/}/)) { ERROR("open brace '{' following function declarations go on the next line\n" . $herecurr); } # open braces for enum, union and struct go on the same line. if ($line =~ /^.\s*{/ && $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { ERROR("open brace '{' following $1 go on the same line\n" . $hereprev); } # check for spacing round square brackets; allowed: # 1. with a type on the left -- int [] a; # 2. at the beginning of a line for slice initialisers -- [0...10] = 5, # 3. inside a curly brace -- = { [0...10] = 5 } while ($line =~ /(.*?\s)\[/g) { my ($where, $prefix) = ($-[1], $1); if ($prefix !~ /$Type\s+$/ && ($where != 0 || $prefix !~ /^.\s+$/) && $prefix !~ /{\s+$/) { ERROR("space prohibited before open square bracket '['\n" . $herecurr); } } # check for spaces between functions and their parentheses. while ($line =~ /($Ident)\s+\(/g) { my $name = $1; my $ctx_before = substr($line, 0, $-[1]); my $ctx = "$ctx_before$name"; # Ignore those directives where spaces _are_ permitted. if ($name =~ /^(?: if|for|while|switch|return|case| volatile|__volatile__| __attribute__|format|__extension__| asm|__asm__)$/x) { # cpp #define statements have non-optional spaces, ie # if there is a space between the name and the open # parenthesis it is simply not a parameter group. } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { # cpp #elif statement condition may start with a ( } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { # If this whole things ends with a type its most # likely a typedef for a function. } elsif ($ctx =~ /$Type$/) { } else { WARN("space prohibited between function name and open parenthesis '('\n" . $herecurr); } } # Check operator spacing. if (!($line=~/\#\s*include/)) { my $ops = qr{ <<=|>>=|<=|>=|==|!=| \+=|-=|\*=|\/=|%=|\^=|\|=|&=| =>|->|<<|>>|<|>|=|!|~| &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| \?|: }x; my @elements = split(/($ops|;)/, $opline); my $off = 0; my $blank = copy_spacing($opline); for (my $n = 0; $n < $#elements; $n += 2) { $off += length($elements[$n]); # Pick up the preceeding and succeeding characters. my $ca = substr($opline, 0, $off); my $cc = ''; if (length($opline) >= ($off + length($elements[$n + 1]))) { $cc = substr($opline, $off + length($elements[$n + 1])); } my $cb = "$ca$;$cc"; my $a = ''; $a = 'V' if ($elements[$n] ne ''); $a = 'W' if ($elements[$n] =~ /\s$/); $a = 'C' if ($elements[$n] =~ /$;$/); $a = 'B' if ($elements[$n] =~ /(\[|\()$/); $a = 'O' if ($elements[$n] eq ''); $a = 'E' if ($ca =~ /^\s*$/); my $op = $elements[$n + 1]; my $c = ''; if (defined $elements[$n + 2]) { $c = 'V' if ($elements[$n + 2] ne ''); $c = 'W' if ($elements[$n + 2] =~ /^\s/); $c = 'C' if ($elements[$n + 2] =~ /^$;/); $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); $c = 'O' if ($elements[$n + 2] eq ''); $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); } else { $c = 'E'; } my $ctx = "${a}x${c}"; my $at = "(ctx:$ctx)"; my $ptr = substr($blank, 0, $off) . "^"; my $hereptr = "$hereline$ptr\n"; # Pull out the value of this operator. my $op_type = substr($curr_values, $off + 1, 1); # Get the full operator variant. my $opv = $op . substr($curr_vars, $off, 1); # Ignore operators passed as parameters. if ($op_type ne 'V' && $ca =~ /\s$/ && $cc =~ /^\s*,/) { # # Ignore comments # } elsif ($op =~ /^$;+$/) { # ; should have either the end of line or a space or \ after it } elsif ($op eq ';') { if ($ctx !~ /.x[WEBC]/ && $cc !~ /^\\/ && $cc !~ /^;/) { ERROR("space required after that '$op' $at\n" . $hereptr); } # // is a comment } elsif ($op eq '//') { # No spaces for: # -> # : when part of a bitfield } elsif ($op eq '->' || $opv eq ':B') { if ($ctx =~ /Wx.|.xW/) { ERROR("spaces prohibited around that '$op' $at\n" . $hereptr); } # , must have a space on the right. } elsif ($op eq ',') { if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { ERROR("space required after that '$op' $at\n" . $hereptr); } # '*' as part of a type definition -- reported already. } elsif ($opv eq '*_') { #warn "'*' is part of type\n"; # unary operators should have a space before and # none after. May be left adjacent to another # unary operator, or a cast } elsif ($op eq '!' || $op eq '~' || $opv eq '*U' || $opv eq '-U' || $opv eq '&U' || $opv eq '&&U') { if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { ERROR("space required before that '$op' $at\n" . $hereptr); } if ($op eq '*' && $cc =~/\s*$Modifier\b/) { # A unary '*' may be const } elsif ($ctx =~ /.xW/) { ERROR("space prohibited after that '$op' $at\n" . $hereptr); } # unary ++ and unary -- are allowed no space on one side. } elsif ($op eq '++' or $op eq '--') { if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { ERROR("space required one side of that '$op' $at\n" . $hereptr); } if ($ctx =~ /Wx[BE]/ || ($ctx =~ /Wx./ && $cc =~ /^;/)) { ERROR("space prohibited before that '$op' $at\n" . $hereptr); } if ($ctx =~ /ExW/) { ERROR("space prohibited after that '$op' $at\n" . $hereptr); } # << and >> may either have or not have spaces both sides } elsif ($op eq '<<' or $op eq '>>' or $op eq '&' or $op eq '^' or $op eq '|' or $op eq '+' or $op eq '-' or $op eq '*' or $op eq '/' or $op eq '%') { if ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { ERROR("need consistent spacing around '$op' $at\n" . $hereptr); } # A colon needs no spaces before when it is # terminating a case value or a label. } elsif ($opv eq ':C' || $opv eq ':L') { if ($ctx =~ /Wx./) { ERROR("space prohibited before that '$op' $at\n" . $hereptr); } # All the others need spaces both sides. } elsif ($ctx !~ /[EWC]x[CWE]/) { my $ok = 0; # Ignore email addresses if (($op eq '<' && $cc =~ /^\S+\@\S+>/) || ($op eq '>' && $ca =~ /<\S+\@\S+$/)) { $ok = 1; } # Ignore ?: if (($opv eq ':O' && $ca =~ /\?$/) || ($op eq '?' && $cc =~ /^:/)) { $ok = 1; } if ($ok == 0) { ERROR("spaces required around that '$op' $at\n" . $hereptr); } } $off += length($elements[$n + 1]); } } # check for multiple assignments if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { CHK("multiple assignments should be avoided\n" . $herecurr); } ## # check for multiple declarations, allowing for a function declaration ## # continuation. ## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && ## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { ## ## # Remove any bracketed sections to ensure we do not ## # falsly report the parameters of functions. ## my $ln = $line; ## while ($ln =~ s/\([^\(\)]*\)//g) { ## } ## if ($ln =~ /,/) { ## WARN("declaring multiple variables together should be avoided\n" . $herecurr); ## } ## } #need space before brace following if, while, etc if (($line =~ /\(.*\){/ && $line !~ /\($Type\){/) || $line =~ /do{/) { ERROR("space required before the open brace '{'\n" . $herecurr); } # closing brace should have a space following it when it has anything # on the line if ($line =~ /}(?!(?:,|;|\)))\S/) { ERROR("space required after that close brace '}'\n" . $herecurr); } # check spacing on square brackets if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { ERROR("space prohibited after that open square bracket '['\n" . $herecurr); } if ($line =~ /\s\]/) { ERROR("space prohibited before that close square bracket ']'\n" . $herecurr); } # check spacing on parentheses if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && $line !~ /for\s*\(\s+;/) { ERROR("space prohibited after that open parenthesis '('\n" . $herecurr); } if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && $line !~ /for\s*\(.*;\s+\)/ && $line !~ /:\s+\)/) { ERROR("space prohibited before that close parenthesis ')'\n" . $herecurr); } #goto labels aren't indented, allow a single space however if ($line=~/^.\s+[A-Za-z\d_]+:(?![0-9]+)/ and !($line=~/^. [A-Za-z\d_]+:/) and !($line=~/^.\s+default:/)) { WARN("labels should not be indented\n" . $herecurr); } # Return is not a function. if (defined($stat) && $stat =~ /^.\s*return(\s*)(\(.*);/s) { my $spacing = $1; my $value = $2; # Flatten any parentheses $value =~ s/\)\(/\) \(/g; while ($value =~ s/\[[^\{\}]*\]/1/ || $value !~ /(?:$Ident|-?$Constant)\s* $Compare\s* (?:$Ident|-?$Constant)/x && $value =~ s/\([^\(\)]*\)/1/) { } if ($value =~ /^(?:$Ident|-?$Constant)$/) { ERROR("return is not a function, parentheses are not required\n" . $herecurr); } elsif ($spacing !~ /\s+/) { ERROR("space required before the open parenthesis '('\n" . $herecurr); } } # Need a space before open parenthesis after if, while etc if ($line=~/\b(if|while|for|switch)\(/) { ERROR("space required before the open parenthesis '('\n" . $herecurr); } # Check for illegal assignment in if conditional -- and check for trailing # statements after the conditional. if ($line =~ /do\s*(?!{)/) { my ($stat_next) = ctx_statement_block($line_nr_next, $remain_next, $off_next); $stat_next =~ s/\n./\n /g; ##print "stat<$stat> stat_next<$stat_next>\n"; if ($stat_next =~ /^\s*while\b/) { # If the statement carries leading newlines, # then count those as offsets. my ($whitespace) = ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; $suppress_whiletrailers{$line_nr_next + $offset} = 1; } } if (!defined $suppress_whiletrailers{$linenr} && $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { my ($s, $c) = ($stat, $cond); if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { ERROR("do not use assignment in if condition\n" . $herecurr); } # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; $s =~ s/$;//g; # Remove any comments if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && $c !~ /}\s*while\s*/) { # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; my $stat_real = ''; $stat_real = raw_line($linenr, $cond_lines) . "\n" if ($cond_lines); if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } ERROR("trailing statements should be on next line\n" . $herecurr . $stat_real); } } # Check for bitwise tests written as boolean if ($line =~ / (?: (?:\[|\(|\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\|) | (?:\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\||\)|\]) )/x) { WARN("boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); } # if and else should not have general statements after it if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { my $s = $1; $s =~ s/$;//g; # Remove any comments if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { ERROR("trailing statements should be on next line\n" . $herecurr); } } # if should not continue a brace if ($line =~ /}\s*if\b/) { ERROR("trailing statements should be on next line\n" . $herecurr); } # case and default should not have general statements after them if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && $line !~ /\G(?: (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| \s*return\s+ )/xg) { ERROR("trailing statements should be on next line\n" . $herecurr); } # Check for }else {, these must be at the same # indent level to be relevant to each other. if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ and $previndent == $indent) { ERROR("else should follow close brace '}'\n" . $hereprev); } if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ and $previndent == $indent) { my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; if ($s =~ /^\s*;/) { ERROR("while should follow close brace '}'\n" . $hereprev); } } #studly caps, commented out until figure out how to distinguish between use of existing and adding new # if (($line=~/[\w_][a-z\d]+[A-Z]/) and !($line=~/print/)) { # print "No studly caps, use _\n"; # print "$herecurr"; # $clean = 0; # } #no spaces allowed after \ in define if ($line=~/\#\s*define.*\\\s$/) { WARN("Whitepspace after \\ makes next lines useless\n" . $herecurr); } #warn if is #included and is available (uses RAW line) if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\}) { my $file = "$1.h"; my $checkfile = "include/linux/$file"; if (-f "$root/$checkfile" && $realfile ne $checkfile && $1 !~ /$allowed_asm_includes/) { if ($realfile =~ m{^arch/}) { CHK("Consider using #include instead of \n" . $herecurr); } else { WARN("Use #include instead of \n" . $herecurr); } } } # multi-statement macros should be enclosed in a do while loop, grab the # first statement and ensure its the whole macro if its not enclosed # in a known good container if ($realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { my $ln = $linenr; my $cnt = $realcnt; my ($off, $dstat, $dcond, $rest); my $ctx = ''; my $args = defined($1); # Find the end of the macro and limit our statement # search to that. while ($cnt > 0 && defined $lines[$ln - 1] && $lines[$ln - 1] =~ /^(?:-|..*\\$)/) { $ctx .= $rawlines[$ln - 1] . "\n"; $cnt-- if ($lines[$ln - 1] !~ /^-/); $ln++; } $ctx .= $rawlines[$ln - 1]; ($dstat, $dcond, $ln, $cnt, $off) = ctx_statement_block($linenr, $ln - $linenr + 1, 0); #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; # Extract the remainder of the define (if any) and # rip off surrounding spaces, and trailing \'s. $rest = ''; while ($off != 0 || ($cnt > 0 && $rest =~ /\\\s*$/)) { #print "ADDING cnt<$cnt> $off <" . substr($lines[$ln - 1], $off) . "> rest<$rest>\n"; if ($off != 0 || $lines[$ln - 1] !~ /^-/) { $rest .= substr($lines[$ln - 1], $off) . "\n"; $cnt--; } $ln++; $off = 0; } $rest =~ s/\\\n.//g; $rest =~ s/^\s*//s; $rest =~ s/\s*$//s; # Clean up the original statement. if ($args) { substr($dstat, 0, length($dcond), ''); } else { $dstat =~ s/^.\s*\#\s*define\s+$Ident\s*//; } $dstat =~ s/$;//g; $dstat =~ s/\\\n.//g; $dstat =~ s/^\s*//s; $dstat =~ s/\s*$//s; # Flatten any parentheses and braces while ($dstat =~ s/\([^\(\)]*\)/1/ || $dstat =~ s/\{[^\{\}]*\}/1/ || $dstat =~ s/\[[^\{\}]*\]/1/) { } my $exceptions = qr{ $Declare| module_param_named| MODULE_PARAM_DESC| DECLARE_PER_CPU| DEFINE_PER_CPU| __typeof__\(| union| struct| \.$Ident\s*=\s*| ^\"|\"$ }x; #print "REST<$rest> dstat<$dstat>\n"; if ($rest ne '') { if ($rest !~ /while\s*\(/ && $dstat !~ /$exceptions/) { ERROR("Macros with multiple statements should be enclosed in a do - while loop\n" . "$here\n$ctx\n"); } } elsif ($ctx !~ /;/) { if ($dstat ne '' && $dstat !~ /^(?:$Ident|-?$Constant)$/ && $dstat !~ /$exceptions/ && $dstat !~ /^\.$Ident\s*=/ && $dstat =~ /$Operators/) { ERROR("Macros with complex values should be enclosed in parenthesis\n" . "$here\n$ctx\n"); } } } # make sure symbols are always wrapped with VMLINUX_SYMBOL() ... # all assignments may have only one of the following with an assignment: # . # ALIGN(...) # VMLINUX_SYMBOL(...) if ($realfile eq 'vmlinux.lds.h' && $line =~ /(?:(?:^|\s)$Ident\s*=|=\s*$Ident(?:\s|$))/) { WARN("vmlinux.lds.h needs VMLINUX_SYMBOL() around C-visible symbols\n" . $herecurr); } # check for redundant bracing round if etc if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, 1); #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; if ($#chunks > 0 && $level == 0) { my $allowed = 0; my $seen = 0; my $herectx = $here . "\n"; my $ln = $linenr - 1; for my $chunk (@chunks) { my ($cond, $block) = @{$chunk}; # If the condition carries leading newlines, then count those as offsets. my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; # We have looked at and allowed this specific line. $suppress_ifbraces{$ln + $offset} = 1; $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; $ln += statement_rawlines($block) - 1; substr($block, 0, length($cond), ''); $seen++ if ($block =~ /^\s*{/); #print "cond<$cond> block<$block> allowed<$allowed>\n"; if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed = 1; } } if ($seen && !$allowed) { WARN("braces {} are not necessary for any arm of this statement\n" . $herectx); } } } if (!defined $suppress_ifbraces{$linenr - 1} && $line =~ /\b(if|while|for|else)\b/) { my $allowed = 0; # Check the pre-context. if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { #print "APW: ALLOWED: pre<$1>\n"; $allowed = 1; } my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, $-[0]); # Check the condition. my ($cond, $block) = @{$chunks[0]}; #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; if (defined $cond) { substr($block, 0, length($cond), ''); } if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed = 1; } # Check the post-context. if (defined $chunks[1]) { my ($cond, $block) = @{$chunks[1]}; if (defined $cond) { substr($block, 0, length($cond), ''); } if ($block =~ /^\s*\{/) { #print "APW: ALLOWED: chunk-1 block<$block>\n"; $allowed = 1; } } if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { my $herectx = $here . "\n";; my $cnt = statement_rawlines($block); for (my $n = 0; $n < $cnt; $n++) { $herectx .= raw_line($linenr, $n) . "\n";; } WARN("braces {} are not necessary for single statement blocks\n" . $herectx); } } # don't include deprecated include files (uses RAW line) for my $inc (@dep_includes) { if ($rawline =~ m@^.\s*\#\s*include\s*\<$inc>@) { ERROR("Don't use <$inc>: see Documentation/feature-removal-schedule.txt\n" . $herecurr); } } # don't use deprecated functions for my $func (@dep_functions) { if ($line =~ /\b$func\b/) { ERROR("Don't use $func(): see Documentation/feature-removal-schedule.txt\n" . $herecurr); } } # no volatiles please my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { WARN("Use of volatile is usually wrong: see Documentation/volatile-considered-harmful.txt\n" . $herecurr); } # SPIN_LOCK_UNLOCKED & RW_LOCK_UNLOCKED are deprecated if ($line =~ /\b(SPIN_LOCK_UNLOCKED|RW_LOCK_UNLOCKED)/) { ERROR("Use of $1 is deprecated: see Documentation/spinlocks.txt\n" . $herecurr); } # warn about #if 0 if ($line =~ /^.\s*\#\s*if\s+0\b/) { CHK("if this code is redundant consider removing it\n" . $herecurr); } # check for needless kfree() checks if ($prevline =~ /\bif\s*\(([^\)]*)\)/) { my $expr = $1; if ($line =~ /\bkfree\(\Q$expr\E\);/) { WARN("kfree(NULL) is safe this check is probably not required\n" . $hereprev); } } # check for needless usb_free_urb() checks if ($prevline =~ /\bif\s*\(([^\)]*)\)/) { my $expr = $1; if ($line =~ /\busb_free_urb\(\Q$expr\E\);/) { WARN("usb_free_urb(NULL) is safe this check is probably not required\n" . $hereprev); } } # prefer usleep_range over udelay if ($line =~ /\budelay\s*\(\s*(\w+)\s*\)/) { # ignore udelay's < 10, however if (! (($1 =~ /(\d+)/) && ($1 < 10)) ) { CHK("usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt\n" . $line); } } # warn about unexpectedly long msleep's if ($line =~ /\bmsleep\s*\((\d+)\);/) { if ($1 < 20) { WARN("msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt\n" . $line); } } # warn about #ifdefs in C files # if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { # print "#ifdef in C files should be avoided\n"; # print "$herecurr"; # $clean = 0; # } # warn about spacing in #ifdefs if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { ERROR("exactly one space required after that #$1\n" . $herecurr); } # check for spinlock_t definitions without a comment. if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { my $which = $1; if (!ctx_has_comment($first_line, $linenr)) { CHK("$1 definition without comment\n" . $herecurr); } } # check for memory barriers without a comment. if ($line =~ /\b(mb|rmb|wmb|read_barrier_depends|smp_mb|smp_rmb|smp_wmb|smp_read_barrier_depends)\(/) { if (!ctx_has_comment($first_line, $linenr)) { CHK("memory barrier without comment\n" . $herecurr); } } # check of hardware specific defines if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { CHK("architecture specific defines should be avoided\n" . $herecurr); } # Check that the storage class is at the beginning of a declaration if ($line =~ /\b$Storage\b/ && $line !~ /^.\s*$Storage\b/) { WARN("storage class should be at the beginning of the declaration\n" . $herecurr) } # check the location of the inline attribute, that it is between # storage class and type. if ($line =~ /\b$Type\s+$Inline\b/ || $line =~ /\b$Inline\s+$Storage\b/) { ERROR("inline keyword should sit between storage class and type\n" . $herecurr); } # Check for __inline__ and __inline, prefer inline if ($line =~ /\b(__inline__|__inline)\b/) { WARN("plain inline is preferred over $1\n" . $herecurr); } # check for sizeof(&) if ($line =~ /\bsizeof\s*\(\s*\&/) { WARN("sizeof(& should be avoided\n" . $herecurr); } # check for new externs in .c files. if ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) { my $function_name = $1; my $paren_space = $2; my $s = $stat; if (defined $cond) { substr($s, 0, length($cond), ''); } if ($s =~ /^\s*;/ && $function_name ne 'uninitialized_var') { WARN("externs should be avoided in .c files\n" . $herecurr); } if ($paren_space =~ /\n/) { WARN("arguments for function declarations should follow identifier\n" . $herecurr); } } elsif ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*extern\s+/) { WARN("externs should be avoided in .c files\n" . $herecurr); } # checks for new __setup's if ($rawline =~ /\b__setup\("([^"]*)"/) { my $name = $1; if (!grep(/$name/, @setup_docs)) { CHK("__setup appears un-documented -- check Documentation/kernel-parameters.txt\n" . $herecurr); } } # check for pointless casting of kmalloc return if ($line =~ /\*\s*\)\s*k[czm]alloc\b/) { WARN("unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); } # check for gcc specific __FUNCTION__ if ($line =~ /__FUNCTION__/) { WARN("__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr); } # check for semaphores used as mutexes if ($line =~ /^.\s*(DECLARE_MUTEX|init_MUTEX)\s*\(/) { WARN("mutexes are preferred for single holder semaphores\n" . $herecurr); } # check for semaphores used as mutexes if ($line =~ /^.\s*init_MUTEX_LOCKED\s*\(/) { WARN("consider using a completion\n" . $herecurr); } # recommend strict_strto* over simple_strto* if ($line =~ /\bsimple_(strto.*?)\s*\(/) { WARN("consider using strict_$1 in preference to simple_$1\n" . $herecurr); } # check for __initcall(), use device_initcall() explicitly please if ($line =~ /^.\s*__initcall\s*\(/) { WARN("please use device_initcall() instead of __initcall()\n" . $herecurr); } # check for various ops structs, ensure they are const. my $struct_ops = qr{acpi_dock_ops| address_space_operations| backlight_ops| block_device_operations| dentry_operations| dev_pm_ops| dma_map_ops| extent_io_ops| file_lock_operations| file_operations| hv_ops| ide_dma_ops| intel_dvo_dev_ops| item_operations| iwl_ops| kgdb_arch| kgdb_io| kset_uevent_ops| lock_manager_operations| microcode_ops| mtrr_ops| neigh_ops| nlmsvc_binding| pci_raw_ops| pipe_buf_operations| platform_hibernation_ops| platform_suspend_ops| proto_ops| rpc_pipe_ops| seq_operations| snd_ac97_build_ops| soc_pcmcia_socket_ops| stacktrace_ops| sysfs_ops| tty_operations| usb_mon_operations| wd_ops}x; if ($line !~ /\bconst\b/ && $line =~ /\bstruct\s+($struct_ops)\b/) { WARN("struct $1 should normally be const\n" . $herecurr); } # use of NR_CPUS is usually wrong # ignore definitions of NR_CPUS and usage to define arrays as likely right if ($line =~ /\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/) { WARN("usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); } # check for %L{u,d,i} in strings my $string; while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { $string = substr($rawline, $-[1], $+[1] - $-[1]); $string =~ s/%%/__/g; if ($string =~ /(?mutex.\n" . $herecurr); } } } # If we have no input at all, then there is nothing to report on # so just keep quiet. if ($#rawlines == -1) { exit(0); } # In mailback mode only produce a report in the negative, for # things that appear to be patches. if ($mailback && ($clean == 1 || !$is_patch)) { exit(0); } # This is not a patch, and we are are in 'no-patch' mode so # just keep quiet. if (!$chk_patch && !$is_patch) { exit(0); } if (!$is_patch) { ERROR("Does not appear to be a unified-diff format patch\n"); } if ($is_patch && $chk_signoff && $signoff == 0) { ERROR("Missing Signed-off-by: line(s)\n"); } print report_dump(); if ($summary && !($clean == 1 && $quiet == 1)) { print "$filename " if ($summary_file); print "total: $cnt_error errors, $cnt_warn warnings, " . (($check)? "$cnt_chk checks, " : "") . "$cnt_lines lines checked\n"; print "\n" if ($quiet == 0); } if ($clean == 1 && $quiet == 0) { print "$vname has no obvious style problems and is ready for submission.\n" } if ($clean == 0 && $quiet == 0) { print "$vname has style problems, please review. If any of these errors\n"; print "are false positives report them to the maintainer, see\n"; print "CHECKPATCH in MAINTAINERS.\n"; } return $clean; }