├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── ag_src ├── LICENSE ├── config.h ├── config.h.in ├── decompress.c ├── decompress.h ├── ignore.c ├── ignore.h ├── lang.c ├── lang.h ├── log.c ├── log.h ├── main.c ├── options.c ├── options.h ├── print.c ├── print.h ├── print_w32.c ├── scandir.c ├── scandir.h ├── search.c ├── search.h ├── uthash.h ├── util.c ├── util.h ├── win32 │ └── config.h └── zfile.c ├── bindings ├── CMakeLists.txt ├── javascript │ ├── CMakeLists.txt │ ├── README.md │ ├── examples │ │ ├── init_config.js │ │ └── simple.js │ ├── libag_node.c │ └── macros.h └── python │ ├── CMakeLists.txt │ ├── README.md │ ├── examples │ ├── init_config.py │ └── simple.py │ └── libag.i ├── doc ├── libag.pc.in ├── man1 │ ├── ag.1 │ ├── ag.1.md │ └── generate_man.sh └── man3 │ ├── ag_finish.3 │ ├── ag_free_all_results.3 │ ├── ag_free_result.3 │ ├── ag_get_stats.3 │ ├── ag_init.3 │ ├── ag_init_config.3 │ ├── ag_search.3 │ ├── ag_search_ts.3 │ ├── ag_set_config.3 │ ├── ag_start_workers.3 │ └── ag_stop_workers.3 ├── examples ├── init_config.c └── simple.c ├── libag.c └── libag.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Davidson Francis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Default rules. 16 | *.o 17 | *.d 18 | examples/simple 19 | examples/init_config 20 | bindings/python/__pycache__ 21 | bindings/python/libag.py 22 | bindings/python/libag.pyc 23 | bindings/python/_libag.so 24 | bindings/python/libag_wrap.c 25 | bindings/javascript/build/ 26 | libag.so 27 | build/ 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Davidson Francis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cmake_minimum_required(VERSION 3.5.2) 16 | set(CMAKE_C_STANDARD 99) 17 | 18 | project(ag C) 19 | include(GNUInstallDirs) 20 | 21 | # General 22 | add_compile_options(-Wall -Wextra) 23 | include_directories(${CMAKE_SOURCE_DIR}) 24 | add_definitions(-D_GNU_SOURCE) 25 | 26 | # Files 27 | set(AG_SRC 28 | ag_src/decompress.c 29 | ag_src/ignore.c 30 | ag_src/lang.c 31 | ag_src/log.c 32 | ag_src/main.c 33 | ag_src/options.c 34 | ag_src/print.c 35 | ag_src/print_w32.c 36 | ag_src/scandir.c 37 | ag_src/search.c 38 | ag_src/util.c 39 | ag_src/zfile.c 40 | ) 41 | set(LIBAG_DOC 42 | doc/man3/ag_finish.3 43 | doc/man3/ag_free_all_results.3 44 | doc/man3/ag_free_result.3 45 | doc/man3/ag_get_stats.3 46 | doc/man3/ag_init.3 47 | doc/man3/ag_init_config.3 48 | doc/man3/ag_search.3 49 | doc/man3/ag_search_ts.3 50 | doc/man3/ag_set_config.3 51 | doc/man3/ag_start_workers.3 52 | doc/man3/ag_stop_workers.3 53 | ) 54 | 55 | # libag objects 56 | add_library(libag_objects OBJECT ${AG_SRC} libag.c) 57 | set_target_properties(libag_objects PROPERTIES 58 | PUBLIC_HEADER libag.h 59 | POSITION_INDEPENDENT_CODE 1 60 | ) 61 | target_include_directories(libag_objects PRIVATE ag_src/) 62 | 63 | # libag 64 | add_library(ag SHARED $) 65 | target_link_libraries(ag pcre lzma z pthread) 66 | 67 | # pkg-config 68 | configure_file(doc/libag.pc.in libag.pc @ONLY) 69 | 70 | # Install libag 71 | install(TARGETS ag 72 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 73 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 74 | # Install manpages 75 | install(FILES doc/man1/ag.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) 76 | install(FILES ${LIBAG_DOC} DESTINATION ${CMAKE_INSTALL_MANDIR}/man3) 77 | # Install pkgconfig 78 | install(FILES ${CMAKE_BINARY_DIR}/libag.pc 79 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 80 | 81 | # Examples 82 | add_executable(simple 83 | examples/simple.c) 84 | target_link_libraries(simple ag) 85 | 86 | add_executable(init_config 87 | examples/init_config.c) 88 | target_link_libraries(init_config ag) 89 | 90 | ## Bindings 91 | # Python 92 | add_subdirectory(bindings) 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Davidson Francis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #=================================================================== 16 | # Paths 17 | #=================================================================== 18 | 19 | AG_SRC = $(CURDIR)/ag_src 20 | INCLUDE = -I $(AG_SRC) 21 | PREFIX ?= /usr/local 22 | BINDIR = $(PREFIX)/bin 23 | LIBDIR = $(PREFIX)/lib 24 | MANPAGES = $(CURDIR)/doc/ 25 | 26 | # Bindings 27 | SWIG ?= swig 28 | PYBIND = $(CURDIR)/bindings/python 29 | JSBIND = $(CURDIR)/bindings/javascript 30 | PY_INC ?= $(shell python-config --includes) 31 | 32 | #=================================================================== 33 | # Flags 34 | #=================================================================== 35 | 36 | CC ?= gcc 37 | PY_CFLAGS := $(CFLAGS) 38 | CFLAGS += -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -Wshadow 39 | CFLAGS += -Wpointer-arith -Wcast-qual -Wmissing-prototypes -Wno-missing-braces 40 | CFLAGS += -fPIC -std=c99 -D_GNU_SOURCE -MMD -O3 41 | LDFLAGS = -shared 42 | LDLIBS = -lpcre -llzma -lz -pthread 43 | 44 | # Bindings 45 | PY_CFLAGS += -fPIC -std=c99 -D_GNU_SOURCE -MMD -O3 46 | 47 | #=================================================================== 48 | # Rules 49 | #=================================================================== 50 | 51 | # Conflicts 52 | .PHONY : all clean examples install uninstall libag.pc 53 | .PHONY : bindings python-binding node-binding 54 | 55 | # Paths 56 | INCDIR = $(PREFIX)/include 57 | BINDIR = $(PREFIX)/bin 58 | MANDIR = $(PREFIX)/man 59 | PKGDIR = $(LIBDIR)/pkgconfig 60 | PKGFILE = $(DESTDIR)$(PKGDIR)/libag.pc 61 | 62 | # Sources 63 | C_SRC = ag_src/decompress.c ag_src/ignore.c ag_src/lang.c ag_src/log.c \ 64 | ag_src/main.c ag_src/options.c ag_src/print.c ag_src/print_w32.c \ 65 | ag_src/scandir.c ag_src/search.c ag_src/util.c ag_src/zfile.c \ 66 | libag.c 67 | 68 | # Objects 69 | OBJ = $(C_SRC:.c=.o) 70 | 71 | # Dependencies 72 | DEP = $(OBJ:.o=.d) 73 | 74 | all: libag.so examples 75 | 76 | # Pretty print 77 | Q := @ 78 | ifeq ($(V), 1) 79 | Q := 80 | endif 81 | 82 | # Build objects rule 83 | %.o: %.c 84 | @echo " CC $@" 85 | $(Q)$(CC) $< $(CFLAGS) $(INCLUDE) -c -o $@ 86 | 87 | # Ag standalone build 88 | libag.so: $(OBJ) 89 | @echo " LD $@" 90 | $(Q)$(CC) $^ $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ 91 | 92 | # Install 93 | install: libag.so libag.pc 94 | @echo " INSTALL $@" 95 | $(Q)install -d $(DESTDIR)$(LIBDIR) 96 | $(Q)install -m 755 $(CURDIR)/libag.so $(DESTDIR)$(LIBDIR) 97 | $(Q)install -d $(DESTDIR)$(INCDIR)/ 98 | $(Q)install -m 644 $(CURDIR)/libag.h $(DESTDIR)$(INCDIR)/ 99 | $(Q)install -d $(DESTDIR)$(MANDIR)/man1 100 | $(Q)install -d $(DESTDIR)$(MANDIR)/man3 101 | $(Q)install -m 644 $(MANPAGES)/man1/*.1 $(DESTDIR)$(MANDIR)/man1/ 102 | $(Q)install -m 644 $(MANPAGES)/man3/*.3 $(DESTDIR)$(MANDIR)/man3/ 103 | 104 | # Uninstall 105 | uninstall: 106 | @echo " UNINSTALL $@" 107 | $(Q)rm -f $(DESTDIR)$(INCDIR)/libag.h 108 | $(Q)rm -f $(DESTDIR)$(LIBDIR)/libag.so 109 | $(Q)rm -f $(DESTDIR)$(PKGDIR)/libag.pc 110 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man1/ag.1 111 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_finish.3 112 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_free_all_results.3 113 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_free_result.3 114 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_get_stats.3 115 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_init.3 116 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_init_config.3 117 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_search.3 118 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_search_ts.3 119 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_set_config.3 120 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_start_workers.3 121 | $(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_stop_workers.3 122 | 123 | # Generate libag.pc 124 | libag.pc: 125 | @install -d $(DESTDIR)$(PKGDIR) 126 | @echo 'prefix='$(DESTDIR)$(PREFIX) > $(PKGFILE) 127 | @echo 'libdir='$(DESTDIR)$(LIBDIR) >> $(PKGFILE) 128 | @echo 'includedir=$${prefix}/include' >> $(PKGFILE) 129 | @echo 'Name: libag' >> $(PKGFILE) 130 | @echo 'Description: The Silver Searcher Library' >> $(PKGFILE) 131 | @echo 'Version: 1.0' >> $(PKGFILE) 132 | @echo 'Libs: -L$${libdir} -lag' >> $(PKGFILE) 133 | @echo 'Libs.private: -lpcre -lzma -lz -pthread' >> $(PKGFILE) 134 | @echo 'Cflags: -I$${includedir}/' >> $(PKGFILE) 135 | 136 | # Examples 137 | examples: examples/simple examples/init_config 138 | examples/%.o: examples/%.c 139 | @echo " CC $@" 140 | $(Q)$(CC) $^ -c -I $(CURDIR) -o $@ 141 | examples/simple: examples/simple.o libag.so 142 | @echo " LD $@" 143 | $(Q)$(CC) $< -o $@ libag.so -Wl,-rpath,$(CURDIR) 144 | examples/init_config: examples/init_config.o libag.so 145 | @echo " LD $@" 146 | $(Q)$(CC) $< -o $@ libag.so -Wl,-rpath,$(CURDIR) 147 | 148 | # Bindings 149 | bindings: python-binding node-binding 150 | python-binding: $(PYBIND)/_libag.so 151 | node-binding: $(JSBIND)/build/Release/libag_wrapper.node 152 | 153 | # Python binding 154 | $(PYBIND)/_libag.so: $(PYBIND)/libag_wrap.o $(OBJ) 155 | @echo " LD $@" 156 | $(Q)$(CC) $^ $(PY_CFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ 157 | $(PYBIND)/libag_wrap.o: $(PYBIND)/libag_wrap.c 158 | @echo " CC $@" 159 | $(Q)$(CC) $^ $(PY_CFLAGS) $(PY_INC) -I $(CURDIR) -c -o $@ 160 | $(PYBIND)/libag_wrap.c: $(PYBIND)/libag.i 161 | @echo " SWIG $@" 162 | $(Q)$(SWIG) -python -o $(PYBIND)/libag_wrap.c $(PYBIND)/libag.i 163 | 164 | # Node binding 165 | $(JSBIND)/build/Release/libag_wrapper.node: $(JSBIND)/libag_node.c libag.so 166 | @echo " CC $@" 167 | $(Q)cd $(JSBIND) && cmake-js --CDCMAKE_PREFIX_PATH=$(PREFIX) 168 | 169 | # Clean 170 | clean: 171 | @echo " CLEAN " 172 | $(Q)rm -f $(AG_SRC)/*.o 173 | $(Q)rm -f $(CURDIR)/*.o 174 | $(Q)rm -f $(CURDIR)/libag.so 175 | $(Q)rm -f $(CURDIR)/examples/*.o 176 | $(Q)rm -f $(CURDIR)/examples/simple 177 | $(Q)rm -f $(CURDIR)/examples/init_config 178 | $(Q)rm -f $(PYBIND)/*.o 179 | $(Q)rm -f $(PYBIND)/*.py 180 | $(Q)rm -f $(PYBIND)/*.pyc 181 | $(Q)rm -f $(PYBIND)/*.so 182 | $(Q)rm -f $(PYBIND)/*.d 183 | $(Q)rm -f $(PYBIND)/libag_wrap.c 184 | $(Q)rm -rf $(PYBIND)/__pycache__ 185 | $(Q)rm -f $(DEP) 186 | 187 | # Our dependencies =) 188 | -include $(DEP) 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libag - The Silver Searcher _Library_ 📚 2 | [![License: Apache v2](https://img.shields.io/badge/license-Apache%20v2-orange)](https://opensource.org/licenses/Apache-2.0) 3 | 4 | libag - The famous The Silver Searcher, but library 5 | 6 | ## Introduction 7 | A few weeks ago, a friend asked me if I knew any tool for recursive regular 8 | expression search in text and binary files. Ag immediately came to my mind, 9 | but unfortunately, [ag(1)](https://github.com/ggreer/the_silver_searcher) is a 10 | program, not a library. 11 | 12 | While not impossible, parsing the Ag output would be a bit of a headache, plus 13 | spawning a new process for every search sounds tedious. Similar tools like 14 | [ripgrep(1)](https://github.com/BurntSushi/ripgrep) can output in JSON format, 15 | which certainly makes it a lot easier, but we're still talking about spawning 16 | processes and parsing outputs. 17 | 18 | That's how libag was born. Libag allows you to use the ag search engine (and its 19 | facilities), but in the right way (or nearly so). 20 | 21 | ## Usage & Features 22 | Libag is intended to be as simple as possible and therefore divides the search 23 | process into three simple steps: 24 | 1. Initialize all internal ag structures 25 | (via `ag_init()`) 26 | 27 | 2. Perform as many searches as you like (via `ag_search()`). 28 | 29 | 3. Clean up the resources (via `ag_finish()`). 30 | 31 | Custom search settings are done via `ag_init_config()` and `ag_set_config()`. 32 | The results are a list of struct ag_result*, which contains the file, a list of 33 | matches (containing the match and file offsets corresponding to the match), and 34 | flags. 35 | 36 | ### A minimal example: 37 | (Complete examples can be found in 38 | [examples/](https://github.com/Theldus/libag/tree/master/examples)) 39 | ```c 40 | #include 41 | 42 | ... 43 | struct ag_result **results; 44 | size_t nresults; 45 | 46 | char *query = "foo"; 47 | char *paths[1] = {"."}; 48 | 49 | /* Initiate Ag library with default options. */ 50 | ag_init(); 51 | 52 | /* Searches for foo in the current path. */ 53 | results = ag_search(query, 1, paths, &nresults); 54 | if (!results) { 55 | printf("No result found\n"); 56 | return (1); 57 | } 58 | 59 | printf("%zu results found\\n", nresults); 60 | 61 | /* Show them on the screen, if any. */ 62 | for (size_t i = 0; i < nresults; i++) { 63 | for (size_t j = 0; j < results[i]->nmatches; j++) { 64 | printf("file: %s, match: %s\n", 65 | results[i]->file, results[i]->matches[j]->match, 66 | } 67 | } 68 | 69 | /* Free all results. */ 70 | ag_free_all_results(results, nresults); 71 | 72 | /* Release Ag resources. */ 73 | ag_finish(); 74 | ... 75 | ``` 76 | 77 | Libag intends to support all features (work in progress) of ag (or at least, 78 | those that make sense for a library). In addition, it allows detailed control 79 | over worker threads, via `ag_start_workers()` and `ag_stop_workers()` (see 80 | [docs](https://github.com/Theldus/libag#documentation) for more details). 81 | 82 | ## Bindings 83 | Libag has (experimental) bindings support to other programming languages: 84 | Python and Node.js. For more information and more detailed documentation, see 85 | [bindings/python](https://github.com/Theldus/libag/tree/master/bindings/python) 86 | and 87 | [bindings/javascript](https://github.com/Theldus/libag/tree/master/bindings/javascript). 88 | 89 | ## Building/Installing 90 | ### Dependencies 91 | Libag requires the same dependencies as ag: c99 compiler and libraries: zlib, 92 | lzma, and pcre. These libraries can be installed one by one or by your package 93 | manager. 94 | 95 | For Debian-like distributions, something like: 96 | ```bash 97 | $ sudo apt install libpcre3-dev zlib1g-dev liblzma-dev 98 | ``` 99 | (or follow the Ag recommendations 100 | [here](https://github.com/ggreer/the_silver_searcher/blob/a61f1780b64266587e7bc30f0f5f71c6cca97c0f/README.md#building-master)) 101 | 102 | ### Building from source 103 | Once the dependencies are resolved, clone the repository and build. Libag 104 | supports Makefile and CMake. Choose the one that best suits your needs: 105 | 106 | ```bash 107 | $ git clone https://github.com/Theldus/libag.git 108 | $ cd libag/ 109 | ``` 110 | 111 | #### Makefile 112 | ```bash 113 | $ make -j4 114 | 115 | # Optionally (if you want to install): 116 | $ make install # (PREFIX and DESTDIR allowed here, defaults to /usr/local/) 117 | ``` 118 | 119 | #### CMake 120 | ```bash 121 | $ mkdir build/ && cd build/ 122 | $ cmake .. -DCMAKE_BUILD_TYPE=Release 123 | $ make -j4 124 | 125 | # Optionally (if you want to install): 126 | $ make install 127 | ``` 128 | 129 | ## Documentation 130 | Detailed documentation of each available routine can be found on the 131 | [man-pages](https://github.com/Theldus/libag/tree/master/doc/man3). 132 | Also, the source code is extensively commented 133 | ([libag.h](https://github.com/Theldus/libag/blob/master/libag.h) is a 134 | must-read!). Documentation on bindings and examples can be found 135 | [here](https://github.com/Theldus/libag/tree/master/bindings/python). 136 | 137 | Complete examples are also available in the 138 | [examples/](https://github.com/Theldus/libag/tree/master/examples) folder 139 | and are automatically built together with the library for your 140 | convenience ;-). 141 | 142 | ## Contributing 143 | Libag is always open to the community and willing to accept contributions, 144 | whether with issues, documentation, testing, new features, bug fixes, typos, 145 | etc. Welcome aboard. 146 | 147 | ## License 148 | Libag is licensed under Apache v2 license. 149 | -------------------------------------------------------------------------------- /ag_src/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /ag_src/config.h: -------------------------------------------------------------------------------- 1 | /* src/config.h. Generated from config.h.in by configure. */ 2 | /* src/config.h.in. Generated from configure.ac by autoheader. */ 3 | 4 | /* Have dirent struct member d_namlen */ 5 | /* #undef HAVE_DIRENT_DNAMLEN */ 6 | 7 | /* Have dirent struct member d_type */ 8 | #define HAVE_DIRENT_DTYPE /**/ 9 | 10 | /* Define to 1 if you have the header file. */ 11 | #define HAVE_ERR_H 1 12 | 13 | /* Define to 1 if you have the `fgetln' function. */ 14 | /* #undef HAVE_FGETLN */ 15 | 16 | /* Define to 1 if you have the `fopencookie' function. */ 17 | #define HAVE_FOPENCOOKIE 1 18 | 19 | /* Define to 1 if you have the `getline' function. */ 20 | #define HAVE_GETLINE 1 21 | 22 | /* Define to 1 if you have the header file. */ 23 | #define HAVE_INTTYPES_H 1 24 | 25 | /* Define to 1 if you have the `shlwapi' library (-lshlwapi). */ 26 | /* #undef HAVE_LIBSHLWAPI */ 27 | 28 | /* Define to 1 if you have the header file. */ 29 | #define HAVE_LZMA_H 1 30 | 31 | /* Define to 1 if you have the `madvise' function. */ 32 | #define HAVE_MADVISE 1 33 | 34 | /* Define to 1 if you have the header file. */ 35 | #define HAVE_MEMORY_H 1 36 | 37 | /* Define to 1 if you have the `pledge' function. */ 38 | /* #undef HAVE_PLEDGE */ 39 | 40 | /* Define to 1 if you have the `posix_fadvise' function. */ 41 | #define HAVE_POSIX_FADVISE 1 42 | 43 | /* Define to 1 if you have the header file. */ 44 | #define HAVE_PTHREAD_H 1 45 | 46 | /* Have PTHREAD_PRIO_INHERIT. */ 47 | #define HAVE_PTHREAD_PRIO_INHERIT 1 48 | 49 | /* Define to 1 if you have the `pthread_setaffinity_np' function. */ 50 | #define HAVE_PTHREAD_SETAFFINITY_NP 1 51 | 52 | /* Define to 1 if you have the `realpath' function. */ 53 | #define HAVE_REALPATH 1 54 | 55 | /* Define to 1 if you have the header file. */ 56 | #define HAVE_STDINT_H 1 57 | 58 | /* Define to 1 if you have the header file. */ 59 | #define HAVE_STDLIB_H 1 60 | 61 | /* Define to 1 if you have the header file. */ 62 | #define HAVE_STRINGS_H 1 63 | 64 | /* Define to 1 if you have the header file. */ 65 | #define HAVE_STRING_H 1 66 | 67 | /* Define to 1 if you have the `strlcpy' function. */ 68 | /* #undef HAVE_STRLCPY */ 69 | 70 | /* Define to 1 if you have the `strndup' function. */ 71 | #define HAVE_STRNDUP 1 72 | 73 | /* Define to 1 if you have the header file. */ 74 | /* #undef HAVE_SYS_CPUSET_H */ 75 | 76 | /* Define to 1 if you have the header file. */ 77 | #define HAVE_SYS_STAT_H 1 78 | 79 | /* Define to 1 if you have the header file. */ 80 | #define HAVE_SYS_TYPES_H 1 81 | 82 | /* Define to 1 if you have the header file. */ 83 | #define HAVE_UNISTD_H 1 84 | 85 | /* Define to 1 if you have the `vasprintf' function. */ 86 | #define HAVE_VASPRINTF 1 87 | 88 | /* Define to 1 if you have the header file. */ 89 | #define HAVE_ZLIB_H 1 90 | 91 | /* Define to the address where bug reports for this package should be sent. */ 92 | #define PACKAGE_BUGREPORT "https://github.com/ggreer/the_silver_searcher/issues" 93 | 94 | /* Define to the full name of this package. */ 95 | #define PACKAGE_NAME "the_silver_searcher" 96 | 97 | /* Define to the full name and version of this package. */ 98 | #define PACKAGE_STRING "the_silver_searcher 2.2.0" 99 | 100 | /* Define to the one symbol short name of this package. */ 101 | #define PACKAGE_TARNAME "the_silver_searcher" 102 | 103 | /* Define to the home page for this package. */ 104 | #define PACKAGE_URL "https://github.com/ggreer/the_silver_searcher" 105 | 106 | /* Define to the version of this package. */ 107 | #define PACKAGE_VERSION "2.2.0" 108 | 109 | /* Define to necessary symbol if this constant uses a non-standard name on 110 | your system. */ 111 | /* #undef PTHREAD_CREATE_JOINABLE */ 112 | 113 | /* Define to 1 if you have the ANSI C header files. */ 114 | #define STDC_HEADERS 1 115 | 116 | /* Use CPU_SET macros */ 117 | #define USE_CPU_SET /**/ 118 | 119 | /* Use PCRE JIT */ 120 | #define USE_PCRE_JIT /**/ 121 | -------------------------------------------------------------------------------- /ag_src/config.h.in: -------------------------------------------------------------------------------- 1 | /* src/config.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* Have dirent struct member d_namlen */ 4 | #undef HAVE_DIRENT_DNAMLEN 5 | 6 | /* Have dirent struct member d_type */ 7 | #undef HAVE_DIRENT_DTYPE 8 | 9 | /* Define to 1 if you have the header file. */ 10 | #undef HAVE_ERR_H 11 | 12 | /* Define to 1 if you have the `fgetln' function. */ 13 | #undef HAVE_FGETLN 14 | 15 | /* Define to 1 if you have the `fopencookie' function. */ 16 | #undef HAVE_FOPENCOOKIE 17 | 18 | /* Define to 1 if you have the `getline' function. */ 19 | #undef HAVE_GETLINE 20 | 21 | /* Define to 1 if you have the header file. */ 22 | #undef HAVE_INTTYPES_H 23 | 24 | /* Define to 1 if you have the `shlwapi' library (-lshlwapi). */ 25 | #undef HAVE_LIBSHLWAPI 26 | 27 | /* Define to 1 if you have the header file. */ 28 | #undef HAVE_LZMA_H 29 | 30 | /* Define to 1 if you have the `madvise' function. */ 31 | #undef HAVE_MADVISE 32 | 33 | /* Define to 1 if you have the header file. */ 34 | #undef HAVE_MEMORY_H 35 | 36 | /* Define to 1 if you have the `pledge' function. */ 37 | #undef HAVE_PLEDGE 38 | 39 | /* Define to 1 if you have the `posix_fadvise' function. */ 40 | #undef HAVE_POSIX_FADVISE 41 | 42 | /* Define to 1 if you have the header file. */ 43 | #undef HAVE_PTHREAD_H 44 | 45 | /* Have PTHREAD_PRIO_INHERIT. */ 46 | #undef HAVE_PTHREAD_PRIO_INHERIT 47 | 48 | /* Define to 1 if you have the `pthread_setaffinity_np' function. */ 49 | #undef HAVE_PTHREAD_SETAFFINITY_NP 50 | 51 | /* Define to 1 if you have the `realpath' function. */ 52 | #undef HAVE_REALPATH 53 | 54 | /* Define to 1 if you have the header file. */ 55 | #undef HAVE_STDINT_H 56 | 57 | /* Define to 1 if you have the header file. */ 58 | #undef HAVE_STDLIB_H 59 | 60 | /* Define to 1 if you have the header file. */ 61 | #undef HAVE_STRINGS_H 62 | 63 | /* Define to 1 if you have the header file. */ 64 | #undef HAVE_STRING_H 65 | 66 | /* Define to 1 if you have the `strlcpy' function. */ 67 | #undef HAVE_STRLCPY 68 | 69 | /* Define to 1 if you have the `strndup' function. */ 70 | #undef HAVE_STRNDUP 71 | 72 | /* Define to 1 if you have the header file. */ 73 | #undef HAVE_SYS_CPUSET_H 74 | 75 | /* Define to 1 if you have the header file. */ 76 | #undef HAVE_SYS_STAT_H 77 | 78 | /* Define to 1 if you have the header file. */ 79 | #undef HAVE_SYS_TYPES_H 80 | 81 | /* Define to 1 if you have the header file. */ 82 | #undef HAVE_UNISTD_H 83 | 84 | /* Define to 1 if you have the `vasprintf' function. */ 85 | #undef HAVE_VASPRINTF 86 | 87 | /* Define to 1 if you have the header file. */ 88 | #undef HAVE_ZLIB_H 89 | 90 | /* Define to the address where bug reports for this package should be sent. */ 91 | #undef PACKAGE_BUGREPORT 92 | 93 | /* Define to the full name of this package. */ 94 | #undef PACKAGE_NAME 95 | 96 | /* Define to the full name and version of this package. */ 97 | #undef PACKAGE_STRING 98 | 99 | /* Define to the one symbol short name of this package. */ 100 | #undef PACKAGE_TARNAME 101 | 102 | /* Define to the home page for this package. */ 103 | #undef PACKAGE_URL 104 | 105 | /* Define to the version of this package. */ 106 | #undef PACKAGE_VERSION 107 | 108 | /* Define to necessary symbol if this constant uses a non-standard name on 109 | your system. */ 110 | #undef PTHREAD_CREATE_JOINABLE 111 | 112 | /* Define to 1 if you have the ANSI C header files. */ 113 | #undef STDC_HEADERS 114 | 115 | /* Use CPU_SET macros */ 116 | #undef USE_CPU_SET 117 | 118 | /* Use PCRE JIT */ 119 | #undef USE_PCRE_JIT 120 | -------------------------------------------------------------------------------- /ag_src/decompress.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "decompress.h" 5 | 6 | #ifdef HAVE_LZMA_H 7 | #include 8 | 9 | /* http://tukaani.org/xz/xz-file-format.txt */ 10 | const uint8_t XZ_HEADER_MAGIC[6] = { 0xFD, '7', 'z', 'X', 'Z', 0x00 }; 11 | const uint8_t LZMA_HEADER_SOMETIMES[3] = { 0x5D, 0x00, 0x00 }; 12 | #endif 13 | 14 | 15 | #ifdef HAVE_ZLIB_H 16 | #define ZLIB_CONST 1 17 | #include 18 | 19 | /* Code in decompress_zlib from 20 | * 21 | * https://raw.github.com/madler/zlib/master/examples/zpipe.c 22 | * 23 | * zpipe.c: example of proper use of zlib's inflate() and deflate() 24 | * Not copyrighted -- provided to the public domain 25 | * Version 1.4 11 December 2005 Mark Adler 26 | */ 27 | static void *decompress_zlib(const void *buf, const int buf_len, 28 | const char *dir_full_path, int *new_buf_len) { 29 | int ret = 0; 30 | unsigned char *result = NULL; 31 | size_t result_size = 0; 32 | size_t pagesize = 0; 33 | z_stream stream; 34 | 35 | log_debug("Decompressing zlib file %s", dir_full_path); 36 | 37 | /* allocate inflate state */ 38 | stream.zalloc = Z_NULL; 39 | stream.zfree = Z_NULL; 40 | stream.opaque = Z_NULL; 41 | stream.avail_in = 0; 42 | stream.next_in = Z_NULL; 43 | 44 | /* Add 32 to allow zlib and gzip format detection */ 45 | if (inflateInit2(&stream, 32 + 15) != Z_OK) { 46 | log_err("Unable to initialize zlib: %s", stream.msg); 47 | goto error_out; 48 | } 49 | 50 | stream.avail_in = buf_len; 51 | /* Explicitly cast away the const-ness of buf */ 52 | stream.next_in = (Bytef *)buf; 53 | 54 | pagesize = getpagesize(); 55 | result_size = ((buf_len + pagesize - 1) & ~(pagesize - 1)); 56 | do { 57 | do { 58 | unsigned char *tmp_result = result; 59 | /* Double the buffer size and realloc */ 60 | result_size *= 2; 61 | result = (unsigned char *)realloc(result, result_size * sizeof(unsigned char)); 62 | if (result == NULL) { 63 | free(tmp_result); 64 | log_err("Unable to allocate %d bytes to decompress file %s", result_size * sizeof(unsigned char), dir_full_path); 65 | inflateEnd(&stream); 66 | goto error_out; 67 | } 68 | 69 | stream.avail_out = result_size / 2; 70 | stream.next_out = &result[stream.total_out]; 71 | ret = inflate(&stream, Z_SYNC_FLUSH); 72 | log_debug("inflate ret = %d", ret); 73 | switch (ret) { 74 | case Z_STREAM_ERROR: { 75 | log_err("Found stream error while decompressing zlib stream: %s", stream.msg); 76 | inflateEnd(&stream); 77 | goto error_out; 78 | } 79 | case Z_NEED_DICT: 80 | case Z_DATA_ERROR: 81 | case Z_MEM_ERROR: { 82 | log_err("Found mem/data error while decompressing zlib stream: %s", stream.msg); 83 | inflateEnd(&stream); 84 | goto error_out; 85 | } 86 | } 87 | } while (stream.avail_out == 0); 88 | } while (ret == Z_OK); 89 | 90 | *new_buf_len = stream.total_out; 91 | inflateEnd(&stream); 92 | 93 | if (ret == Z_STREAM_END) { 94 | return result; 95 | } 96 | 97 | error_out: 98 | *new_buf_len = 0; 99 | return NULL; 100 | } 101 | #endif 102 | 103 | 104 | static void *decompress_lzw(const void *buf, const int buf_len, 105 | const char *dir_full_path, int *new_buf_len) { 106 | (void)buf; 107 | (void)buf_len; 108 | log_err("LZW (UNIX compress) files not yet supported: %s", dir_full_path); 109 | *new_buf_len = 0; 110 | return NULL; 111 | } 112 | 113 | 114 | static void *decompress_zip(const void *buf, const int buf_len, 115 | const char *dir_full_path, int *new_buf_len) { 116 | (void)buf; 117 | (void)buf_len; 118 | log_err("Zip files not yet supported: %s", dir_full_path); 119 | *new_buf_len = 0; 120 | return NULL; 121 | } 122 | 123 | 124 | #ifdef HAVE_LZMA_H 125 | static void *decompress_lzma(const void *buf, const int buf_len, 126 | const char *dir_full_path, int *new_buf_len) { 127 | lzma_stream stream = LZMA_STREAM_INIT; 128 | lzma_ret lzrt; 129 | unsigned char *result = NULL; 130 | size_t result_size = 0; 131 | size_t pagesize = 0; 132 | 133 | stream.avail_in = buf_len; 134 | stream.next_in = buf; 135 | 136 | lzrt = lzma_auto_decoder(&stream, -1, 0); 137 | 138 | if (lzrt != LZMA_OK) { 139 | log_err("Unable to initialize lzma_auto_decoder: %d", lzrt); 140 | goto error_out; 141 | } 142 | 143 | pagesize = getpagesize(); 144 | result_size = ((buf_len + pagesize - 1) & ~(pagesize - 1)); 145 | do { 146 | do { 147 | unsigned char *tmp_result = result; 148 | /* Double the buffer size and realloc */ 149 | result_size *= 2; 150 | result = (unsigned char *)realloc(result, result_size * sizeof(unsigned char)); 151 | if (result == NULL) { 152 | free(tmp_result); 153 | log_err("Unable to allocate %d bytes to decompress file %s", result_size * sizeof(unsigned char), dir_full_path); 154 | goto error_out; 155 | } 156 | 157 | stream.avail_out = result_size / 2; 158 | stream.next_out = &result[stream.total_out]; 159 | lzrt = lzma_code(&stream, LZMA_RUN); 160 | log_debug("lzma_code ret = %d", lzrt); 161 | switch (lzrt) { 162 | case LZMA_OK: 163 | case LZMA_STREAM_END: 164 | break; 165 | default: 166 | log_err("Found mem/data error while decompressing xz/lzma stream: %d", lzrt); 167 | goto error_out; 168 | } 169 | } while (stream.avail_out == 0); 170 | } while (lzrt == LZMA_OK); 171 | 172 | *new_buf_len = stream.total_out; 173 | 174 | if (lzrt == LZMA_STREAM_END) { 175 | lzma_end(&stream); 176 | return result; 177 | } 178 | 179 | 180 | error_out: 181 | lzma_end(&stream); 182 | *new_buf_len = 0; 183 | if (result) { 184 | free(result); 185 | } 186 | return NULL; 187 | } 188 | #endif 189 | 190 | 191 | /* This function is very hot. It's called on every file when zip is enabled. */ 192 | void *decompress(const ag_compression_type zip_type, const void *buf, const int buf_len, 193 | const char *dir_full_path, int *new_buf_len) { 194 | switch (zip_type) { 195 | #ifdef HAVE_ZLIB_H 196 | case AG_GZIP: 197 | return decompress_zlib(buf, buf_len, dir_full_path, new_buf_len); 198 | #endif 199 | case AG_COMPRESS: 200 | return decompress_lzw(buf, buf_len, dir_full_path, new_buf_len); 201 | case AG_ZIP: 202 | return decompress_zip(buf, buf_len, dir_full_path, new_buf_len); 203 | #ifdef HAVE_LZMA_H 204 | case AG_XZ: 205 | return decompress_lzma(buf, buf_len, dir_full_path, new_buf_len); 206 | #endif 207 | case AG_NO_COMPRESSION: 208 | log_err("File %s is not compressed", dir_full_path); 209 | break; 210 | default: 211 | log_err("Unsupported compression type: %d", zip_type); 212 | } 213 | 214 | *new_buf_len = 0; 215 | return NULL; 216 | } 217 | 218 | 219 | /* This function is very hot. It's called on every file. */ 220 | ag_compression_type is_zipped(const void *buf, const int buf_len) { 221 | /* Zip magic numbers 222 | * compressed file: { 0x1F, 0x9B } 223 | * http://en.wikipedia.org/wiki/Compress 224 | * 225 | * gzip file: { 0x1F, 0x8B } 226 | * http://www.gzip.org/zlib/rfc-gzip.html#file-format 227 | * 228 | * zip file: { 0x50, 0x4B, 0x03, 0x04 } 229 | * http://www.pkware.com/documents/casestudies/APPNOTE.TXT (Section 4.3) 230 | */ 231 | 232 | const unsigned char *buf_c = buf; 233 | 234 | if (buf_len == 0) 235 | return AG_NO_COMPRESSION; 236 | 237 | /* Check for gzip & compress */ 238 | if (buf_len >= 2) { 239 | if (buf_c[0] == 0x1F) { 240 | if (buf_c[1] == 0x8B) { 241 | #ifdef HAVE_ZLIB_H 242 | log_debug("Found gzip-based stream"); 243 | return AG_GZIP; 244 | #endif 245 | } else if (buf_c[1] == 0x9B) { 246 | log_debug("Found compress-based stream"); 247 | return AG_COMPRESS; 248 | } 249 | } 250 | } 251 | 252 | /* Check for zip */ 253 | if (buf_len >= 4) { 254 | if (buf_c[0] == 0x50 && buf_c[1] == 0x4B && buf_c[2] == 0x03 && buf_c[3] == 0x04) { 255 | log_debug("Found zip-based stream"); 256 | return AG_ZIP; 257 | } 258 | } 259 | 260 | #ifdef HAVE_LZMA_H 261 | if (buf_len >= 6) { 262 | if (memcmp(XZ_HEADER_MAGIC, buf_c, 6) == 0) { 263 | log_debug("Found xz based stream"); 264 | return AG_XZ; 265 | } 266 | } 267 | 268 | /* LZMA doesn't really have a header: http://www.mail-archive.com/xz-devel@tukaani.org/msg00003.html */ 269 | if (buf_len >= 3) { 270 | if (memcmp(LZMA_HEADER_SOMETIMES, buf_c, 3) == 0) { 271 | log_debug("Found lzma-based stream"); 272 | return AG_XZ; 273 | } 274 | } 275 | #endif 276 | 277 | return AG_NO_COMPRESSION; 278 | } 279 | -------------------------------------------------------------------------------- /ag_src/decompress.h: -------------------------------------------------------------------------------- 1 | #ifndef DECOMPRESS_H 2 | #define DECOMPRESS_H 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "log.h" 8 | #include "options.h" 9 | 10 | typedef enum { 11 | AG_NO_COMPRESSION, 12 | AG_GZIP, 13 | AG_COMPRESS, 14 | AG_ZIP, 15 | AG_XZ, 16 | } ag_compression_type; 17 | 18 | ag_compression_type is_zipped(const void *buf, const int buf_len); 19 | 20 | void *decompress(const ag_compression_type zip_type, const void *buf, const int buf_len, const char *dir_full_path, int *new_buf_len); 21 | 22 | #if HAVE_FOPENCOOKIE 23 | FILE *decompress_open(int fd, const char *mode, ag_compression_type ctype); 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /ag_src/ignore.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ignore.h" 10 | #include "log.h" 11 | #include "options.h" 12 | #include "scandir.h" 13 | #include "util.h" 14 | 15 | #ifdef _WIN32 16 | #include 17 | #define fnmatch(x, y, z) (!PathMatchSpec(y, x)) 18 | #else 19 | #include 20 | const int fnmatch_flags = FNM_PATHNAME; 21 | #endif 22 | 23 | ignores *root_ignores; 24 | 25 | /* TODO: build a huge-ass list of files we want to ignore by default (build cache stuff, pyc files, etc) */ 26 | 27 | const char *evil_hardcoded_ignore_files[] = { 28 | ".", 29 | "..", 30 | NULL 31 | }; 32 | 33 | /* Warning: changing the first two strings will break skip_vcs_ignores. */ 34 | const char *ignore_pattern_files[] = { 35 | ".ignore", 36 | ".gitignore", 37 | ".git/info/exclude", 38 | ".hgignore", 39 | NULL 40 | }; 41 | 42 | int is_empty(ignores *ig) { 43 | return (ig->extensions_len + ig->names_len + ig->slash_names_len + ig->regexes_len + ig->slash_regexes_len == 0); 44 | }; 45 | 46 | ignores *init_ignore(ignores *parent, const char *dirname, const size_t dirname_len) { 47 | ignores *ig = ag_malloc(sizeof(ignores)); 48 | ig->extensions = NULL; 49 | ig->extensions_len = 0; 50 | ig->names = NULL; 51 | ig->names_len = 0; 52 | ig->slash_names = NULL; 53 | ig->slash_names_len = 0; 54 | ig->regexes = NULL; 55 | ig->regexes_len = 0; 56 | ig->invert_regexes = NULL; 57 | ig->invert_regexes_len = 0; 58 | ig->slash_regexes = NULL; 59 | ig->slash_regexes_len = 0; 60 | ig->dirname = dirname; 61 | ig->dirname_len = dirname_len; 62 | 63 | if (parent && is_empty(parent) && parent->parent) { 64 | ig->parent = parent->parent; 65 | } else { 66 | ig->parent = parent; 67 | } 68 | 69 | if (parent && parent->abs_path_len > 0) { 70 | ag_asprintf(&(ig->abs_path), "%s/%s", parent->abs_path, dirname); 71 | ig->abs_path_len = parent->abs_path_len + 1 + dirname_len; 72 | } else if (dirname_len == 1 && dirname[0] == '.') { 73 | ig->abs_path = ag_malloc(sizeof(char)); 74 | ig->abs_path[0] = '\0'; 75 | ig->abs_path_len = 0; 76 | } else { 77 | ag_asprintf(&(ig->abs_path), "%s", dirname); 78 | ig->abs_path_len = dirname_len; 79 | } 80 | return ig; 81 | } 82 | 83 | void cleanup_ignore(ignores *ig) { 84 | if (ig == NULL) { 85 | return; 86 | } 87 | free_strings(ig->extensions, ig->extensions_len); 88 | free_strings(ig->names, ig->names_len); 89 | free_strings(ig->slash_names, ig->slash_names_len); 90 | free_strings(ig->regexes, ig->regexes_len); 91 | free_strings(ig->invert_regexes, ig->invert_regexes_len); 92 | free_strings(ig->slash_regexes, ig->slash_regexes_len); 93 | if (ig->abs_path) { 94 | free(ig->abs_path); 95 | } 96 | free(ig); 97 | } 98 | 99 | void add_ignore_pattern(ignores *ig, const char *pattern) { 100 | int i; 101 | int pattern_len; 102 | 103 | /* Strip off the leading dot so that matches are more likely. */ 104 | if (strncmp(pattern, "./", 2) == 0) { 105 | pattern++; 106 | } 107 | 108 | /* Kill trailing whitespace */ 109 | for (pattern_len = strlen(pattern); pattern_len > 0; pattern_len--) { 110 | if (!isspace(pattern[pattern_len - 1])) { 111 | break; 112 | } 113 | } 114 | 115 | if (pattern_len == 0) { 116 | log_debug("Pattern is empty. Not adding any ignores."); 117 | return; 118 | } 119 | 120 | char ***patterns_p; 121 | size_t *patterns_len; 122 | if (is_fnmatch(pattern)) { 123 | if (pattern[0] == '*' && pattern[1] == '.' && strchr(pattern + 2, '.') && !is_fnmatch(pattern + 2)) { 124 | patterns_p = &(ig->extensions); 125 | patterns_len = &(ig->extensions_len); 126 | pattern += 2; 127 | pattern_len -= 2; 128 | } else if (pattern[0] == '/') { 129 | patterns_p = &(ig->slash_regexes); 130 | patterns_len = &(ig->slash_regexes_len); 131 | pattern++; 132 | pattern_len--; 133 | } else if (pattern[0] == '!') { 134 | patterns_p = &(ig->invert_regexes); 135 | patterns_len = &(ig->invert_regexes_len); 136 | pattern++; 137 | pattern_len--; 138 | } else { 139 | patterns_p = &(ig->regexes); 140 | patterns_len = &(ig->regexes_len); 141 | } 142 | } else { 143 | if (pattern[0] == '/') { 144 | patterns_p = &(ig->slash_names); 145 | patterns_len = &(ig->slash_names_len); 146 | pattern++; 147 | pattern_len--; 148 | } else { 149 | patterns_p = &(ig->names); 150 | patterns_len = &(ig->names_len); 151 | } 152 | } 153 | 154 | ++*patterns_len; 155 | 156 | char **patterns; 157 | 158 | /* a balanced binary tree is best for performance, but I'm lazy */ 159 | *patterns_p = patterns = ag_realloc(*patterns_p, (*patterns_len) * sizeof(char *)); 160 | /* TODO: de-dupe these patterns */ 161 | for (i = *patterns_len - 1; i > 0; i--) { 162 | if (strcmp(pattern, patterns[i - 1]) > 0) { 163 | break; 164 | } 165 | patterns[i] = patterns[i - 1]; 166 | } 167 | patterns[i] = ag_strndup(pattern, pattern_len); 168 | log_debug("added ignore pattern %s to %s", pattern, 169 | ig == root_ignores ? "root ignores" : ig->abs_path); 170 | } 171 | 172 | /* For loading git/hg ignore patterns */ 173 | void load_ignore_patterns(ignores *ig, const char *path) { 174 | FILE *fp = NULL; 175 | fp = fopen(path, "r"); 176 | if (fp == NULL) { 177 | log_debug("Skipping ignore file %s: not readable", path); 178 | return; 179 | } 180 | log_debug("Loading ignore file %s.", path); 181 | 182 | char *line = NULL; 183 | ssize_t line_len = 0; 184 | size_t line_cap = 0; 185 | 186 | while ((line_len = getline(&line, &line_cap, fp)) > 0) { 187 | if (line_len == 0 || line[0] == '\n' || line[0] == '#') { 188 | continue; 189 | } 190 | if (line[line_len - 1] == '\n') { 191 | line[line_len - 1] = '\0'; /* kill the \n */ 192 | } 193 | add_ignore_pattern(ig, line); 194 | } 195 | 196 | free(line); 197 | fclose(fp); 198 | } 199 | 200 | static int ackmate_dir_match(const char *dir_name) { 201 | if (opts.ackmate_dir_filter == NULL) { 202 | return 0; 203 | } 204 | /* we just care about the match, not where the matches are */ 205 | return pcre_exec(opts.ackmate_dir_filter, NULL, dir_name, strlen(dir_name), 0, 0, NULL, 0); 206 | } 207 | 208 | /* This is the hottest code in Ag. 10-15% of all execution time is spent here */ 209 | static int path_ignore_search(const ignores *ig, const char *path, const char *filename) { 210 | char *temp; 211 | size_t i; 212 | int match_pos; 213 | 214 | match_pos = binary_search(filename, ig->names, 0, ig->names_len); 215 | if (match_pos >= 0) { 216 | log_debug("file %s ignored because name matches static pattern %s", filename, ig->names[match_pos]); 217 | return 1; 218 | } 219 | 220 | ag_asprintf(&temp, "%s/%s", path[0] == '.' ? path + 1 : path, filename); 221 | 222 | if (strncmp(temp, ig->abs_path, ig->abs_path_len) == 0) { 223 | char *slash_filename = temp + ig->abs_path_len; 224 | if (slash_filename[0] == '/') { 225 | slash_filename++; 226 | } 227 | match_pos = binary_search(slash_filename, ig->names, 0, ig->names_len); 228 | if (match_pos >= 0) { 229 | log_debug("file %s ignored because name matches static pattern %s", temp, ig->names[match_pos]); 230 | free(temp); 231 | return 1; 232 | } 233 | 234 | match_pos = binary_search(slash_filename, ig->slash_names, 0, ig->slash_names_len); 235 | if (match_pos >= 0) { 236 | log_debug("file %s ignored because name matches slash static pattern %s", slash_filename, ig->slash_names[match_pos]); 237 | free(temp); 238 | return 1; 239 | } 240 | 241 | for (i = 0; i < ig->names_len; i++) { 242 | char *pos = strstr(slash_filename, ig->names[i]); 243 | if (pos == slash_filename || (pos && *(pos - 1) == '/')) { 244 | pos += strlen(ig->names[i]); 245 | if (*pos == '\0' || *pos == '/') { 246 | log_debug("file %s ignored because path somewhere matches name %s", slash_filename, ig->names[i]); 247 | free(temp); 248 | return 1; 249 | } 250 | } 251 | log_debug("pattern %s doesn't match path %s", ig->names[i], slash_filename); 252 | } 253 | 254 | for (i = 0; i < ig->slash_regexes_len; i++) { 255 | if (fnmatch(ig->slash_regexes[i], slash_filename, fnmatch_flags) == 0) { 256 | log_debug("file %s ignored because name matches slash regex pattern %s", slash_filename, ig->slash_regexes[i]); 257 | free(temp); 258 | return 1; 259 | } 260 | log_debug("pattern %s doesn't match slash file %s", ig->slash_regexes[i], slash_filename); 261 | } 262 | } 263 | 264 | for (i = 0; i < ig->invert_regexes_len; i++) { 265 | if (fnmatch(ig->invert_regexes[i], filename, fnmatch_flags) == 0) { 266 | log_debug("file %s not ignored because name matches regex pattern !%s", filename, ig->invert_regexes[i]); 267 | free(temp); 268 | return 0; 269 | } 270 | log_debug("pattern !%s doesn't match file %s", ig->invert_regexes[i], filename); 271 | } 272 | 273 | for (i = 0; i < ig->regexes_len; i++) { 274 | if (fnmatch(ig->regexes[i], filename, fnmatch_flags) == 0) { 275 | log_debug("file %s ignored because name matches regex pattern %s", filename, ig->regexes[i]); 276 | free(temp); 277 | return 1; 278 | } 279 | log_debug("pattern %s doesn't match file %s", ig->regexes[i], filename); 280 | } 281 | 282 | int rv = ackmate_dir_match(temp); 283 | free(temp); 284 | return rv; 285 | } 286 | 287 | /* This function is REALLY HOT. It gets called for every file */ 288 | int filename_filter(const char *path, const struct dirent *dir, void *baton) { 289 | const char *filename = dir->d_name; 290 | if (!opts.search_hidden_files && filename[0] == '.') { 291 | return 0; 292 | } 293 | 294 | size_t i; 295 | for (i = 0; evil_hardcoded_ignore_files[i] != NULL; i++) { 296 | if (strcmp(filename, evil_hardcoded_ignore_files[i]) == 0) { 297 | return 0; 298 | } 299 | } 300 | 301 | if (!opts.follow_symlinks && is_symlink(path, dir)) { 302 | log_debug("File %s ignored becaused it's a symlink", dir->d_name); 303 | return 0; 304 | } 305 | 306 | if (is_named_pipe(path, dir)) { 307 | log_debug("%s ignored because it's a named pipe or socket", path); 308 | return 0; 309 | } 310 | 311 | if (opts.search_all_files && !opts.path_to_ignore) { 312 | return 1; 313 | } 314 | 315 | scandir_baton_t *scandir_baton = (scandir_baton_t *)baton; 316 | const char *path_start = scandir_baton->path_start; 317 | 318 | const char *extension = strchr(filename, '.'); 319 | if (extension) { 320 | if (extension[1]) { 321 | // The dot is not the last character, extension starts at the next one 322 | ++extension; 323 | } else { 324 | // No extension 325 | extension = NULL; 326 | } 327 | } 328 | 329 | #ifdef HAVE_DIRENT_DNAMLEN 330 | size_t filename_len = dir->d_namlen; 331 | #else 332 | size_t filename_len = 0; 333 | #endif 334 | 335 | if (strncmp(filename, "./", 2) == 0) { 336 | #ifndef HAVE_DIRENT_DNAMLEN 337 | filename_len = strlen(filename); 338 | #endif 339 | filename++; 340 | filename_len--; 341 | } 342 | 343 | const ignores *ig = scandir_baton->ig; 344 | 345 | while (ig != NULL) { 346 | if (extension) { 347 | int match_pos = binary_search(extension, ig->extensions, 0, ig->extensions_len); 348 | if (match_pos >= 0) { 349 | log_debug("file %s ignored because name matches extension %s", filename, ig->extensions[match_pos]); 350 | return 0; 351 | } 352 | } 353 | 354 | if (path_ignore_search(ig, path_start, filename)) { 355 | return 0; 356 | } 357 | 358 | if (is_directory(path, dir)) { 359 | #ifndef HAVE_DIRENT_DNAMLEN 360 | if (!filename_len) { 361 | filename_len = strlen(filename); 362 | } 363 | #endif 364 | if (filename[filename_len - 1] != '/') { 365 | char *temp; 366 | ag_asprintf(&temp, "%s/", filename); 367 | int rv = path_ignore_search(ig, path_start, temp); 368 | free(temp); 369 | if (rv) { 370 | return 0; 371 | } 372 | } 373 | } 374 | ig = ig->parent; 375 | } 376 | 377 | log_debug("%s not ignored", filename); 378 | return 1; 379 | } 380 | -------------------------------------------------------------------------------- /ag_src/ignore.h: -------------------------------------------------------------------------------- 1 | #ifndef IGNORE_H 2 | #define IGNORE_H 3 | 4 | #include 5 | #include 6 | 7 | struct ignores { 8 | char **extensions; /* File extensions to ignore */ 9 | size_t extensions_len; 10 | 11 | char **names; /* Non-regex ignore lines. Sorted so we can binary search them. */ 12 | size_t names_len; 13 | char **slash_names; /* Same but starts with a slash */ 14 | size_t slash_names_len; 15 | 16 | char **regexes; /* For patterns that need fnmatch */ 17 | size_t regexes_len; 18 | char **invert_regexes; /* For "!" patterns */ 19 | size_t invert_regexes_len; 20 | char **slash_regexes; 21 | size_t slash_regexes_len; 22 | 23 | const char *dirname; 24 | size_t dirname_len; 25 | char *abs_path; 26 | size_t abs_path_len; 27 | 28 | struct ignores *parent; 29 | }; 30 | typedef struct ignores ignores; 31 | 32 | extern ignores *root_ignores; 33 | 34 | extern const char *evil_hardcoded_ignore_files[]; 35 | extern const char *ignore_pattern_files[]; 36 | 37 | ignores *init_ignore(ignores *parent, const char *dirname, const size_t dirname_len); 38 | void cleanup_ignore(ignores *ig); 39 | 40 | void add_ignore_pattern(ignores *ig, const char *pattern); 41 | 42 | void load_ignore_patterns(ignores *ig, const char *path); 43 | 44 | int filename_filter(const char *path, const struct dirent *dir, void *baton); 45 | 46 | int is_empty(ignores *ig); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /ag_src/lang.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "lang.h" 5 | #include "util.h" 6 | 7 | lang_spec_t langs[] = { 8 | { "actionscript", { "as", "mxml" } }, 9 | { "ada", { "ada", "adb", "ads" } }, 10 | { "asciidoc", { "adoc", "ad", "asc", "asciidoc" } }, 11 | { "apl", { "apl" } }, 12 | { "asm", { "asm", "s" } }, 13 | { "batch", { "bat", "cmd" } }, 14 | { "bitbake", { "bb", "bbappend", "bbclass", "inc" } }, 15 | { "bro", { "bro", "bif" } }, 16 | { "cc", { "c", "h", "xs" } }, 17 | { "cfmx", { "cfc", "cfm", "cfml" } }, 18 | { "chpl", { "chpl" } }, 19 | { "clojure", { "clj", "cljs", "cljc", "cljx" } }, 20 | { "coffee", { "coffee", "cjsx" } }, 21 | { "coq", { "coq", "g", "v" } }, 22 | { "cpp", { "cpp", "cc", "C", "cxx", "m", "hpp", "hh", "h", "H", "hxx", "tpp" } }, 23 | { "crystal", { "cr", "ecr" } }, 24 | { "csharp", { "cs" } }, 25 | { "css", { "css" } }, 26 | { "cython", { "pyx", "pxd", "pxi" } }, 27 | { "delphi", { "pas", "int", "dfm", "nfm", "dof", "dpk", "dpr", "dproj", "groupproj", "bdsgroup", "bdsproj" } }, 28 | { "dlang", { "d", "di" } }, 29 | { "dot", { "dot", "gv" } }, 30 | { "dts", { "dts", "dtsi" } }, 31 | { "ebuild", { "ebuild", "eclass" } }, 32 | { "elisp", { "el" } }, 33 | { "elixir", { "ex", "eex", "exs" } }, 34 | { "elm", { "elm" } }, 35 | { "erlang", { "erl", "hrl" } }, 36 | { "factor", { "factor" } }, 37 | { "fortran", { "f", "f77", "f90", "f95", "f03", "for", "ftn", "fpp" } }, 38 | { "fsharp", { "fs", "fsi", "fsx" } }, 39 | { "gettext", { "po", "pot", "mo" } }, 40 | { "glsl", { "vert", "tesc", "tese", "geom", "frag", "comp" } }, 41 | { "go", { "go" } }, 42 | { "groovy", { "groovy", "gtmpl", "gpp", "grunit", "gradle" } }, 43 | { "haml", { "haml" } }, 44 | { "handlebars", { "hbs" } }, 45 | { "haskell", { "hs", "hsig", "lhs" } }, 46 | { "haxe", { "hx" } }, 47 | { "hh", { "h" } }, 48 | { "html", { "htm", "html", "shtml", "xhtml" } }, 49 | { "idris", { "idr", "ipkg", "lidr" } }, 50 | { "ini", { "ini" } }, 51 | { "ipython", { "ipynb" } }, 52 | { "isabelle", { "thy" } }, 53 | { "j", { "ijs" } }, 54 | { "jade", { "jade" } }, 55 | { "java", { "java", "properties" } }, 56 | { "jinja2", { "j2" } }, 57 | { "js", { "es6", "js", "jsx", "vue" } }, 58 | { "json", { "json" } }, 59 | { "jsp", { "jsp", "jspx", "jhtm", "jhtml", "jspf", "tag", "tagf" } }, 60 | { "julia", { "jl" } }, 61 | { "kotlin", { "kt" } }, 62 | { "less", { "less" } }, 63 | { "liquid", { "liquid" } }, 64 | { "lisp", { "lisp", "lsp" } }, 65 | { "log", { "log" } }, 66 | { "lua", { "lua" } }, 67 | { "m4", { "m4" } }, 68 | { "make", { "Makefiles", "mk", "mak" } }, 69 | { "mako", { "mako" } }, 70 | { "markdown", { "markdown", "mdown", "mdwn", "mkdn", "mkd", "md" } }, 71 | { "mason", { "mas", "mhtml", "mpl", "mtxt" } }, 72 | { "matlab", { "m" } }, 73 | { "mathematica", { "m", "wl" } }, 74 | { "md", { "markdown", "mdown", "mdwn", "mkdn", "mkd", "md" } }, 75 | { "mercury", { "m", "moo" } }, 76 | { "naccess", { "asa", "rsa" } }, 77 | { "nim", { "nim" } }, 78 | { "nix", { "nix" } }, 79 | { "objc", { "m", "h" } }, 80 | { "objcpp", { "mm", "h" } }, 81 | { "ocaml", { "ml", "mli", "mll", "mly" } }, 82 | { "octave", { "m" } }, 83 | { "org", { "org" } }, 84 | { "parrot", { "pir", "pasm", "pmc", "ops", "pod", "pg", "tg" } }, 85 | { "pdb", { "pdb" } }, 86 | { "perl", { "pl", "pm", "pm6", "pod", "t" } }, 87 | { "php", { "php", "phpt", "php3", "php4", "php5", "phtml" } }, 88 | { "pike", { "pike", "pmod" } }, 89 | { "plist", { "plist" } }, 90 | { "plone", { "pt", "cpt", "metadata", "cpy", "py", "xml", "zcml" } }, 91 | { "proto", { "proto" } }, 92 | { "pug", { "pug" } }, 93 | { "puppet", { "pp" } }, 94 | { "python", { "py" } }, 95 | { "qml", { "qml" } }, 96 | { "racket", { "rkt", "ss", "scm" } }, 97 | { "rake", { "Rakefile" } }, 98 | { "restructuredtext", { "rst" } }, 99 | { "rs", { "rs" } }, 100 | { "r", { "r", "R", "Rmd", "Rnw", "Rtex", "Rrst" } }, 101 | { "rdoc", { "rdoc" } }, 102 | { "ruby", { "rb", "rhtml", "rjs", "rxml", "erb", "rake", "spec" } }, 103 | { "rust", { "rs" } }, 104 | { "salt", { "sls" } }, 105 | { "sass", { "sass", "scss" } }, 106 | { "scala", { "scala" } }, 107 | { "scheme", { "scm", "ss" } }, 108 | { "shell", { "sh", "bash", "csh", "tcsh", "ksh", "zsh", "fish" } }, 109 | { "smalltalk", { "st" } }, 110 | { "sml", { "sml", "fun", "mlb", "sig" } }, 111 | { "sql", { "sql", "ctl" } }, 112 | { "stata", { "do", "ado" } }, 113 | { "stylus", { "styl" } }, 114 | { "swift", { "swift" } }, 115 | { "tcl", { "tcl", "itcl", "itk" } }, 116 | { "terraform", { "tf", "tfvars" } }, 117 | { "tex", { "tex", "cls", "sty" } }, 118 | { "thrift", { "thrift" } }, 119 | { "tla", { "tla" } }, 120 | { "tt", { "tt", "tt2", "ttml" } }, 121 | { "toml", { "toml" } }, 122 | { "ts", { "ts", "tsx" } }, 123 | { "twig", { "twig" } }, 124 | { "vala", { "vala", "vapi" } }, 125 | { "vb", { "bas", "cls", "frm", "ctl", "vb", "resx" } }, 126 | { "velocity", { "vm", "vtl", "vsl" } }, 127 | { "verilog", { "v", "vh", "sv" } }, 128 | { "vhdl", { "vhd", "vhdl" } }, 129 | { "vim", { "vim" } }, 130 | { "wix", { "wxi", "wxs" } }, 131 | { "wsdl", { "wsdl" } }, 132 | { "wadl", { "wadl" } }, 133 | { "xml", { "xml", "dtd", "xsl", "xslt", "ent", "tld", "plist" } }, 134 | { "yaml", { "yaml", "yml" } } 135 | }; 136 | 137 | size_t get_lang_count() { 138 | return sizeof(langs) / sizeof(lang_spec_t); 139 | } 140 | 141 | char *make_lang_regex(char *ext_array, size_t num_exts) { 142 | int regex_capacity = 100; 143 | char *regex = ag_malloc(regex_capacity); 144 | int regex_length = 3; 145 | int subsequent = 0; 146 | char *extension; 147 | size_t i; 148 | 149 | strcpy(regex, "\\.("); 150 | 151 | for (i = 0; i < num_exts; ++i) { 152 | extension = ext_array + i * SINGLE_EXT_LEN; 153 | int extension_length = strlen(extension); 154 | while (regex_length + extension_length + 3 + subsequent > regex_capacity) { 155 | regex_capacity *= 2; 156 | regex = ag_realloc(regex, regex_capacity); 157 | } 158 | if (subsequent) { 159 | regex[regex_length++] = '|'; 160 | } else { 161 | subsequent = 1; 162 | } 163 | strcpy(regex + regex_length, extension); 164 | regex_length += extension_length; 165 | } 166 | 167 | regex[regex_length++] = ')'; 168 | regex[regex_length++] = '$'; 169 | regex[regex_length++] = 0; 170 | return regex; 171 | } 172 | 173 | size_t combine_file_extensions(size_t *extension_index, size_t len, char **exts) { 174 | /* Keep it fixed as 100 for the reason that if you have more than 100 175 | * file types to search, you'd better search all the files. 176 | * */ 177 | size_t ext_capacity = 100; 178 | (*exts) = (char *)ag_malloc(ext_capacity * SINGLE_EXT_LEN); 179 | memset((*exts), 0, ext_capacity * SINGLE_EXT_LEN); 180 | size_t num_of_extensions = 0; 181 | 182 | size_t i; 183 | for (i = 0; i < len; ++i) { 184 | size_t j = 0; 185 | const char *ext = langs[extension_index[i]].extensions[j]; 186 | do { 187 | if (num_of_extensions == ext_capacity) { 188 | break; 189 | } 190 | char *pos = (*exts) + num_of_extensions * SINGLE_EXT_LEN; 191 | strncpy(pos, ext, strlen(ext)); 192 | ++num_of_extensions; 193 | ext = langs[extension_index[i]].extensions[++j]; 194 | } while (ext); 195 | } 196 | 197 | return num_of_extensions; 198 | } 199 | -------------------------------------------------------------------------------- /ag_src/lang.h: -------------------------------------------------------------------------------- 1 | #ifndef LANG_H 2 | #define LANG_H 3 | 4 | #define MAX_EXTENSIONS 12 5 | #define SINGLE_EXT_LEN 20 6 | 7 | typedef struct { 8 | const char *name; 9 | const char *extensions[MAX_EXTENSIONS]; 10 | } lang_spec_t; 11 | 12 | extern lang_spec_t langs[]; 13 | 14 | /** 15 | Return the language count. 16 | */ 17 | size_t get_lang_count(void); 18 | 19 | /** 20 | Convert a NULL-terminated array of language extensions 21 | into a regular expression of the form \.(extension1|extension2...)$ 22 | 23 | Caller is responsible for freeing the returned string. 24 | */ 25 | char *make_lang_regex(char *ext_array, size_t num_exts); 26 | 27 | 28 | /** 29 | Combine multiple file type extensions into one array. 30 | 31 | The combined result is returned through *exts*; 32 | *exts* is one-dimension array, which can contain up to 100 extensions; 33 | The number of extensions that *exts* actually contain is returned. 34 | */ 35 | size_t combine_file_extensions(size_t *extension_index, size_t len, char **exts); 36 | #endif 37 | -------------------------------------------------------------------------------- /ag_src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "log.h" 5 | #include "util.h" 6 | 7 | pthread_mutex_t print_mtx; 8 | 9 | static enum log_level log_threshold = LOG_LEVEL_ERR; 10 | 11 | void set_log_level(enum log_level threshold) { 12 | log_threshold = threshold; 13 | } 14 | 15 | void log_debug(const char *fmt, ...) { 16 | va_list args; 17 | va_start(args, fmt); 18 | vplog(LOG_LEVEL_DEBUG, fmt, args); 19 | va_end(args); 20 | } 21 | 22 | void log_msg(const char *fmt, ...) { 23 | va_list args; 24 | va_start(args, fmt); 25 | vplog(LOG_LEVEL_MSG, fmt, args); 26 | va_end(args); 27 | } 28 | 29 | void log_warn(const char *fmt, ...) { 30 | va_list args; 31 | va_start(args, fmt); 32 | vplog(LOG_LEVEL_WARN, fmt, args); 33 | va_end(args); 34 | } 35 | 36 | void log_err(const char *fmt, ...) { 37 | va_list args; 38 | va_start(args, fmt); 39 | vplog(LOG_LEVEL_ERR, fmt, args); 40 | va_end(args); 41 | } 42 | 43 | void vplog(const unsigned int level, const char *fmt, va_list args) { 44 | if (level < log_threshold) { 45 | return; 46 | } 47 | 48 | pthread_mutex_lock(&print_mtx); 49 | FILE *stream = out_fd; 50 | 51 | switch (level) { 52 | case LOG_LEVEL_DEBUG: 53 | fprintf(stream, "DEBUG: "); 54 | break; 55 | case LOG_LEVEL_MSG: 56 | fprintf(stream, "MSG: "); 57 | break; 58 | case LOG_LEVEL_WARN: 59 | fprintf(stream, "WARN: "); 60 | break; 61 | case LOG_LEVEL_ERR: 62 | stream = stderr; 63 | fprintf(stream, "ERR: "); 64 | break; 65 | } 66 | 67 | vfprintf(stream, fmt, args); 68 | fprintf(stream, "\n"); 69 | pthread_mutex_unlock(&print_mtx); 70 | } 71 | 72 | void plog(const unsigned int level, const char *fmt, ...) { 73 | va_list args; 74 | va_start(args, fmt); 75 | vplog(level, fmt, args); 76 | va_end(args); 77 | } 78 | -------------------------------------------------------------------------------- /ag_src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | 6 | #include "config.h" 7 | 8 | #ifdef HAVE_PTHREAD_H 9 | #include 10 | #endif 11 | 12 | extern pthread_mutex_t print_mtx; 13 | 14 | enum log_level { 15 | LOG_LEVEL_DEBUG = 10, 16 | LOG_LEVEL_MSG = 20, 17 | LOG_LEVEL_WARN = 30, 18 | LOG_LEVEL_ERR = 40, 19 | LOG_LEVEL_NONE = 100 20 | }; 21 | 22 | void set_log_level(enum log_level threshold); 23 | 24 | void log_debug(const char *fmt, ...); 25 | void log_msg(const char *fmt, ...); 26 | void log_warn(const char *fmt, ...); 27 | void log_err(const char *fmt, ...); 28 | 29 | void vplog(const unsigned int level, const char *fmt, va_list args); 30 | void plog(const unsigned int level, const char *fmt, ...); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /ag_src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #ifdef _WIN32 9 | #include 10 | #endif 11 | 12 | #include "config.h" 13 | 14 | #ifdef HAVE_SYS_CPUSET_H 15 | #include 16 | #endif 17 | 18 | #ifdef HAVE_PTHREAD_H 19 | #include 20 | #endif 21 | 22 | #if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(__FreeBSD__) 23 | #include 24 | #endif 25 | 26 | #include "log.h" 27 | #include "options.h" 28 | #include "search.h" 29 | #include "util.h" 30 | 31 | typedef struct { 32 | pthread_t thread; 33 | int id; 34 | } worker_t; 35 | 36 | int main(int argc, char **argv) { 37 | char **base_paths = NULL; 38 | char **paths = NULL; 39 | int i; 40 | int pcre_opts = PCRE_MULTILINE; 41 | int study_opts = 0; 42 | worker_t *workers = NULL; 43 | int workers_len; 44 | int num_cores; 45 | 46 | #ifdef HAVE_PLEDGE 47 | if (pledge("stdio rpath proc exec", NULL) == -1) { 48 | die("pledge: %s", strerror(errno)); 49 | } 50 | #endif 51 | 52 | set_log_level(LOG_LEVEL_WARN); 53 | 54 | work_queue = NULL; 55 | work_queue_tail = NULL; 56 | root_ignores = init_ignore(NULL, "", 0); 57 | out_fd = stdout; 58 | 59 | parse_options(argc, argv, &base_paths, &paths); 60 | log_debug("PCRE Version: %s", pcre_version()); 61 | if (opts.stats) { 62 | memset(&stats, 0, sizeof(stats)); 63 | gettimeofday(&(stats.time_start), NULL); 64 | } 65 | 66 | #ifdef USE_PCRE_JIT 67 | int has_jit = 0; 68 | pcre_config(PCRE_CONFIG_JIT, &has_jit); 69 | if (has_jit) { 70 | study_opts |= PCRE_STUDY_JIT_COMPILE; 71 | } 72 | #endif 73 | 74 | #ifdef _WIN32 75 | { 76 | SYSTEM_INFO si; 77 | GetSystemInfo(&si); 78 | num_cores = si.dwNumberOfProcessors; 79 | } 80 | #else 81 | num_cores = (int)sysconf(_SC_NPROCESSORS_ONLN); 82 | #endif 83 | 84 | workers_len = num_cores < 8 ? num_cores : 8; 85 | if (opts.literal) { 86 | workers_len--; 87 | } 88 | if (opts.workers) { 89 | workers_len = opts.workers; 90 | } 91 | if (workers_len < 1) { 92 | workers_len = 1; 93 | } 94 | 95 | log_debug("Using %i workers", workers_len); 96 | done_adding_files = FALSE; 97 | workers = ag_calloc(workers_len, sizeof(worker_t)); 98 | if (pthread_cond_init(&files_ready, NULL)) { 99 | die("pthread_cond_init failed!"); 100 | } 101 | if (pthread_mutex_init(&print_mtx, NULL)) { 102 | die("pthread_mutex_init failed!"); 103 | } 104 | if (opts.stats && pthread_mutex_init(&stats_mtx, NULL)) { 105 | die("pthread_mutex_init failed!"); 106 | } 107 | if (pthread_mutex_init(&work_queue_mtx, NULL)) { 108 | die("pthread_mutex_init failed!"); 109 | } 110 | 111 | if (opts.casing == CASE_SMART) { 112 | opts.casing = is_lowercase(opts.query) ? CASE_INSENSITIVE : CASE_SENSITIVE; 113 | } 114 | 115 | if (opts.literal) { 116 | if (opts.casing == CASE_INSENSITIVE) { 117 | /* Search routine needs the query to be lowercase */ 118 | char *c = opts.query; 119 | for (; *c != '\0'; ++c) { 120 | *c = (char)tolower(*c); 121 | } 122 | } 123 | generate_alpha_skip(opts.query, opts.query_len, alpha_skip_lookup, opts.casing == CASE_SENSITIVE); 124 | find_skip_lookup = NULL; 125 | generate_find_skip(opts.query, opts.query_len, &find_skip_lookup, opts.casing == CASE_SENSITIVE); 126 | generate_hash(opts.query, opts.query_len, h_table, opts.casing == CASE_SENSITIVE); 127 | if (opts.word_regexp) { 128 | init_wordchar_table(); 129 | opts.literal_starts_wordchar = is_wordchar(opts.query[0]); 130 | opts.literal_ends_wordchar = is_wordchar(opts.query[opts.query_len - 1]); 131 | } 132 | } else { 133 | if (opts.casing == CASE_INSENSITIVE) { 134 | pcre_opts |= PCRE_CASELESS; 135 | } 136 | if (opts.word_regexp) { 137 | char *word_regexp_query; 138 | ag_asprintf(&word_regexp_query, "\\b(?:%s)\\b", opts.query); 139 | free(opts.query); 140 | opts.query = word_regexp_query; 141 | opts.query_len = strlen(opts.query); 142 | } 143 | compile_study(&opts.re, &opts.re_extra, opts.query, pcre_opts, study_opts); 144 | } 145 | 146 | if (opts.search_stream) { 147 | search_stream(0, stdin, ""); 148 | } else { 149 | for (i = 0; i < workers_len; i++) { 150 | workers[i].id = i; 151 | int rv = pthread_create(&(workers[i].thread), NULL, &search_file_worker, &(workers[i].id)); 152 | if (rv != 0) { 153 | die("Error in pthread_create(): %s", strerror(rv)); 154 | } 155 | #if defined(HAVE_PTHREAD_SETAFFINITY_NP) && (defined(USE_CPU_SET) || defined(HAVE_SYS_CPUSET_H)) 156 | if (opts.use_thread_affinity) { 157 | #ifdef __linux__ 158 | cpu_set_t cpu_set; 159 | #elif __FreeBSD__ 160 | cpuset_t cpu_set; 161 | #endif 162 | CPU_ZERO(&cpu_set); 163 | CPU_SET(i % num_cores, &cpu_set); 164 | rv = pthread_setaffinity_np(workers[i].thread, sizeof(cpu_set), &cpu_set); 165 | if (rv) { 166 | log_err("Error in pthread_setaffinity_np(): %s", strerror(rv)); 167 | log_err("Performance may be affected. Use --noaffinity to suppress this message."); 168 | } else { 169 | log_debug("Thread %i set to CPU %i", i, i); 170 | } 171 | } else { 172 | log_debug("Thread affinity disabled."); 173 | } 174 | #else 175 | log_debug("No CPU affinity support."); 176 | #endif 177 | } 178 | 179 | #ifdef HAVE_PLEDGE 180 | if (pledge("stdio rpath", NULL) == -1) { 181 | die("pledge: %s", strerror(errno)); 182 | } 183 | #endif 184 | for (i = 0; paths[i] != NULL; i++) { 185 | log_debug("searching path %s for %s", paths[i], opts.query); 186 | symhash = NULL; 187 | ignores *ig = init_ignore(root_ignores, "", 0); 188 | struct stat s = { .st_dev = 0 }; 189 | #ifndef _WIN32 190 | /* The device is ignored if opts.one_dev is false, so it's fine 191 | * to leave it at the default 0 192 | */ 193 | if (opts.one_dev && lstat(paths[i], &s) == -1) { 194 | log_err("Failed to get device information for path %s. Skipping...", paths[i]); 195 | } 196 | #endif 197 | search_dir(ig, base_paths[i], paths[i], 0, s.st_dev); 198 | cleanup_ignore(ig); 199 | } 200 | pthread_mutex_lock(&work_queue_mtx); 201 | done_adding_files = TRUE; 202 | pthread_cond_broadcast(&files_ready); 203 | pthread_mutex_unlock(&work_queue_mtx); 204 | for (i = 0; i < workers_len; i++) { 205 | if (pthread_join(workers[i].thread, NULL)) { 206 | die("pthread_join failed!"); 207 | } 208 | } 209 | } 210 | 211 | if (opts.stats) { 212 | gettimeofday(&(stats.time_end), NULL); 213 | double time_diff = ((long)stats.time_end.tv_sec * 1000000 + stats.time_end.tv_usec) - 214 | ((long)stats.time_start.tv_sec * 1000000 + stats.time_start.tv_usec); 215 | time_diff /= 1000000; 216 | printf("%zu matches\n%zu files contained matches\n%zu files searched\n%zu bytes searched\n%f seconds\n", 217 | stats.total_matches, stats.total_file_matches, stats.total_files, stats.total_bytes, time_diff); 218 | pthread_mutex_destroy(&stats_mtx); 219 | } 220 | 221 | if (opts.pager) { 222 | pclose(out_fd); 223 | } 224 | cleanup_options(); 225 | pthread_cond_destroy(&files_ready); 226 | pthread_mutex_destroy(&work_queue_mtx); 227 | pthread_mutex_destroy(&print_mtx); 228 | cleanup_ignore(root_ignores); 229 | free(workers); 230 | for (i = 0; paths[i] != NULL; i++) { 231 | free(paths[i]); 232 | free(base_paths[i]); 233 | } 234 | free(base_paths); 235 | free(paths); 236 | if (find_skip_lookup) { 237 | free(find_skip_lookup); 238 | } 239 | return !opts.match_found; 240 | } 241 | -------------------------------------------------------------------------------- /ag_src/options.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_H 2 | #define OPTIONS_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define DEFAULT_AFTER_LEN 2 10 | #define DEFAULT_BEFORE_LEN 2 11 | #define DEFAULT_CONTEXT_LEN 2 12 | #define DEFAULT_MAX_SEARCH_DEPTH 25 13 | enum case_behavior { 14 | CASE_DEFAULT = 4, /* Changes to CASE_SMART at the end of option parsing */ 15 | CASE_SENSITIVE = 1, 16 | CASE_INSENSITIVE = 2, 17 | CASE_SMART = 0, 18 | CASE_SENSITIVE_RETRY_INSENSITIVE = 3/* for future use */ 19 | }; 20 | 21 | enum path_print_behavior { 22 | PATH_PRINT_DEFAULT, /* PRINT_TOP if > 1 file being searched, else PRINT_NOTHING */ 23 | PATH_PRINT_DEFAULT_EACH_LINE, /* PRINT_EACH_LINE if > 1 file being searched, else PRINT_NOTHING */ 24 | PATH_PRINT_TOP, 25 | PATH_PRINT_EACH_LINE, 26 | PATH_PRINT_NOTHING 27 | }; 28 | 29 | typedef struct { 30 | int ackmate; 31 | pcre *ackmate_dir_filter; 32 | pcre_extra *ackmate_dir_filter_extra; 33 | size_t after; 34 | size_t before; 35 | enum case_behavior casing; 36 | const char *file_search_string; 37 | int match_files; 38 | pcre *file_search_regex; 39 | pcre_extra *file_search_regex_extra; 40 | int color; 41 | char *color_line_number; 42 | char *color_match; 43 | char *color_path; 44 | int color_win_ansi; 45 | int column; 46 | int context; 47 | int follow_symlinks; 48 | int invert_match; 49 | int literal; 50 | int literal_starts_wordchar; 51 | int literal_ends_wordchar; 52 | size_t max_matches_per_file; 53 | int max_search_depth; 54 | int mmap; 55 | int multiline; 56 | int one_dev; 57 | int only_matching; 58 | char path_sep; 59 | int path_to_ignore; 60 | int print_break; 61 | int print_count; 62 | int print_filename_only; 63 | int print_path; 64 | int print_all_paths; 65 | int print_line_numbers; 66 | int print_long_lines; /* TODO: support this in print.c */ 67 | int passthrough; 68 | pcre *re; 69 | pcre_extra *re_extra; 70 | int recurse_dirs; 71 | int search_all_files; 72 | int skip_vcs_ignores; 73 | int search_binary_files; 74 | int search_zip_files; 75 | int search_hidden_files; 76 | int search_stream; /* true if tail -F blah | ag */ 77 | int stats; 78 | size_t stream_line_num; /* This should totally not be in here */ 79 | int match_found; /* This should totally not be in here */ 80 | ino_t stdout_inode; 81 | char *query; 82 | int query_len; 83 | char *pager; 84 | int paths_len; 85 | int parallel; 86 | int use_thread_affinity; 87 | int vimgrep; 88 | size_t width; 89 | int word_regexp; 90 | int workers; 91 | } cli_options; 92 | 93 | /* global options. parse_options gives it sane values, everything else reads from it */ 94 | extern cli_options opts; 95 | 96 | typedef struct option option_t; 97 | 98 | void usage(void); 99 | void print_version(void); 100 | 101 | void init_options(void); 102 | void parse_options(int argc, char **argv, char **base_paths[], char **paths[]); 103 | void cleanup_options(void); 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /ag_src/print.h: -------------------------------------------------------------------------------- 1 | #ifndef PRINT_H 2 | #define PRINT_H 3 | 4 | #include "util.h" 5 | 6 | void print_init_context(void); 7 | void print_cleanup_context(void); 8 | void print_context_append(const char *line, size_t len); 9 | void print_trailing_context(const char *path, const char *buf, size_t n); 10 | void print_path(const char *path, const char sep); 11 | void print_path_count(const char *path, const char sep, const size_t count); 12 | void print_line(const char *buf, size_t buf_pos, size_t prev_line_offset); 13 | void print_binary_file_matches(const char *path); 14 | void print_file_matches(const char *path, const char *buf, const size_t buf_len, const match_t matches[], const size_t matches_len); 15 | void print_line_number(size_t line, const char sep); 16 | void print_column_number(const match_t matches[], size_t last_printed_match, 17 | size_t prev_line_offset, const char sep); 18 | void print_file_separator(void); 19 | const char *normalize_path(const char *path); 20 | 21 | #ifdef _WIN32 22 | void windows_use_ansi(int use_ansi); 23 | int fprintf_w32(FILE *fp, const char *format, ...); 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /ag_src/print_w32.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #include "print.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef FOREGROUND_MASK 10 | #define FOREGROUND_MASK (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY) 11 | #endif 12 | #ifndef BACKGROUND_MASK 13 | #define BACKGROUND_MASK (BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_INTENSITY) 14 | #endif 15 | 16 | #define FG_RGB (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN) 17 | #define BG_RGB (BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN) 18 | 19 | // BUFSIZ is guarenteed to be "at least 256 bytes" which might 20 | // not be enough for us. Use an arbitrary but reasonably big 21 | // buffer. win32 colored output will be truncated to this length. 22 | #define BUF_SIZE (16 * 1024) 23 | 24 | // max consecutive ansi sequence values beyond which we're aborting 25 | // e.g. this is 3 values: \e[0;1;33m 26 | #define MAX_VALUES 8 27 | 28 | static int g_use_ansi = 0; 29 | void windows_use_ansi(int use_ansi) { 30 | g_use_ansi = use_ansi; 31 | } 32 | 33 | int fprintf_w32(FILE *fp, const char *format, ...) { 34 | va_list args; 35 | char buf[BUF_SIZE] = { 0 }, *ptr = buf; 36 | static WORD attr_reset; 37 | static BOOL attr_initialized = FALSE; 38 | HANDLE stdo = INVALID_HANDLE_VALUE; 39 | WORD attr; 40 | DWORD written, csize; 41 | CONSOLE_CURSOR_INFO cci; 42 | CONSOLE_SCREEN_BUFFER_INFO csbi; 43 | COORD coord; 44 | 45 | // if we don't output to screen (tty) e.g. for pager/pipe or 46 | // if for other reason we can't access the screen info, of if 47 | // the user just prefers ansi, do plain passthrough. 48 | BOOL passthrough = 49 | g_use_ansi || 50 | !isatty(fileno(fp)) || 51 | INVALID_HANDLE_VALUE == (stdo = (HANDLE)_get_osfhandle(fileno(fp))) || 52 | !GetConsoleScreenBufferInfo(stdo, &csbi); 53 | 54 | if (passthrough) { 55 | int rv; 56 | va_start(args, format); 57 | rv = vfprintf(fp, format, args); 58 | va_end(args); 59 | return rv; 60 | } 61 | 62 | va_start(args, format); 63 | // truncates to (null terminated) BUF_SIZE if too long. 64 | // if too long - vsnprintf will fill count chars without 65 | // terminating null. buf is zeroed, so make sure we don't fill it. 66 | vsnprintf(buf, BUF_SIZE - 1, format, args); 67 | va_end(args); 68 | 69 | attr = csbi.wAttributes; 70 | if (!attr_initialized) { 71 | // reset is defined to have all (non color) attributes off 72 | attr_reset = attr & (FG_RGB | BG_RGB); 73 | attr_initialized = TRUE; 74 | } 75 | 76 | while (*ptr) { 77 | if (*ptr == '\033') { 78 | unsigned char c; 79 | int i, n = 0, m = '\0', v[MAX_VALUES], w, h; 80 | for (i = 0; i < MAX_VALUES; i++) 81 | v[i] = -1; 82 | ptr++; 83 | retry: 84 | if ((c = *ptr++) == 0) 85 | break; 86 | if (isdigit(c)) { 87 | if (v[n] == -1) 88 | v[n] = c - '0'; 89 | else 90 | v[n] = v[n] * 10 + c - '0'; 91 | goto retry; 92 | } 93 | if (c == '[') { 94 | goto retry; 95 | } 96 | if (c == ';') { 97 | if (++n == MAX_VALUES) 98 | break; 99 | goto retry; 100 | } 101 | if (c == '>' || c == '?') { 102 | m = c; 103 | goto retry; 104 | } 105 | 106 | switch (c) { 107 | // n is the last occupied index, so we have n+1 values 108 | case 'h': 109 | if (m == '?') { 110 | for (i = 0; i <= n; i++) { 111 | switch (v[i]) { 112 | case 3: 113 | GetConsoleScreenBufferInfo(stdo, &csbi); 114 | w = csbi.dwSize.X; 115 | h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 116 | csize = w * (h + 1); 117 | coord.X = 0; 118 | coord.Y = csbi.srWindow.Top; 119 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 120 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 121 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 122 | csbi.dwSize.X = 132; 123 | SetConsoleScreenBufferSize(stdo, csbi.dwSize); 124 | csbi.srWindow.Right = csbi.srWindow.Left + 131; 125 | SetConsoleWindowInfo(stdo, TRUE, &csbi.srWindow); 126 | break; 127 | case 5: 128 | attr = 129 | ((attr & FOREGROUND_MASK) << 4) | 130 | ((attr & BACKGROUND_MASK) >> 4); 131 | SetConsoleTextAttribute(stdo, attr); 132 | break; 133 | case 9: 134 | break; 135 | case 25: 136 | GetConsoleCursorInfo(stdo, &cci); 137 | cci.bVisible = TRUE; 138 | SetConsoleCursorInfo(stdo, &cci); 139 | break; 140 | case 47: 141 | coord.X = 0; 142 | coord.Y = 0; 143 | SetConsoleCursorPosition(stdo, coord); 144 | break; 145 | default: 146 | break; 147 | } 148 | } 149 | } else if (m == '>' && v[0] == 5) { 150 | GetConsoleCursorInfo(stdo, &cci); 151 | cci.bVisible = FALSE; 152 | SetConsoleCursorInfo(stdo, &cci); 153 | } 154 | break; 155 | case 'l': 156 | if (m == '?') { 157 | for (i = 0; i <= n; i++) { 158 | switch (v[i]) { 159 | case 3: 160 | GetConsoleScreenBufferInfo(stdo, &csbi); 161 | w = csbi.dwSize.X; 162 | h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 163 | csize = w * (h + 1); 164 | coord.X = 0; 165 | coord.Y = csbi.srWindow.Top; 166 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 167 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 168 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 169 | csbi.srWindow.Right = csbi.srWindow.Left + 79; 170 | SetConsoleWindowInfo(stdo, TRUE, &csbi.srWindow); 171 | csbi.dwSize.X = 80; 172 | SetConsoleScreenBufferSize(stdo, csbi.dwSize); 173 | break; 174 | case 5: 175 | attr = 176 | ((attr & FOREGROUND_MASK) << 4) | 177 | ((attr & BACKGROUND_MASK) >> 4); 178 | SetConsoleTextAttribute(stdo, attr); 179 | break; 180 | case 25: 181 | GetConsoleCursorInfo(stdo, &cci); 182 | cci.bVisible = FALSE; 183 | SetConsoleCursorInfo(stdo, &cci); 184 | break; 185 | default: 186 | break; 187 | } 188 | } 189 | } else if (m == '>' && v[0] == 5) { 190 | GetConsoleCursorInfo(stdo, &cci); 191 | cci.bVisible = TRUE; 192 | SetConsoleCursorInfo(stdo, &cci); 193 | } 194 | break; 195 | case 'm': 196 | for (i = 0; i <= n; i++) { 197 | if (v[i] == -1 || v[i] == 0) 198 | attr = attr_reset; 199 | else if (v[i] == 1) 200 | attr |= FOREGROUND_INTENSITY; 201 | else if (v[i] == 4) 202 | attr |= FOREGROUND_INTENSITY; 203 | else if (v[i] == 5) // blink is typically applied as bg intensity 204 | attr |= BACKGROUND_INTENSITY; 205 | else if (v[i] == 7) 206 | attr = 207 | ((attr & FOREGROUND_MASK) << 4) | 208 | ((attr & BACKGROUND_MASK) >> 4); 209 | else if (v[i] == 10) 210 | ; // symbol on 211 | else if (v[i] == 11) 212 | ; // symbol off 213 | else if (v[i] == 22) 214 | attr &= ~FOREGROUND_INTENSITY; 215 | else if (v[i] == 24) 216 | attr &= ~FOREGROUND_INTENSITY; 217 | else if (v[i] == 25) 218 | attr &= ~BACKGROUND_INTENSITY; 219 | else if (v[i] == 27) 220 | attr = 221 | ((attr & FOREGROUND_MASK) << 4) | 222 | ((attr & BACKGROUND_MASK) >> 4); 223 | else if (v[i] >= 30 && v[i] <= 37) { 224 | attr &= ~FG_RGB; // doesn't affect attributes 225 | if ((v[i] - 30) & 1) 226 | attr |= FOREGROUND_RED; 227 | if ((v[i] - 30) & 2) 228 | attr |= FOREGROUND_GREEN; 229 | if ((v[i] - 30) & 4) 230 | attr |= FOREGROUND_BLUE; 231 | } else if (v[i] == 39) // reset fg color and attributes 232 | attr = (attr & ~FOREGROUND_MASK) | (attr_reset & FG_RGB); 233 | else if (v[i] >= 40 && v[i] <= 47) { 234 | attr &= ~BG_RGB; 235 | if ((v[i] - 40) & 1) 236 | attr |= BACKGROUND_RED; 237 | if ((v[i] - 40) & 2) 238 | attr |= BACKGROUND_GREEN; 239 | if ((v[i] - 40) & 4) 240 | attr |= BACKGROUND_BLUE; 241 | } else if (v[i] == 49) // reset bg color 242 | attr = (attr & ~BACKGROUND_MASK) | (attr_reset & BG_RGB); 243 | } 244 | SetConsoleTextAttribute(stdo, attr); 245 | break; 246 | case 'K': 247 | GetConsoleScreenBufferInfo(stdo, &csbi); 248 | coord = csbi.dwCursorPosition; 249 | switch (v[0]) { 250 | default: 251 | case 0: 252 | csize = csbi.dwSize.X - coord.X; 253 | break; 254 | case 1: 255 | csize = coord.X; 256 | coord.X = 0; 257 | break; 258 | case 2: 259 | csize = csbi.dwSize.X; 260 | coord.X = 0; 261 | break; 262 | } 263 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 264 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 265 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 266 | break; 267 | case 'J': 268 | GetConsoleScreenBufferInfo(stdo, &csbi); 269 | w = csbi.dwSize.X; 270 | h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 271 | coord = csbi.dwCursorPosition; 272 | switch (v[0]) { 273 | default: 274 | case 0: 275 | csize = w * (h - coord.Y) - coord.X; 276 | coord.X = 0; 277 | break; 278 | case 1: 279 | csize = w * coord.Y + coord.X; 280 | coord.X = 0; 281 | coord.Y = csbi.srWindow.Top; 282 | break; 283 | case 2: 284 | csize = w * (h + 1); 285 | coord.X = 0; 286 | coord.Y = csbi.srWindow.Top; 287 | break; 288 | } 289 | FillConsoleOutputCharacter(stdo, ' ', csize, coord, &written); 290 | FillConsoleOutputAttribute(stdo, csbi.wAttributes, csize, coord, &written); 291 | SetConsoleCursorPosition(stdo, csbi.dwCursorPosition); 292 | break; 293 | case 'H': 294 | GetConsoleScreenBufferInfo(stdo, &csbi); 295 | coord = csbi.dwCursorPosition; 296 | if (v[0] != -1) { 297 | if (v[1] != -1) { 298 | coord.Y = csbi.srWindow.Top + v[0] - 1; 299 | coord.X = v[1] - 1; 300 | } else 301 | coord.X = v[0] - 1; 302 | } else { 303 | coord.X = 0; 304 | coord.Y = csbi.srWindow.Top; 305 | } 306 | if (coord.X < csbi.srWindow.Left) 307 | coord.X = csbi.srWindow.Left; 308 | else if (coord.X > csbi.srWindow.Right) 309 | coord.X = csbi.srWindow.Right; 310 | if (coord.Y < csbi.srWindow.Top) 311 | coord.Y = csbi.srWindow.Top; 312 | else if (coord.Y > csbi.srWindow.Bottom) 313 | coord.Y = csbi.srWindow.Bottom; 314 | SetConsoleCursorPosition(stdo, coord); 315 | break; 316 | default: 317 | break; 318 | } 319 | } else { 320 | putchar(*ptr); 321 | ptr++; 322 | } 323 | } 324 | return ptr - buf; 325 | } 326 | 327 | #endif /* _WIN32 */ 328 | -------------------------------------------------------------------------------- /ag_src/scandir.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "scandir.h" 5 | #include "util.h" 6 | 7 | int ag_scandir(const char *dirname, 8 | struct dirent ***namelist, 9 | filter_fp filter, 10 | void *baton) { 11 | DIR *dirp = NULL; 12 | struct dirent **names = NULL; 13 | struct dirent *entry, *d; 14 | int names_len = 32; 15 | int results_len = 0; 16 | 17 | dirp = opendir(dirname); 18 | if (dirp == NULL) { 19 | goto fail; 20 | } 21 | 22 | names = malloc(sizeof(struct dirent *) * names_len); 23 | if (names == NULL) { 24 | goto fail; 25 | } 26 | 27 | while ((entry = readdir(dirp)) != NULL) { 28 | if ((*filter)(dirname, entry, baton) == FALSE) { 29 | continue; 30 | } 31 | if (results_len >= names_len) { 32 | struct dirent **tmp_names = names; 33 | names_len *= 2; 34 | names = realloc(names, sizeof(struct dirent *) * names_len); 35 | if (names == NULL) { 36 | free(tmp_names); 37 | goto fail; 38 | } 39 | } 40 | 41 | #if defined(__MINGW32__) || defined(__CYGWIN__) 42 | d = malloc(sizeof(struct dirent)); 43 | #else 44 | d = malloc(entry->d_reclen); 45 | #endif 46 | 47 | if (d == NULL) { 48 | goto fail; 49 | } 50 | #if defined(__MINGW32__) || defined(__CYGWIN__) 51 | memcpy(d, entry, sizeof(struct dirent)); 52 | #else 53 | memcpy(d, entry, entry->d_reclen); 54 | #endif 55 | 56 | names[results_len] = d; 57 | results_len++; 58 | } 59 | 60 | closedir(dirp); 61 | *namelist = names; 62 | return results_len; 63 | 64 | fail: 65 | if (dirp) { 66 | closedir(dirp); 67 | } 68 | 69 | if (names != NULL) { 70 | int i; 71 | for (i = 0; i < results_len; i++) { 72 | free(names[i]); 73 | } 74 | free(names); 75 | } 76 | return -1; 77 | } 78 | -------------------------------------------------------------------------------- /ag_src/scandir.h: -------------------------------------------------------------------------------- 1 | #ifndef SCANDIR_H 2 | #define SCANDIR_H 3 | 4 | #include "ignore.h" 5 | 6 | typedef struct { 7 | const ignores *ig; 8 | const char *base_path; 9 | size_t base_path_len; 10 | const char *path_start; 11 | } scandir_baton_t; 12 | 13 | typedef int (*filter_fp)(const char *path, const struct dirent *, void *); 14 | 15 | int ag_scandir(const char *dirname, 16 | struct dirent ***namelist, 17 | filter_fp filter, 18 | void *baton); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /ag_src/search.h: -------------------------------------------------------------------------------- 1 | #ifndef SEARCH_H 2 | #define SEARCH_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #ifdef _WIN32 13 | #include 14 | #else 15 | #include 16 | #endif 17 | #include 18 | #include 19 | 20 | #include "config.h" 21 | 22 | #ifdef HAVE_PTHREAD_H 23 | #include 24 | #endif 25 | 26 | #include "decompress.h" 27 | #include "ignore.h" 28 | #include "log.h" 29 | #include "options.h" 30 | #include "print.h" 31 | #include "uthash.h" 32 | #include "util.h" 33 | 34 | extern size_t alpha_skip_lookup[256]; 35 | extern size_t *find_skip_lookup; 36 | extern uint8_t h_table[H_SIZE] __attribute__((aligned(64))); 37 | 38 | struct work_queue_t { 39 | char *path; 40 | struct work_queue_t *next; 41 | }; 42 | typedef struct work_queue_t work_queue_t; 43 | 44 | extern work_queue_t *work_queue; 45 | extern work_queue_t *work_queue_tail; 46 | extern int done_adding_files; 47 | extern int stop_workers; 48 | extern pthread_cond_t files_ready; 49 | extern pthread_mutex_t stats_mtx; 50 | extern pthread_mutex_t work_queue_mtx; 51 | 52 | extern pthread_barrier_t worker_done; 53 | extern pthread_barrier_t results_done; 54 | 55 | /* For symlink loop detection */ 56 | #define SYMLOOP_ERROR (-1) 57 | #define SYMLOOP_OK (0) 58 | #define SYMLOOP_LOOP (1) 59 | 60 | typedef struct { 61 | dev_t dev; 62 | ino_t ino; 63 | } dirkey_t; 64 | 65 | typedef struct { 66 | dirkey_t key; 67 | UT_hash_handle hh; 68 | } symdir_t; 69 | 70 | extern symdir_t *symhash; 71 | 72 | void search_buf(int worker_id, const char *buf, const size_t buf_len, 73 | const char *dir_full_path); 74 | void search_stream(int worker_id, FILE *stream, const char *path); 75 | void search_file(int worker_id, const char *file_full_path); 76 | 77 | void *search_file_worker(void *i); 78 | 79 | void search_dir(ignores *ig, const char *base_path, const char *path, const int depth, dev_t original_dev); 80 | 81 | /* libag 'private' routines and variables. */ 82 | extern int add_local_result(int worker_id, const char *file, 83 | const match_t matches[], const size_t matches_len, 84 | const char *buf, int flags); 85 | 86 | extern int init_local_results(int worker_id); 87 | extern int has_ag_init; 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /ag_src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | #include "log.h" 13 | #include "options.h" 14 | 15 | extern FILE *out_fd; 16 | 17 | #ifndef TRUE 18 | #define TRUE 1 19 | #endif 20 | 21 | #ifndef FALSE 22 | #define FALSE 0 23 | #endif 24 | 25 | #define H_SIZE (64 * 1024) 26 | 27 | #ifdef __clang__ 28 | #define NO_SANITIZE_ALIGNMENT __attribute__((no_sanitize("alignment"))) 29 | #else 30 | #define NO_SANITIZE_ALIGNMENT 31 | #endif 32 | 33 | void *ag_malloc(size_t size); 34 | void *ag_realloc(void *ptr, size_t size); 35 | void *ag_calloc(size_t nelem, size_t elsize); 36 | char *ag_strdup(const char *s); 37 | char *ag_strndup(const char *s, size_t size); 38 | 39 | typedef struct { 40 | size_t start; /* Byte at which the match starts */ 41 | size_t end; /* and where it ends */ 42 | } match_t; 43 | 44 | typedef struct { 45 | size_t total_bytes; 46 | size_t total_files; 47 | size_t total_matches; 48 | size_t total_file_matches; 49 | struct timeval time_start; 50 | struct timeval time_end; 51 | } ag_stats; 52 | 53 | 54 | extern ag_stats stats; 55 | 56 | /* Union to translate between chars and words without violating strict aliasing */ 57 | typedef union { 58 | char as_chars[sizeof(uint16_t)]; 59 | uint16_t as_word; 60 | } word_t; 61 | 62 | void free_strings(char **strs, const size_t strs_len); 63 | 64 | void generate_alpha_skip(const char *find, size_t f_len, size_t skip_lookup[], const int case_sensitive); 65 | int is_prefix(const char *s, const size_t s_len, const size_t pos, const int case_sensitive); 66 | size_t suffix_len(const char *s, const size_t s_len, const size_t pos, const int case_sensitive); 67 | void generate_find_skip(const char *find, const size_t f_len, size_t **skip_lookup, const int case_sensitive); 68 | void generate_hash(const char *find, const size_t f_len, uint8_t *H, const int case_sensitive); 69 | 70 | /* max is already defined on spec-violating compilers such as MinGW */ 71 | size_t ag_max(size_t a, size_t b); 72 | size_t ag_min(size_t a, size_t b); 73 | 74 | const char *boyer_moore_strnstr(const char *s, const char *find, const size_t s_len, const size_t f_len, 75 | const size_t alpha_skip_lookup[], const size_t *find_skip_lookup, const int case_insensitive); 76 | const char *hash_strnstr(const char *s, const char *find, const size_t s_len, const size_t f_len, uint8_t *h_table, const int case_sensitive); 77 | 78 | size_t invert_matches(const char *buf, const size_t buf_len, match_t matches[], size_t matches_len); 79 | void realloc_matches(match_t **matches, size_t *matches_size, size_t matches_len); 80 | void compile_study(pcre **re, pcre_extra **re_extra, char *q, const int pcre_opts, const int study_opts); 81 | 82 | 83 | int is_binary(const void *buf, const size_t buf_len); 84 | int is_regex(const char *query); 85 | int is_fnmatch(const char *filename); 86 | int binary_search(const char *needle, char **haystack, int start, int end); 87 | 88 | void init_wordchar_table(void); 89 | int is_wordchar(char ch); 90 | 91 | int is_lowercase(const char *s); 92 | 93 | int is_directory(const char *path, const struct dirent *d); 94 | int is_symlink(const char *path, const struct dirent *d); 95 | int is_named_pipe(const char *path, const struct dirent *d); 96 | 97 | void die(const char *fmt, ...); 98 | 99 | void ag_asprintf(char **ret, const char *fmt, ...); 100 | 101 | ssize_t buf_getline(const char **line, const char *buf, const size_t buf_len, const size_t buf_offset); 102 | 103 | #ifndef HAVE_FGETLN 104 | char *fgetln(FILE *fp, size_t *lenp); 105 | #endif 106 | #ifndef HAVE_GETLINE 107 | ssize_t getline(char **lineptr, size_t *n, FILE *stream); 108 | #endif 109 | #ifndef HAVE_REALPATH 110 | char *realpath(const char *path, char *resolved_path); 111 | #endif 112 | #ifndef HAVE_STRLCPY 113 | size_t strlcpy(char *dest, const char *src, size_t size); 114 | #endif 115 | #ifndef HAVE_VASPRINTF 116 | int vasprintf(char **ret, const char *fmt, va_list args); 117 | #endif 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /ag_src/win32/config.h: -------------------------------------------------------------------------------- 1 | #define HAVE_LZMA_H 2 | #define HAVE_PTHREAD_H 3 | -------------------------------------------------------------------------------- /ag_src/zfile.c: -------------------------------------------------------------------------------- 1 | #ifdef __FreeBSD__ 2 | #include 3 | #endif 4 | #include 5 | 6 | #ifdef __CYGWIN__ 7 | typedef _off64_t off64_t; 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "config.h" 21 | 22 | #ifdef HAVE_ERR_H 23 | #include 24 | #endif 25 | #ifdef HAVE_ZLIB_H 26 | #include 27 | #endif 28 | #ifdef HAVE_LZMA_H 29 | #include 30 | #endif 31 | 32 | #include "decompress.h" 33 | 34 | #if HAVE_FOPENCOOKIE 35 | 36 | #define min(a, b) ({ \ 37 | __typeof (a) _a = (a); \ 38 | __typeof (b) _b = (b); \ 39 | _a < _b ? _a : _b; }) 40 | 41 | static cookie_read_function_t zfile_read; 42 | static cookie_seek_function_t zfile_seek; 43 | static cookie_close_function_t zfile_close; 44 | 45 | static const cookie_io_functions_t zfile_io = { 46 | .read = zfile_read, 47 | .write = NULL, 48 | .seek = zfile_seek, 49 | .close = zfile_close, 50 | }; 51 | 52 | #define KB (1024) 53 | struct zfile { 54 | FILE *in; // Source FILE stream 55 | uint64_t logic_offset, // Logical offset in output (forward seeks) 56 | decode_offset, // Where we've decoded to 57 | actual_len; 58 | uint32_t outbuf_start; 59 | 60 | ag_compression_type ctype; 61 | 62 | union { 63 | z_stream gz; 64 | lzma_stream lzma; 65 | } stream; 66 | 67 | uint8_t inbuf[32 * KB]; 68 | uint8_t outbuf[256 * KB]; 69 | bool eof; 70 | }; 71 | 72 | #define CAVAIL_IN(c) ((c)->ctype == AG_GZIP ? (c)->stream.gz.avail_in : (c)->stream.lzma.avail_in) 73 | #define CNEXT_OUT(c) ((c)->ctype == AG_GZIP ? (c)->stream.gz.next_out : (c)->stream.lzma.next_out) 74 | 75 | static int 76 | zfile_cookie_init(struct zfile *cookie) { 77 | #ifdef HAVE_LZMA_H 78 | lzma_ret lzrc; 79 | #endif 80 | int rc; 81 | 82 | assert(cookie->logic_offset == 0); 83 | assert(cookie->decode_offset == 0); 84 | 85 | cookie->actual_len = 0; 86 | 87 | switch (cookie->ctype) { 88 | #ifdef HAVE_ZLIB_H 89 | case AG_GZIP: 90 | memset(&cookie->stream.gz, 0, sizeof cookie->stream.gz); 91 | rc = inflateInit2(&cookie->stream.gz, 32 + 15); 92 | if (rc != Z_OK) { 93 | log_err("Unable to initialize zlib: %s", zError(rc)); 94 | return EIO; 95 | } 96 | cookie->stream.gz.next_in = NULL; 97 | cookie->stream.gz.avail_in = 0; 98 | cookie->stream.gz.next_out = cookie->outbuf; 99 | cookie->stream.gz.avail_out = sizeof cookie->outbuf; 100 | break; 101 | #endif 102 | #ifdef HAVE_LZMA_H 103 | case AG_XZ: 104 | cookie->stream.lzma = (lzma_stream)LZMA_STREAM_INIT; 105 | lzrc = lzma_auto_decoder(&cookie->stream.lzma, -1, 0); 106 | if (lzrc != LZMA_OK) { 107 | log_err("Unable to initialize lzma_auto_decoder: %d", lzrc); 108 | return EIO; 109 | } 110 | cookie->stream.lzma.next_in = NULL; 111 | cookie->stream.lzma.avail_in = 0; 112 | cookie->stream.lzma.next_out = cookie->outbuf; 113 | cookie->stream.lzma.avail_out = sizeof cookie->outbuf; 114 | break; 115 | #endif 116 | default: 117 | log_err("Unsupported compression type: %d", cookie->ctype); 118 | return EINVAL; 119 | } 120 | 121 | 122 | cookie->outbuf_start = 0; 123 | cookie->eof = false; 124 | return 0; 125 | } 126 | 127 | static void 128 | zfile_cookie_cleanup(struct zfile *cookie) { 129 | switch (cookie->ctype) { 130 | #ifdef HAVE_ZLIB_H 131 | case AG_GZIP: 132 | inflateEnd(&cookie->stream.gz); 133 | break; 134 | #endif 135 | #ifdef HAVE_LZMA_H 136 | case AG_XZ: 137 | lzma_end(&cookie->stream.lzma); 138 | break; 139 | #endif 140 | default: 141 | /* Compiler false positive - unreachable. */ 142 | break; 143 | } 144 | } 145 | 146 | /* 147 | * Open compressed file 'path' as a (forward-)seekable (and rewindable), 148 | * read-only stream. 149 | */ 150 | FILE * 151 | decompress_open(int fd, const char *mode, ag_compression_type ctype) { 152 | struct zfile *cookie; 153 | FILE *res, *in; 154 | int error; 155 | 156 | cookie = NULL; 157 | in = res = NULL; 158 | if (strstr(mode, "w") || strstr(mode, "a")) { 159 | errno = EINVAL; 160 | goto out; 161 | } 162 | 163 | in = fdopen(fd, mode); 164 | if (in == NULL) 165 | goto out; 166 | 167 | /* 168 | * No validation of compression type is done -- file is assumed to 169 | * match input. In Ag, the compression type is already detected, so 170 | * that's ok. 171 | */ 172 | cookie = malloc(sizeof *cookie); 173 | if (cookie == NULL) { 174 | errno = ENOMEM; 175 | goto out; 176 | } 177 | 178 | cookie->in = in; 179 | cookie->logic_offset = 0; 180 | cookie->decode_offset = 0; 181 | cookie->ctype = ctype; 182 | 183 | error = zfile_cookie_init(cookie); 184 | if (error != 0) { 185 | errno = error; 186 | goto out; 187 | } 188 | 189 | res = fopencookie(cookie, mode, zfile_io); 190 | 191 | out: 192 | if (res == NULL) { 193 | if (in != NULL) 194 | fclose(in); 195 | if (cookie != NULL) 196 | free(cookie); 197 | } 198 | return res; 199 | } 200 | 201 | /* 202 | * Return number of bytes into buf, 0 on EOF, -1 on error. Update stream 203 | * offset. 204 | */ 205 | static ssize_t 206 | zfile_read(void *cookie_, char *buf, size_t size) { 207 | struct zfile *cookie = cookie_; 208 | size_t nb, ignorebytes; 209 | ssize_t total = 0; 210 | lzma_ret lzret; 211 | int ret; 212 | 213 | assert(size <= SSIZE_MAX); 214 | 215 | if (size == 0) 216 | return 0; 217 | 218 | if (cookie->eof) 219 | return 0; 220 | 221 | ret = Z_OK; 222 | lzret = LZMA_OK; 223 | 224 | ignorebytes = cookie->logic_offset - cookie->decode_offset; 225 | assert(ignorebytes == 0); 226 | 227 | do { 228 | size_t inflated; 229 | 230 | /* Drain output buffer first */ 231 | while (CNEXT_OUT(cookie) > 232 | &cookie->outbuf[cookie->outbuf_start]) { 233 | size_t left = CNEXT_OUT(cookie) - 234 | &cookie->outbuf[cookie->outbuf_start]; 235 | size_t ignoreskip = min(ignorebytes, left); 236 | size_t toread; 237 | 238 | if (ignoreskip > 0) { 239 | ignorebytes -= ignoreskip; 240 | left -= ignoreskip; 241 | cookie->outbuf_start += ignoreskip; 242 | cookie->decode_offset += ignoreskip; 243 | } 244 | 245 | // Ran out of output before we seek()ed up. 246 | if (ignorebytes > 0) 247 | break; 248 | 249 | toread = min(left, size); 250 | memcpy(buf, &cookie->outbuf[cookie->outbuf_start], 251 | toread); 252 | 253 | buf += toread; 254 | size -= toread; 255 | left -= toread; 256 | cookie->outbuf_start += toread; 257 | cookie->decode_offset += toread; 258 | cookie->logic_offset += toread; 259 | total += toread; 260 | 261 | if (size == 0) 262 | break; 263 | } 264 | 265 | if (size == 0) 266 | break; 267 | 268 | /* 269 | * If we have not satisfied read, the output buffer must be 270 | * empty. 271 | */ 272 | assert(cookie->stream.gz.next_out == 273 | &cookie->outbuf[cookie->outbuf_start]); 274 | 275 | if ((cookie->ctype == AG_XZ && lzret == LZMA_STREAM_END) || 276 | (cookie->ctype == AG_GZIP && ret == Z_STREAM_END)) { 277 | cookie->eof = true; 278 | break; 279 | } 280 | 281 | /* Read more input if empty */ 282 | if (CAVAIL_IN(cookie) == 0) { 283 | nb = fread(cookie->inbuf, 1, sizeof cookie->inbuf, 284 | cookie->in); 285 | if (ferror(cookie->in)) { 286 | warn("error read core"); 287 | exit(1); 288 | } 289 | if (nb == 0 && feof(cookie->in)) { 290 | warn("truncated file"); 291 | exit(1); 292 | } 293 | if (cookie->ctype == AG_XZ) { 294 | cookie->stream.lzma.avail_in = nb; 295 | cookie->stream.lzma.next_in = cookie->inbuf; 296 | } else { 297 | cookie->stream.gz.avail_in = nb; 298 | cookie->stream.gz.next_in = cookie->inbuf; 299 | } 300 | } 301 | 302 | /* Reset stream state to beginning of output buffer */ 303 | if (cookie->ctype == AG_XZ) { 304 | cookie->stream.lzma.next_out = cookie->outbuf; 305 | cookie->stream.lzma.avail_out = sizeof cookie->outbuf; 306 | } else { 307 | cookie->stream.gz.next_out = cookie->outbuf; 308 | cookie->stream.gz.avail_out = sizeof cookie->outbuf; 309 | } 310 | cookie->outbuf_start = 0; 311 | 312 | if (cookie->ctype == AG_GZIP) { 313 | ret = inflate(&cookie->stream.gz, Z_NO_FLUSH); 314 | if (ret != Z_OK && ret != Z_STREAM_END) { 315 | log_err("Found mem/data error while decompressing zlib stream: %s", zError(ret)); 316 | return -1; 317 | } 318 | } else { 319 | lzret = lzma_code(&cookie->stream.lzma, LZMA_RUN); 320 | if (lzret != LZMA_OK && lzret != LZMA_STREAM_END) { 321 | log_err("Found mem/data error while decompressing xz/lzma stream: %d", lzret); 322 | return -1; 323 | } 324 | } 325 | inflated = CNEXT_OUT(cookie) - &cookie->outbuf[0]; 326 | cookie->actual_len += inflated; 327 | } while (!ferror(cookie->in) && size > 0); 328 | 329 | assert(total <= SSIZE_MAX); 330 | return total; 331 | } 332 | 333 | static int 334 | zfile_seek(void *cookie_, off64_t *offset_, int whence) { 335 | struct zfile *cookie = cookie_; 336 | off64_t new_offset = 0, offset = *offset_; 337 | 338 | if (whence == SEEK_SET) { 339 | new_offset = offset; 340 | } else if (whence == SEEK_CUR) { 341 | new_offset = (off64_t)cookie->logic_offset + offset; 342 | } else { 343 | /* SEEK_END not ok */ 344 | return -1; 345 | } 346 | 347 | if (new_offset < 0) 348 | return -1; 349 | 350 | /* Backward seeks to anywhere but 0 are not ok */ 351 | if (new_offset < (off64_t)cookie->logic_offset && new_offset != 0) { 352 | return -1; 353 | } 354 | 355 | if (new_offset == 0) { 356 | /* rewind(3) */ 357 | cookie->decode_offset = 0; 358 | cookie->logic_offset = 0; 359 | zfile_cookie_cleanup(cookie); 360 | zfile_cookie_init(cookie); 361 | } else if ((uint64_t)new_offset > cookie->logic_offset) { 362 | /* Emulate forward seek by skipping ... */ 363 | char *buf; 364 | const size_t bsz = 32 * 1024; 365 | 366 | buf = malloc(bsz); 367 | while ((uint64_t)new_offset > cookie->logic_offset) { 368 | size_t diff = min(bsz, 369 | (uint64_t)new_offset - cookie->logic_offset); 370 | ssize_t err = zfile_read(cookie_, buf, diff); 371 | if (err < 0) { 372 | free(buf); 373 | return -1; 374 | } 375 | 376 | /* Seek past EOF gets positioned at EOF */ 377 | if (err == 0) { 378 | assert(cookie->eof); 379 | new_offset = cookie->logic_offset; 380 | break; 381 | } 382 | } 383 | free(buf); 384 | } 385 | 386 | assert(cookie->logic_offset == (uint64_t)new_offset); 387 | 388 | *offset_ = new_offset; 389 | return 0; 390 | } 391 | 392 | static int 393 | zfile_close(void *cookie_) { 394 | struct zfile *cookie = cookie_; 395 | 396 | zfile_cookie_cleanup(cookie); 397 | fclose(cookie->in); 398 | free(cookie); 399 | 400 | return 0; 401 | } 402 | 403 | #endif /* HAVE_FOPENCOOKIE */ 404 | -------------------------------------------------------------------------------- /bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Davidson Francis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | add_subdirectory(python) 16 | -------------------------------------------------------------------------------- /bindings/javascript/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Davidson Francis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cmake_minimum_required(VERSION 3.13) 16 | cmake_policy(SET CMP0042 NEW) 17 | set (CMAKE_C_STANDARD 99) 18 | 19 | # Get PkgConfig package 20 | find_package(PkgConfig REQUIRED) 21 | 22 | project(libag_wrapper) 23 | 24 | # Node headers 25 | include_directories(${CMAKE_JS_INC}) 26 | 27 | # Our binding lib 28 | add_library(${PROJECT_NAME} SHARED libag_node.c ${CMAKE_JS_SRC}) 29 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") 30 | 31 | # Check if we can find libag 32 | pkg_check_modules(LIBAG libag) 33 | 34 | # If not, try to use local libag 35 | if(NOT EQUAL ${LIBAG_FOUND}) 36 | message(WARNING "libag not found!, trying to use local version") 37 | 38 | get_filename_component(LIBAG_PATH "../../" 39 | REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 40 | 41 | target_include_directories(${PROJECT_NAME} PUBLIC ${LIBAG_PATH}) 42 | target_link_directories(${PROJECT_NAME} PUBLIC ${LIBAG_PATH}) 43 | else() 44 | target_include_directories(${PROJECT_NAME} PUBLIC ${LIBAG_INCLUDE_DIRS}) 45 | target_link_directories(${PROJECT_NAME} PUBLIC ${LIBAG_LIBRARY_DIRS}) 46 | endif(NOT EQUAL ${LIBAG_FOUND}) 47 | 48 | # libraries 49 | target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB} libag.so) 50 | 51 | # Define NAPI_VERSION 52 | add_definitions(-DNAPI_VERSION=8) 53 | -------------------------------------------------------------------------------- /bindings/javascript/README.md: -------------------------------------------------------------------------------- 1 | # Node.js bindings 2 | 3 | ## Introduction 4 | Libag has a (very) experimental bindings support for Node.js 5 | 6 | Unlike the 7 | [Python bindings](https://github.com/Theldus/libag/tree/master/bindings/python), 8 | unfortunately, the Node.js bindings could not be written using 9 | [SWIG](http://www.swig.org/): seems like SWIG uses the v8 API directly, which is 10 | always changing; therefore, in the latest SWIG version (v4.0.2), the generated code 11 | does not work for the latest LTS version of Node (v14.17.1). 12 | 13 | That said, the bindings for Node.js were implemented manually using the 14 | [Node-API](https://nodejs.org/dist/latest-v14.x/docs/api/n-api.html), which is an 15 | API implemented by Node.js itself that aims to remain stable between different 16 | versions of Node.js. For any project aiming to implement addons to Node, the 17 | Node-API seems to be the smarter/safer path to take. 18 | 19 | ## Building 20 | The build process of the Node.js addon is as follows: 21 | 22 | ### 1) Download and configure Node.js 23 | Download the latest version (or 24 | [v14.17.1](https://nodejs.org/dist/v14.17.1/node-v14.17.1-linux-x64.tar.xz), 25 | tested here) and set the environment variables to match your 'prefix': 26 | 27 | Something like: 28 | ```bash 29 | # Download Node.js 30 | cd /path/to/nodejs 31 | wget https://nodejs.org/dist/v14.17.1/node-v14.17.1-linux-x64.tar.xz 32 | tar xf node*.tar.xz 33 | 34 | # Configure environment variables and set to your ~/.bashrc too 35 | export PATH=$PATH:$(readlink -f node-*/bin) 36 | echo -e "# Node.js\nexport PATH=\$PATH:$(readlink -f node-*/bin)" >> ~/.bashrc 37 | 38 | # Install cmake-js 39 | npm install -g cmake-js 40 | ``` 41 | ### 2) Build libag addon 42 | Once Node.js and cmake-js were configured, there are several ways to generate the 43 | addon, depending on how libag is located on your system: 44 | 45 | #### a) Installed system-wide or not installed 46 | If you already have the libag properly built and installed (or do not have it installed, 47 | which is optional): 48 | ```bash 49 | cd libag/ 50 | make node-binding 51 | ``` 52 | Just note that if not installed, the generated wrapper has the hardcoded path of the 53 | compiled libag.so, so if this file changes location, the addon will not work. 54 | 55 | #### b) Installed in custom path 56 | If you installed via Makefile or CMake in a PREFIX other than the default, invoke 57 | `make` by providing the installation PATH via `PREFIX`, something like: 58 | ```bash 59 | cd libag/ 60 | make node-binding PREFIX=/your/libag/prefix/here 61 | ``` 62 | ## Usage 63 | The differences between C and Javascript are covered below: 64 | 65 | ### Structures and Objects 66 | All libag's C structures are mapped to javascript objects. It is important to note 67 | that, unlike Python bindings, these objects are not classes and therefore do not 68 | have 'constructors' in the strict sense of the word. 69 | 70 | In this case, the C structures have a function of the same name in JS, and when 71 | invoking them, a new object (wrapper) of the structure in C is created, which means: 72 | ```c 73 | /* C. */ 74 | struct ag_config config; 75 | memset(&config, 0, sizeof(struct ag_config)); 76 | config.search_binary_files = 1; 77 | config.num_workers = 4; 78 | 79 | ag_init_config(config) 80 | ``` 81 | is exactly equivalent to: 82 | ```javascript 83 | /* Node.js. */ 84 | config = libag.ag_config(); 85 | config.search_binary_files = 1; 86 | config.num_workers = 4; 87 | 88 | libag.ag_init_config(config); 89 | ``` 90 | 91 | ### Functions 92 | Functions that take no parameters and/or that take structs and return primitive 93 | types have the expected behavior in both languages, which goes for: `ag_init()`, 94 | `ag_init_config()`, `ag_finish()`, `ag_set_config()`, `ag_get_stats()`, 95 | `ag_start_workers()` and `ag_stop_workers()`. 96 | 97 | Those that differ are listed in the table below: 98 | | Function | C | Javascript equivalent | 99 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 100 | | **ag_search** | Parameters:
query (**char \***), npaths (**int**), target_paths (**char\*\***), nresults (**size_t\***)

Return:
On success: **struct ag_result \*\***, nresults (**size_t\***)
On error: **null**, 0 | Parameters:
query (**string**), target_paths (array of strings)

Return:
On success: pure JS object (not wrapper) containing (nresults, array of (**results**))
On error: undefined | 101 | 102 | Unlike Python bindings, the return of `ag_search()` is a pure JS object, not a 103 | wrapper. Thus, routines like `ag_free_result()` and `ag_free_all_results()` 104 | unnecessary (in fact, they don't even exist in the binding). 105 | 106 | --- 107 | 108 | Examples of how to use bindings can be found in 109 | [bindings/javascript/examples](https://github.com/Theldus/libag/tree/master/bindings/javascript/examples). 110 | -------------------------------------------------------------------------------- /bindings/javascript/examples/init_config.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright 2021 Davidson Francis 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const libag = require('../build/Release/libag_wrapper'); 20 | 21 | if (process.argv.length < 4) 22 | { 23 | console.log("Usage: " + process.argv[1] + " \"regex\" [paths]"); 24 | process.exit(1); 25 | } 26 | 27 | /* 4 workers and enable binary search. */ 28 | config = libag.ag_config(); 29 | config.search_binary_files = 1; 30 | config.num_workers = 4; 31 | 32 | /* Initiate Ag library with default options .*/ 33 | libag.ag_init_config(config); 34 | 35 | /* Search. */ 36 | r = libag.ag_search(process.argv[2], process.argv.slice(3)); 37 | 38 | if (r === undefined) 39 | console.log("no result found"); 40 | else 41 | { 42 | console.log(r.nresults + " results found"); 43 | 44 | /* Show them on the screen if any. */ 45 | r.results.forEach((file, i) => { 46 | file.matches.forEach((match, j) => { 47 | console.log( 48 | "file: " + file.file + ", match: " + match.match + 49 | ", start: " + match.byte_start + " / end: " + match.byte_end + 50 | ", is_binary: " + !!(file.flags & 2)); 51 | }); 52 | }); 53 | } 54 | 55 | /* Release Ag resources. */ 56 | libag.ag_finish(); 57 | -------------------------------------------------------------------------------- /bindings/javascript/examples/simple.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright 2021 Davidson Francis 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const libag = require('../build/Release/libag_wrapper'); 20 | 21 | if (process.argv.length < 4) 22 | { 23 | console.log("Usage: " + process.argv[1] + " \"regex\" [paths]"); 24 | process.exit(1); 25 | } 26 | 27 | /* Initiate Ag library with default options .*/ 28 | libag.ag_init(); 29 | 30 | /* Search. */ 31 | r = libag.ag_search(process.argv[2], process.argv.slice(3)); 32 | 33 | if (r === undefined) 34 | console.log("no result found"); 35 | else 36 | { 37 | console.log(r.nresults + " results found"); 38 | 39 | /* Show them on the screen if any. */ 40 | r.results.forEach((file, i) => { 41 | file.matches.forEach((match, j) => { 42 | console.log( 43 | "File: " + file.file + ", match: " + match.match + 44 | ", start: " + match.byte_start + " / end: " + match.byte_end 45 | ); 46 | }); 47 | }); 48 | } 49 | 50 | /* Release Ag resources. */ 51 | libag.ag_finish(); 52 | -------------------------------------------------------------------------------- /bindings/javascript/macros.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Davidson Francis 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef MACROS_H 18 | #define MACROS_H 19 | 20 | #include 21 | 22 | /** 23 | * 'Convert' size_t to the nearest JS type. 24 | */ 25 | #if SIZE_MAX == 0xffffffffffffffff 26 | #define SIZE_T uint64 27 | #define napi_create_sizet napi_create_uint64 28 | #define napi_get_value_uint64(e,v,r) \ 29 | napi_get_value_bigint_uint64((e),(v),(r),NULL) 30 | #define napi_create_uint64(e,v,r) \ 31 | napi_create_bigint_uint64((e),(v),(r)) 32 | #else 33 | #define SIZE_T uint32 34 | #define napi_create_sizet napi_create_uint32 35 | #endif 36 | 37 | /* Facility to declare a napi_method. */ 38 | #define DECLARE_NAPI_METHOD(name, func)\ 39 | {name, 0, func, 0, 0, 0, napi_default, 0} 40 | 41 | /* Facility to declare a napi field/variable. */ 42 | #define DECLARE_NAPI_FIELD(name)\ 43 | {#name, 0, 0, get_##name, set_##name, 0, napi_default, 0} 44 | 45 | /** 46 | * For a given structure @p str with type @p type and with the 47 | * name @p field, define a getter method to it. 48 | * 49 | * This macro is generic and should work for any primitive type 50 | * that is supported by C and JS. 51 | */ 52 | #define DEFINE_GET(str, field, type)\ 53 | napi_value get_##field(napi_env env, napi_callback_info info)\ 54 | {\ 55 | struct str *st; \ 56 | napi_status status; \ 57 | napi_value jsthis; \ 58 | napi_value value; \ 59 | status = napi_get_cb_info(env, info, NULL, NULL, &jsthis, NULL);\ 60 | if (status != napi_ok)\ 61 | return (NULL);\ 62 | status = napi_unwrap(env, jsthis, (void**)&st);\ 63 | if (status != napi_ok)\ 64 | return (NULL);\ 65 | status = napi_create_##type(env, st->field, &value);\ 66 | if (status != napi_ok)\ 67 | return (NULL);\ 68 | return (value);\ 69 | } 70 | 71 | /** 72 | * For a given structure @p str with type @p type and with the 73 | * name @p field, define a setter method to it. 74 | * 75 | * This macro is generic and should work for any primitive type 76 | * that is supported by C and JS. 77 | */ 78 | #define DEFINE_SET(str, field, type)\ 79 | napi_value set_##field(napi_env env, napi_callback_info info)\ 80 | {\ 81 | struct str *st; \ 82 | napi_status status; \ 83 | napi_value value; \ 84 | napi_value jsthis; \ 85 | size_t argc = 1; \ 86 | status = napi_get_cb_info(env, info, &argc, &value, &jsthis, NULL);\ 87 | if (status != napi_ok) \ 88 | return (NULL); \ 89 | status = napi_unwrap(env, jsthis, (void**)&st);\ 90 | if (status != napi_ok) \ 91 | return (NULL); \ 92 | status = napi_get_value_##type(env, value, &st->field); \ 93 | if (status != napi_ok) \ 94 | return (NULL); \ 95 | return (NULL); \ 96 | } 97 | 98 | /** 99 | * For a given structure @p str with type @p type and with the 100 | * name @p field, define a getter and setter method to it. 101 | * 102 | * This macro is generic and should work for any primitive type 103 | * that is supported by C and JS. 104 | */ 105 | #define DEFINE_GETTER_AND_SETTER(str, field, type)\ 106 | DEFINE_GET(str, field, type)\ 107 | DEFINE_SET(str, field, type) 108 | 109 | /** 110 | * Since every structure/wrapper allocates C memory, this memory 111 | * should be release when out of context. Thankfully JS provides 112 | * us a way to do that by providing a finish method. 113 | */ 114 | #define DEFINE_FINISH_STRUCT(str)\ 115 | void str##_finish(napi_env env, void *finalize_data,\ 116 | void *finalize_hint) { \ 117 | ((void)finalize_hint); \ 118 | free(finalize_data); \ 119 | } 120 | 121 | /** 122 | * Define a tag for a given structure. 123 | * 124 | * Tag is a nice way to identify wrapped objects and ensure that 125 | * we're unwrapping the right JS objects. 126 | 127 | */ 128 | #define DEFINE_TAG(str, tag_lower, tag_upper)\ 129 | static const napi_type_tag str##_tag = {\ 130 | tag_lower, tag_upper \ 131 | }; 132 | 133 | /** 134 | * For a given structure @p str and a list of variadic arguments, 135 | * defines a structure wrapper for a JS object. 136 | * 137 | * This is the 'constructor' (actually just a function) for every 138 | * structure that wants to be wrapped by JS. 139 | */ 140 | #define DEFINE_STRUCT(str, ...)\ 141 | DEFINE_FINISH_STRUCT(str) \ 142 | static napi_value str(napi_env env, napi_callback_info info)\ 143 | {\ 144 | struct str *st; \ 145 | napi_status status; \ 146 | napi_value obj; \ 147 | \ 148 | st = calloc(1, sizeof(*st)); \ 149 | if (!st) \ 150 | return (NULL); \ 151 | \ 152 | /* Create 'object'. */ \ 153 | status = napi_create_object(env, &obj); \ 154 | if (status != napi_ok) \ 155 | return (NULL); \ 156 | \ 157 | /* Associates a tag with the object. */ \ 158 | status = napi_type_tag_object(env, obj, \ 159 | & str##_tag); \ 160 | if (status != napi_ok) \ 161 | return (NULL); \ 162 | \ 163 | /* Set variables. */ \ 164 | napi_property_descriptor properties[] = \ 165 | __VA_ARGS__; \ 166 | \ 167 | /* Define properties. */ \ 168 | status = napi_define_properties(env, obj, \ 169 | sizeof(properties)/sizeof(properties[0]), properties); \ 170 | \ 171 | if (status != napi_ok) \ 172 | return (NULL); \ 173 | \ 174 | /* Wrap everything. */ \ 175 | status = napi_wrap(env, obj, st, str##_finish, NULL, NULL); \ 176 | if (status != napi_ok) \ 177 | return (NULL); \ 178 | return (obj); \ 179 | } 180 | 181 | /** 182 | * This is a generic wrapper for void functions that return a simple 183 | * primitive type, compatible with C and JS. 184 | */ 185 | #define DEFINE_SIMPLE_WRAPPER_RET_VOID(name, js_ret, c_ret)\ 186 | static napi_value wrap_##name(napi_env env, napi_callback_info info)\ 187 | {\ 188 | napi_value napi_ret; /* JS return value. */ \ 189 | napi_status status; /* JS status code. */ \ 190 | c_ret func_ret; /* C return value. */ \ 191 | size_t argc; /* Argument count. */ \ 192 | \ 193 | /* Get arguments. */ \ 194 | argc = 0; \ 195 | status = napi_get_cb_info(env, info, &argc, NULL, NULL, NULL); \ 196 | if (status != napi_ok) \ 197 | return (NULL); \ 198 | \ 199 | /* Check number of arguments. */ \ 200 | if (argc != 0) \ 201 | {\ 202 | napi_throw_error(env, NULL,\ 203 | "Error, "#name " _do not_ expects any arguments!\n"); \ 204 | return (NULL); \ 205 | } \ 206 | \ 207 | /* Call C routine and return. */ \ 208 | func_ret = name(); \ 209 | \ 210 | status = napi_create_##js_ret(env, func_ret, &napi_ret); \ 211 | if (status != napi_ok) \ 212 | return (NULL); \ 213 | \ 214 | return (napi_ret); \ 215 | } 216 | 217 | #endif /* MACROS_H */ 218 | -------------------------------------------------------------------------------- /bindings/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Davidson Francis 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Get python include path: -I/something/ -I/something/ 16 | if("$ENV{PY_INC}" STREQUAL "") 17 | execute_process( 18 | COMMAND python-config --includes 19 | OUTPUT_VARIABLE PY_INC 20 | OUTPUT_STRIP_TRAILING_WHITESPACE 21 | ) 22 | else() 23 | set(PY_INC "$ENV{PY_INC}") 24 | endif("$ENV{PY_INC}" STREQUAL "") 25 | 26 | # If not, try to use Python 3 27 | if("${PY_INC}" STREQUAL "") 28 | message(WARNING "Python 2 not found, trying to use Python 3!") 29 | execute_process( 30 | COMMAND python3-config --includes 31 | OUTPUT_VARIABLE PY_INC 32 | OUTPUT_STRIP_TRAILING_WHITESPACE 33 | ) 34 | if("${PY_INC}" STREQUAL "") 35 | message(WARNING 36 | "Python 3 not found!, python bindings cannot be built without " 37 | "python-config/python3-config please check your python installation " 38 | "and/or manually define the variable PY_INC!") 39 | endif("${PY_INC}" STREQUAL "") 40 | endif("${PY_INC}" STREQUAL "") 41 | 42 | # Copy examples files 43 | file(COPY ${CMAKE_SOURCE_DIR}/bindings/python/examples 44 | DESTINATION ${CMAKE_BINARY_DIR}/bindings/python/ 45 | ) 46 | 47 | # 48 | # Get the first path component of PY_INC 49 | # Yes, this is ugly, but 'python-config' tries to imitate 50 | # pkg-config and appends '-I' to the path component.... 51 | # 52 | set(PY_LIST_PATH ${PY_INC}) 53 | separate_arguments(PY_LIST_PATH) 54 | list(GET PY_LIST_PATH 0 PY_INC_PATH) 55 | string(REPLACE "-I" "" PY_INC_PATH ${PY_INC_PATH}) 56 | 57 | # Invoke swig to generate libag_wrap.c 58 | add_custom_command( 59 | OUTPUT ${CMAKE_BINARY_DIR}/bindings/python/libag_wrap.c 60 | COMMAND swig -python -o 61 | ${CMAKE_BINARY_DIR}/bindings/python/libag_wrap.c 62 | -I${CMAKE_SOURCE_DIR} 63 | ${PY_INC} 64 | ${CMAKE_SOURCE_DIR}/bindings/python/libag.i 65 | ) 66 | 67 | # libag wrapper 68 | add_library(python-binding SHARED EXCLUDE_FROM_ALL 69 | ${CMAKE_BINARY_DIR}/bindings/python/libag_wrap.c 70 | $ 71 | ) 72 | 73 | set_target_properties(python-binding PROPERTIES 74 | PREFIX "_" 75 | OUTPUT_NAME "libag" 76 | ) 77 | target_include_directories(python-binding PRIVATE ${PY_INC_PATH}) 78 | target_link_libraries(python-binding pcre lzma z pthread) 79 | -------------------------------------------------------------------------------- /bindings/python/README.md: -------------------------------------------------------------------------------- 1 | # Python bindings 2 | 3 | ## Introduction 4 | Libag has a (very) experimental bindings support for Python 2/3. 5 | 6 | All the magic is done with the help of [SWIG](http://www.swig.org/), a program 7 | that, once an interface is specified, is able to generate a 'glue code' 8 | that is then built into a dynamic library to be loaded by CPython. 9 | It's an amazing kickstart for the 10 | ['traditional method'](https://docs.python.org/3.7/extending/extending.html). 11 | 12 | Personally speaking, it seemed simpler to use than ctypes: a lot already works 13 | out of the box, and the few situations that need special care can be handled via 14 | type maps, for example. Also, loading a dynamic library makes it possible to get 15 | the best out of libag performance in Python!. Furthermore, SWIG also allows 16 | the creation of bindings for other programming languages too =). 17 | 18 | ## Building 19 | The build process of the Python module can be divided into two simple steps: 20 | 21 | ### 1) Download & Build SWIG 22 | Download the latest version (or 23 | [v4.0.2](http://prdownloads.sourceforge.net/swig/swig-4.0.2.tar.gz), tested here) 24 | and compile as usual. Optionally you can also install. 25 | 26 | Something like: 27 | ```bash 28 | cd /path/to/swig 29 | wget http://prdownloads.sourceforge.net/swig/swig-4.0.2.tar.gz 30 | tar xf swig-*.tar.gz 31 | cd swig-*/ 32 | 33 | # If you want to install system-wide 34 | ./configure 35 | make -j4 36 | make install 37 | 38 | # If you want to specify a path 39 | ./configure --prefix=/some/folder 40 | make -j4 41 | make install 42 | ``` 43 | after the build, if you have not installed it system-wide (default path to 44 | /usr/local/bin), add swig to your `$PATH` or set the `$SWIG` environment variable 45 | pointing to the 'swig' binary. Both options are valid for libag Makefile. 46 | 47 | ### 2) Build libag 48 | Once you have SWIG built and set: 49 | ```bash 50 | cd libag/ 51 | make python-binding -j4 52 | ``` 53 | Several temporary files will be generated in the 54 | bindings/python directory, but the important ones are: `libag.py` and 55 | `_libag.so`. They are what you should take with you to use libag in any Python 56 | script :-). 57 | 58 | Please note that this 'extension module' has been compiled for your processor 59 | architecture and CPython version. If you want to use it in other environments, 60 | this process must be done again. 61 | 62 | #### Notes about Python versions: 63 | Some operating systems use Python 2 as the default version (even though they 64 | have both), others use Python 3, and others just version 3 but without an 65 | 'alias' for the 'python' command, but just 'python3'. 66 | 67 | The libag Makefile uses the `python-config` command to find the system's default 68 | Python include paths and populates the environment variable `$PY_INC` with that 69 | information. If you want to use a version other than the default, consider 70 | changing the environment variable to meet your expectations. If, for example, 71 | a system (which has both versions) uses Python 2 by default, and you want a 72 | build for v3, consider running: `export PY_INC=$(python3-config --includes)` 73 | before invoking the Makefile. 74 | 75 | It is also important to note that builds for Python 2 and 3 are not compatible 76 | with each other, i.e., a `_libag.so` compiled for Python 2 only works on Python 2, 77 | and vice versa. 78 | 79 | #### Notes about '`python-config`': 80 | Some systems (such as Ubuntu) do not provide Python headers by default, and 81 | consequently don't provide the `python-config` tool either. In these cases, it 82 | is necessary to install the package `python-dev` (for Python 2) or `python3-dev` 83 | (for Python 3). 84 | 85 | Package names and installation method may vary from distribution to distribution. 86 | 87 | ## Usage 88 | Since Python is not C, there are some slight differences in usage between the 89 | two languages. The following sections cover it in detail. 90 | 91 | ### Structures and Classes 92 | Thanks to SWIG, the relationship of structures and classes is transparent, which 93 | means that for every structure in C, there is an equivalent class in Python. 94 | Reading and writing values is as expected, as is passing parameters, which means: 95 | ```c 96 | /* C. */ 97 | struct ag_config config; 98 | memset(&config, 0, sizeof(struct ag_config)); 99 | config.search_binary_files = 1; 100 | config.num_workers = 4; 101 | 102 | ag_init_config(config) 103 | ``` 104 | is exactly equivalent to: 105 | ```python 106 | # Python 107 | config = ag_config() 108 | config.search_binary_files = 1 109 | config.num_workers = 4 110 | 111 | ag_init_config(config) 112 | ``` 113 | 114 | ### Functions 115 | Functions that take no parameters and/or that take structs and return primitive 116 | types have the expected behavior in both languages, which goes for: `ag_init()`, 117 | `ag_init_config()`, `ag_finish()`, `ag_set_config()`, `ag_get_stats()`, 118 | `ag_start_workers()`, `ag_stop_workers()` and `ag_free_result()`. 119 | 120 | Those that differ are listed in the table below: 121 | | Function | C | Python equivalent | 122 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 123 | | **ag_search** | Parameters:
query (**char \***), npaths (**int**), target_paths (**char\*\***), nresults (**size_t\***)

Return:
On success: **struct ag_result \*\***, nresults (**size_t\***)
On error: **null**, 0 | Parameters:
query (**string**), target_paths (list of strings)

Return:
On success: tuple of (nresults (integer), tuple(**ag_result**))
On error: tuple of (0, **Py_None**) | 124 | | **ag_search_ts** | Same as **ag_search** | | 125 | | **ag_free_all_results** | Parameters:
results (**struct ag_result \*\***), nresults (**size_t**)

Return: nothing | Parameters:
tuple(**ag_result**)

Return: nothing | 126 | 127 | Please note that although Python has a garbage collector, the memory allocated 128 | in `ag_search()` _needs_ to be freed via `ag_free_result()` or 129 | `ag_free_all_results()` (preferred). 130 | 131 | --- 132 | 133 | Examples of how to use bindings can be found in 134 | [bindings/python/examples](https://github.com/Theldus/libag/tree/master/bindings/python/examples). 135 | -------------------------------------------------------------------------------- /bindings/python/examples/init_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2021 Davidson Francis 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | sys.path.append("..") 19 | from libag import * 20 | 21 | if len(sys.argv) < 3: 22 | sys.stderr.write("Usage: {} \"regex\" [paths]\n".format(sys.argv[0])) 23 | sys.exit(1) 24 | 25 | # 4 workers and enable binary files search 26 | config = ag_config() 27 | config.search_binary_files = 1 28 | config.num_workers = 4 29 | 30 | # Initiate Ag library with default options. 31 | ag_init_config(config) 32 | 33 | # Search. 34 | nresults, results = ag_search(sys.argv[1], sys.argv[2:]) 35 | 36 | if nresults == 0: 37 | print("no result found") 38 | else: 39 | print("{} results found".format(nresults)) 40 | 41 | # Show them on the screen, if any. 42 | for file in results: 43 | for match in file.matches: 44 | print("file: {}, match: {}, start: {} / end: {}, is_binary: {}". 45 | format(file.file, match.match, match.byte_start, match.byte_end, 46 | (not not file.flags & LIBAG_FLG_BINARY))) 47 | 48 | # Free all resources. 49 | if nresults: 50 | ag_free_all_results(results) 51 | 52 | # Release Ag resources. 53 | ag_finish() 54 | -------------------------------------------------------------------------------- /bindings/python/examples/simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2021 Davidson Francis 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | sys.path.append("..") 19 | from libag import * 20 | 21 | if len(sys.argv) < 3: 22 | sys.stderr.write("Usage: {} \"regex\" [paths]\n".format(sys.argv[0])) 23 | sys.exit(1) 24 | 25 | # Initiate Ag library with default options. 26 | ag_init() 27 | 28 | # Search. 29 | nresults, results = ag_search(sys.argv[1], sys.argv[2:]) 30 | 31 | if nresults == 0: 32 | print("no result found") 33 | else: 34 | print("{} results found".format(nresults)) 35 | 36 | # Show them on the screen, if any. 37 | for file in results: 38 | for match in file.matches: 39 | print("file: {}, match: {}, start: {} / end: {}". 40 | format(file.file, match.match, match.byte_start, match.byte_end)) 41 | 42 | # Free all resources. 43 | if nresults: 44 | ag_free_all_results(results) 45 | 46 | # Release Ag resources. 47 | ag_finish() 48 | -------------------------------------------------------------------------------- /bindings/python/libag.i: -------------------------------------------------------------------------------- 1 | %module libag 2 | 3 | %{ 4 | #include "libag.h" 5 | %} 6 | 7 | %include exception.i 8 | %include typemaps.i 9 | 10 | /* 11 | * Maps a list to int npaths, char **target_paths 12 | * Example: ["foo"] -> npaths = 1, target_paths[0] = "foo" 13 | * 14 | * This is specially used in the ag_search() routine. 15 | * 16 | * Typemap extracted from SWIG v4.0.2. in: 17 | * Examples/python/multimap/example.i 18 | */ 19 | %typemap(in,fragment="t_output_helper") (int npaths, char **target_paths) 20 | { 21 | int i; 22 | if (!PyList_Check($input)) 23 | SWIG_exception(SWIG_ValueError, "Expecting a list"); 24 | 25 | $1 = (int)PyList_Size($input); 26 | if ($1 == 0) 27 | SWIG_exception(SWIG_ValueError, "List must contain at least 1 element"); 28 | 29 | $2 = malloc(($1+1)*sizeof(char *)); 30 | 31 | for (i = 0; i < $1; i++) 32 | { 33 | PyObject *s = PyList_GetItem($input,i); 34 | %#if PY_VERSION_HEX >= 0x03000000 35 | if (!PyUnicode_Check(s)) 36 | %#else 37 | if (!PyString_Check(s)) 38 | %#endif 39 | { 40 | free($2); 41 | SWIG_exception(SWIG_ValueError, "List items must be strings"); 42 | } 43 | %#if PY_VERSION_HEX >= 0x03000000 44 | { 45 | PyObject *utf8str = PyUnicode_AsUTF8String(s); 46 | const char *cstr; 47 | if (!utf8str) 48 | SWIG_fail; 49 | cstr = PyBytes_AsString(utf8str); 50 | $2[i] = strdup(cstr); 51 | Py_DECREF(utf8str); 52 | } 53 | %#else 54 | $2[i] = PyString_AsString(s); 55 | %#endif 56 | } 57 | $2[i] = 0; 58 | } 59 | 60 | /* 61 | * Complementary typemap from the above typemap =). 62 | */ 63 | %typemap(freearg) (int count, char *argv[]) 64 | { 65 | %#if PY_VERSION_HEX >= 0x03000000 66 | int i; 67 | for (i = 0; i < $1; i++) 68 | free($2[i]); 69 | %#endif 70 | } 71 | 72 | /* 73 | * Typemap that maps a tuple to the arguments of the ag_free_all_results 74 | * routine. This typemap recreates the (struct ag_results **) based on 75 | * the individual elements present in the tuple and feeds that into the 76 | * function. There is no need to free the pointer here, as 77 | * ag_free_all_results already does. 78 | */ 79 | %typemap(in) (struct ag_result **results, size_t nresults) 80 | { 81 | struct ag_result *r_c; 82 | PyObject *r_py; 83 | size_t i; 84 | 85 | if (!PyTuple_Check($input)) 86 | SWIG_exception(SWIG_ValueError, "Expecting a tuple"); 87 | 88 | /* nresults argument. */ 89 | $2 = (size_t)PyTuple_Size($input); 90 | if (!$2) 91 | SWIG_exception(SWIG_ValueError, "Tuple must not be empty!"); 92 | 93 | /* struct ag_result **results, argument. */ 94 | $1 = malloc($2 * sizeof(struct person *)); 95 | 96 | /* Add all tuple elements into the structure. */ 97 | for (i = 0; i < $2; i++) 98 | { 99 | r_py = PyTuple_GetItem($input, i); 100 | 101 | /* Get C pointer from PyObject. */ 102 | if (SWIG_ConvertPtr(r_py, (void **)&r_c, SWIGTYPE_p_ag_result, 103 | SWIG_POINTER_EXCEPTION) == -1) 104 | { 105 | return NULL; 106 | } 107 | $1[i] = r_c; 108 | } 109 | /* Pointer is freed via the proper function. */ 110 | } 111 | 112 | /* 113 | * Typemap that converts an 'struct ag_result **' to a Python tuple. 114 | * Each tuple element is a 'struct ag_result *' and still requires 115 | * to do a proper cleanup later. 116 | * 117 | * Please note that this typemap is different from the one below 118 | * (ag_match): it releases the first pointer, and we rebuild it 119 | * again into another typemap. This proved to be necessary since 120 | * when we return a tuple, we lose the address of the first pointer, 121 | * which would be a memory leak, so we release it. 122 | * 123 | * In ag_match's typemap, this is not necessary, as the original 124 | * address is always saved in the structure itself, and we can 125 | * clear everything later (in the ag_free_result/ag_free_all_results 126 | * routines). 127 | * 128 | */ 129 | %typemap(out) struct ag_result ** 130 | { 131 | PyObject *list; 132 | PyObject *py_ptr; 133 | struct ag_result **tmp; 134 | struct ag_result *p; 135 | size_t size; 136 | 137 | if (!$1) 138 | { 139 | $result = Py_None; 140 | goto out; 141 | } 142 | 143 | size = 0; 144 | tmp = $1; 145 | 146 | while (*tmp != NULL) 147 | { 148 | size++; 149 | tmp++; 150 | } 151 | 152 | $result = PyTuple_New(size); 153 | if (!$result) 154 | { 155 | PyErr_SetString(PyExc_Exception, 156 | "(TypeMap ag_result) Cannot allocate Tuple"); 157 | return (NULL); 158 | } 159 | 160 | for (size_t i = 0; i < size; ++i) 161 | { 162 | p = $1[i]; 163 | py_ptr = SWIG_NewPointerObj(SWIG_as_voidptr(p), 164 | SWIGTYPE_p_ag_result, 0|0); 165 | PyTuple_SetItem($result, i, py_ptr); 166 | } 167 | free($1); 168 | out: 169 | ; 170 | } 171 | 172 | /* 173 | * Typemap that converts an 'struct ag_match **' to a Python tuple. 174 | * Each tuple element is a 'struct ag_match *' and still requires 175 | * to do a proper cleanup later. 176 | */ 177 | %typemap(out) struct ag_match ** 178 | { 179 | PyObject *py_ptr; 180 | struct ag_match **tmp; 181 | struct ag_match *p; 182 | size_t size; 183 | 184 | if (!$1) 185 | { 186 | $result = Py_None; 187 | goto out; 188 | } 189 | 190 | size = 0; 191 | tmp = $1; 192 | 193 | while (*tmp != NULL) 194 | { 195 | size++; 196 | tmp++; 197 | } 198 | 199 | $result = PyTuple_New(size); 200 | if (!$result) 201 | { 202 | PyErr_SetString(PyExc_Exception, "(TypeMap) Cannot allocate Tuple"); 203 | return (NULL); 204 | } 205 | 206 | for (size_t i = 0; i < size; ++i) 207 | { 208 | p = $1[i]; 209 | py_ptr = SWIG_NewPointerObj(SWIG_as_voidptr(p), 210 | SWIGTYPE_p_ag_match, 0|0); 211 | PyTuple_SetItem($result, i, py_ptr); 212 | } 213 | /* Not required a free here, since we do not lost the pointer. */ 214 | out: 215 | ; 216 | } 217 | 218 | /* 219 | * Typemap that maps the return _and_ the 'size_t *nresults' 220 | * argument of the ag_search routine: both 'get merged' and become 221 | * a tuple. So the expected way to invoke ag_search (in Python) 222 | * would be: 223 | * 224 | * nresults, results = ag_search("query", [list]); 225 | * 226 | * Where 'nresults' stores the number of results (0 if none) and 227 | * 'results' the result tuple (None if none). 228 | */ 229 | %typemap(in, numinputs=0) size_t *nresults(size_t tmp) { 230 | $1 = &tmp; 231 | } 232 | 233 | %typemap(argout) size_t *nresults 234 | { 235 | PyObject *old_result; 236 | old_result = $result; 237 | 238 | $result = PyTuple_New(2); 239 | if (!$result) 240 | { 241 | PyErr_SetString(PyExc_Exception, "Cannot allocate Tuple"); 242 | return (NULL); 243 | } 244 | 245 | /* Check if have some output, if not, set tuple (0, None). */ 246 | if (!old_result || (old_result == Py_None)) 247 | { 248 | PyTuple_SetItem($result, 0, PyLong_FromSize_t(0)); 249 | PyTuple_SetItem($result, 1, Py_None); 250 | } 251 | 252 | else 253 | { 254 | /* Add nresult. */ 255 | PyTuple_SetItem($result, 0, PyLong_FromSize_t(*$1)); 256 | /* Add proper result. */ 257 | PyTuple_SetItem($result, 1, old_result); 258 | } 259 | } 260 | 261 | %include "libag.h" 262 | -------------------------------------------------------------------------------- /doc/libag.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ 3 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 4 | Name: libag 5 | Description: The Silver Searcher Library 6 | Version: 1.0 7 | Libs: -L${libdir} -lag 8 | Libs.private: -lpcre -lzma -lz -pthread 9 | Cflags: -I${includedir} 10 | -------------------------------------------------------------------------------- /doc/man1/ag.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "AG" "1" "December 2016" "" "" 5 | . 6 | .SH "NAME" 7 | \fBag\fR \- The Silver Searcher\. Like ack, but faster\. 8 | . 9 | .SH "SYNOPSIS" 10 | \fBag\fR [\fIoptions\fR] \fIpattern\fR [\fIpath \.\.\.\fR] 11 | . 12 | .SH "DESCRIPTION" 13 | Recursively search for PATTERN in PATH\. Like grep or ack, but faster\. 14 | . 15 | .SH "OPTIONS" 16 | . 17 | .TP 18 | \fB\-\-ackmate\fR 19 | Output results in a format parseable by AckMate \fIhttps://github\.com/protocool/AckMate\fR\. 20 | . 21 | .TP 22 | \fB\-\-[no]affinity\fR 23 | Set thread affinity (if platform supports it)\. Default is true\. 24 | . 25 | .TP 26 | \fB\-a \-\-all\-types\fR 27 | Search all files\. This doesn\'t include hidden files, and doesn\'t respect any ignore files\. 28 | . 29 | .TP 30 | \fB\-A \-\-after [LINES]\fR 31 | Print lines after match\. If not provided, LINES defaults to 2\. 32 | . 33 | .TP 34 | \fB\-B \-\-before [LINES]\fR 35 | Print lines before match\. If not provided, LINES defaults to 2\. 36 | . 37 | .TP 38 | \fB\-\-[no]break\fR 39 | Print a newline between matches in different files\. Enabled by default\. 40 | . 41 | .TP 42 | \fB\-c \-\-count\fR 43 | Only print the number of matches in each file\. Note: This is the number of matches, \fBnot\fR the number of matching lines\. Pipe output to \fBwc \-l\fR if you want the number of matching lines\. 44 | . 45 | .TP 46 | \fB\-\-[no]color\fR 47 | Print color codes in results\. Enabled by default\. 48 | . 49 | .TP 50 | \fB\-\-color\-line\-number\fR 51 | Color codes for line numbers\. Default is 1;33\. 52 | . 53 | .TP 54 | \fB\-\-color\-match\fR 55 | Color codes for result match numbers\. Default is 30;43\. 56 | . 57 | .TP 58 | \fB\-\-color\-path\fR 59 | Color codes for path names\. Default is 1;32\. 60 | . 61 | .TP 62 | \fB\-\-column\fR 63 | Print column numbers in results\. 64 | . 65 | .TP 66 | \fB\-C \-\-context [LINES]\fR 67 | Print lines before and after matches\. Default is 2\. 68 | . 69 | .TP 70 | \fB\-D \-\-debug\fR 71 | Output ridiculous amounts of debugging info\. Not useful unless you\'re actually debugging\. 72 | . 73 | .TP 74 | \fB\-\-depth NUM\fR 75 | Search up to NUM directories deep, \-1 for unlimited\. Default is 25\. 76 | . 77 | .TP 78 | \fB\-\-[no]filename\fR 79 | Print file names\. Enabled by default, except when searching a single file\. 80 | . 81 | .TP 82 | \fB\-f \-\-[no]follow\fR 83 | Follow symlinks\. Default is false\. 84 | . 85 | .TP 86 | \fB\-F \-\-fixed\-strings\fR 87 | Alias for \-\-literal for compatibility with grep\. 88 | . 89 | .TP 90 | \fB\-\-[no]group\fR 91 | The default, \fB\-\-group\fR, lumps multiple matches in the same file together, and presents them under a single occurrence of the filename\. \fB\-\-nogroup\fR refrains from this, and instead places the filename at the start of each match line\. 92 | . 93 | .TP 94 | \fB\-g PATTERN\fR 95 | Print filenames matching PATTERN\. 96 | . 97 | .TP 98 | \fB\-G \-\-file\-search\-regex PATTERN\fR 99 | Only search files whose names match PATTERN\. 100 | . 101 | .TP 102 | \fB\-H \-\-[no]heading\fR 103 | Print filenames above matching contents\. 104 | . 105 | .TP 106 | \fB\-\-hidden\fR 107 | Search hidden files\. This option obeys ignored files\. 108 | . 109 | .TP 110 | \fB\-\-ignore PATTERN\fR 111 | Ignore files/directories whose names match this pattern\. Literal file and directory names are also allowed\. 112 | . 113 | .TP 114 | \fB\-\-ignore\-dir NAME\fR 115 | Alias for \-\-ignore for compatibility with ack\. 116 | . 117 | .TP 118 | \fB\-i \-\-ignore\-case\fR 119 | Match case\-insensitively\. 120 | . 121 | .TP 122 | \fB\-l \-\-files\-with\-matches\fR 123 | Only print the names of files containing matches, not the matching lines\. An empty query will print all files that would be searched\. 124 | . 125 | .TP 126 | \fB\-L \-\-files\-without\-matches\fR 127 | Only print the names of files that don\'t contain matches\. 128 | . 129 | .TP 130 | \fB\-\-list\-file\-types\fR 131 | See \fBFILE TYPES\fR below\. 132 | . 133 | .TP 134 | \fB\-m \-\-max\-count NUM\fR 135 | Skip the rest of a file after NUM matches\. Default is 0, which never skips\. 136 | . 137 | .TP 138 | \fB\-\-[no]mmap\fR 139 | Toggle use of memory\-mapped I/O\. Defaults to true on platforms where \fBmmap()\fR is faster than \fBread()\fR\. (All but macOS\.) 140 | . 141 | .TP 142 | \fB\-\-[no]multiline\fR 143 | Match regexes across newlines\. Enabled by default\. 144 | . 145 | .TP 146 | \fB\-n \-\-norecurse\fR 147 | Don\'t recurse into directories\. 148 | . 149 | .TP 150 | \fB\-\-[no]numbers\fR 151 | Print line numbers\. Default is to omit line numbers when searching streams\. 152 | . 153 | .TP 154 | \fB\-o \-\-only\-matching\fR 155 | Print only the matching part of the lines\. 156 | . 157 | .TP 158 | \fB\-\-one\-device\fR 159 | When recursing directories, don\'t scan dirs that reside on other storage devices\. This lets you avoid scanning slow network mounts\. This feature is not supported on all platforms\. 160 | . 161 | .TP 162 | \fB\-p \-\-path\-to\-ignore STRING\fR 163 | Provide a path to a specific \.ignore file\. 164 | . 165 | .TP 166 | \fB\-\-pager COMMAND\fR 167 | Use a pager such as \fBless\fR\. Use \fB\-\-nopager\fR to override\. This option is also ignored if output is piped to another program\. 168 | . 169 | .TP 170 | \fB\-\-parallel\fR 171 | Parse the input stream as a search term, not data to search\. This is meant to be used with tools such as GNU parallel\. For example: \fBecho "foo\enbar\enbaz" | parallel "ag {} \."\fR will run 3 instances of ag, searching the current directory for "foo", "bar", and "baz"\. 172 | . 173 | .TP 174 | \fB\-\-print\-long\-lines\fR 175 | Print matches on very long lines (> 2k characters by default)\. 176 | . 177 | .TP 178 | \fB\-\-passthrough \-\-passthru\fR 179 | When searching a stream, print all lines even if they don\'t match\. 180 | . 181 | .TP 182 | \fB\-Q \-\-literal\fR 183 | Do not parse PATTERN as a regular expression\. Try to match it literally\. 184 | . 185 | .TP 186 | \fB\-r \-\-recurse\fR 187 | Recurse into directories when searching\. Default is true\. 188 | . 189 | .TP 190 | \fB\-s \-\-case\-sensitive\fR 191 | Match case\-sensitively\. 192 | . 193 | .TP 194 | \fB\-S \-\-smart\-case\fR 195 | Match case\-sensitively if there are any uppercase letters in PATTERN, case\-insensitively otherwise\. Enabled by default\. 196 | . 197 | .TP 198 | \fB\-\-search\-binary\fR 199 | Search binary files for matches\. 200 | . 201 | .TP 202 | \fB\-\-silent\fR 203 | Suppress all log messages, including errors\. 204 | . 205 | .TP 206 | \fB\-\-stats\fR 207 | Print stats (files scanned, time taken, etc)\. 208 | . 209 | .TP 210 | \fB\-\-stats\-only\fR 211 | Print stats (files scanned, time taken, etc) and nothing else\. 212 | . 213 | .TP 214 | \fB\-t \-\-all\-text\fR 215 | Search all text files\. This doesn\'t include hidden files\. 216 | . 217 | .TP 218 | \fB\-u \-\-unrestricted\fR 219 | Search \fIall\fR files\. This ignores \.ignore, \.gitignore, etc\. It searches binary and hidden files as well\. 220 | . 221 | .TP 222 | \fB\-U \-\-skip\-vcs\-ignores\fR 223 | Ignore VCS ignore files (\.gitignore, \.hgignore), but still use \.ignore\. 224 | . 225 | .TP 226 | \fB\-v \-\-invert\-match\fR 227 | Match every line \fInot\fR containing the specified pattern\. 228 | . 229 | .TP 230 | \fB\-V \-\-version\fR 231 | Print version info\. 232 | . 233 | .TP 234 | \fB\-\-vimgrep\fR 235 | Output results in the same form as Vim\'s \fB:vimgrep /pattern/g\fR 236 | . 237 | .IP 238 | Here is a ~/\.vimrc configuration example: 239 | . 240 | .IP 241 | \fBset grepprg=ag\e \-\-vimgrep\e $*\fR \fBset grepformat=%f:%l:%c:%m\fR 242 | . 243 | .IP 244 | Then use \fB:grep\fR to grep for something\. Then use \fB:copen\fR, \fB:cn\fR, \fB:cp\fR, etc\. to navigate through the matches\. 245 | . 246 | .TP 247 | \fB\-w \-\-word\-regexp\fR 248 | Only match whole words\. 249 | . 250 | .TP 251 | \fB\-\-workers NUM\fR 252 | Use NUM worker threads\. Default is the number of CPU cores, with a max of 8\. 253 | . 254 | .TP 255 | \fB\-z \-\-search\-zip\fR 256 | Search contents of compressed files\. Currently, gz and xz are supported\. This option requires that ag is built with lzma and zlib\. 257 | . 258 | .TP 259 | \fB\-0 \-\-null \-\-print0\fR 260 | Separate the filenames with \fB\e0\fR, rather than \fB\en\fR: this allows \fBxargs \-0 \fR to correctly process filenames containing spaces or newlines\. 261 | . 262 | .SH "FILE TYPES" 263 | It is possible to restrict the types of files searched\. For example, passing \fB\-\-html\fR will search only files with the extensions \fBhtm\fR, \fBhtml\fR, \fBshtml\fR or \fBxhtml\fR\. For a list of supported types, run \fBag \-\-list\-file\-types\fR\. 264 | . 265 | .SH "IGNORING FILES" 266 | By default, ag will ignore files whose names match patterns in \.gitignore, \.hgignore, or \.ignore\. These files can be anywhere in the directories being searched\. Binary files are ignored by default as well\. Finally, ag looks in $HOME/\.agignore for ignore patterns\. 267 | . 268 | .P 269 | If you want to ignore \.gitignore and \.hgignore, but still take \.ignore into account, use \fB\-U\fR\. 270 | . 271 | .P 272 | Use the \fB\-t\fR option to search all text files; \fB\-a\fR to search all files; and \fB\-u\fR to search all, including hidden files\. 273 | . 274 | .SH "EXAMPLES" 275 | \fBag printf\fR: Find matches for "printf" in the current directory\. 276 | . 277 | .P 278 | \fBag foo /bar/\fR: Find matches for "foo" in path /bar/\. 279 | . 280 | .P 281 | \fBag \-\- \-\-foo\fR: Find matches for "\-\-foo" in the current directory\. (As with most UNIX command line utilities, "\-\-" is used to signify that the remaining arguments should not be treated as options\.) 282 | . 283 | .SH "ABOUT" 284 | ag was originally created by Geoff Greer\. More information (and the latest release) can be found at http://geoff\.greer\.fm/ag 285 | . 286 | .SH "SEE ALSO" 287 | grep(1) 288 | -------------------------------------------------------------------------------- /doc/man1/ag.1.md: -------------------------------------------------------------------------------- 1 | ag(1) -- The Silver Searcher. Like ack, but faster. 2 | ============================================= 3 | 4 | ## SYNOPSIS 5 | 6 | `ag` [_options_] _pattern_ [_path ..._] 7 | 8 | ## DESCRIPTION 9 | 10 | Recursively search for PATTERN in PATH. Like grep or ack, but faster. 11 | 12 | ## OPTIONS 13 | 14 | * `--ackmate`: 15 | Output results in a format parseable by [AckMate](https://github.com/protocool/AckMate). 16 | 17 | * `--[no]affinity`: 18 | Set thread affinity (if platform supports it). Default is true. 19 | 20 | * `-a --all-types`: 21 | Search all files. This doesn't include hidden files, and doesn't respect any ignore files. 22 | 23 | * `-A --after [LINES]`: 24 | Print lines after match. If not provided, LINES defaults to 2. 25 | 26 | * `-B --before [LINES]`: 27 | Print lines before match. If not provided, LINES defaults to 2. 28 | 29 | * `--[no]break`: 30 | Print a newline between matches in different files. Enabled by default. 31 | 32 | * `-c --count`: 33 | Only print the number of matches in each file. 34 | Note: This is the number of matches, **not** the number of matching lines. 35 | Pipe output to `wc -l` if you want the number of matching lines. 36 | 37 | * `--[no]color`: 38 | Print color codes in results. Enabled by default. 39 | 40 | * `--color-line-number`: 41 | Color codes for line numbers. Default is 1;33. 42 | 43 | * `--color-match`: 44 | Color codes for result match numbers. Default is 30;43. 45 | 46 | * `--color-path`: 47 | Color codes for path names. Default is 1;32. 48 | 49 | * `--column`: 50 | Print column numbers in results. 51 | 52 | * `-C --context [LINES]`: 53 | Print lines before and after matches. Default is 2. 54 | 55 | * `-D --debug`: 56 | Output ridiculous amounts of debugging info. Not useful unless you're actually debugging. 57 | 58 | * `--depth NUM`: 59 | Search up to NUM directories deep, -1 for unlimited. Default is 25. 60 | 61 | * `--[no]filename`: 62 | Print file names. Enabled by default, except when searching a single file. 63 | 64 | * `-f --[no]follow`: 65 | Follow symlinks. Default is false. 66 | 67 | * `-F --fixed-strings`: 68 | Alias for --literal for compatibility with grep. 69 | 70 | * `--[no]group`: 71 | The default, `--group`, lumps multiple matches in the same file 72 | together, and presents them under a single occurrence of the 73 | filename. `--nogroup` refrains from this, and instead places the 74 | filename at the start of each match line. 75 | 76 | * `-g PATTERN`: 77 | Print filenames matching PATTERN. 78 | 79 | * `-G --file-search-regex PATTERN`: 80 | Only search files whose names match PATTERN. 81 | 82 | * `-H --[no]heading`: 83 | Print filenames above matching contents. 84 | 85 | * `--hidden`: 86 | Search hidden files. This option obeys ignored files. 87 | 88 | * `--ignore PATTERN`: 89 | Ignore files/directories whose names match this pattern. Literal 90 | file and directory names are also allowed. 91 | 92 | * `--ignore-dir NAME`: 93 | Alias for --ignore for compatibility with ack. 94 | 95 | * `-i --ignore-case`: 96 | Match case-insensitively. 97 | 98 | * `-l --files-with-matches`: 99 | Only print the names of files containing matches, not the matching 100 | lines. An empty query will print all files that would be searched. 101 | 102 | * `-L --files-without-matches`: 103 | Only print the names of files that don't contain matches. 104 | 105 | * `--list-file-types`: 106 | See `FILE TYPES` below. 107 | 108 | * `-m --max-count NUM`: 109 | Skip the rest of a file after NUM matches. Default is 0, which never skips. 110 | 111 | * `--[no]mmap`: 112 | Toggle use of memory-mapped I/O. Defaults to true on platforms where 113 | `mmap()` is faster than `read()`. (All but macOS.) 114 | 115 | * `--[no]multiline`: 116 | Match regexes across newlines. Enabled by default. 117 | 118 | * `-n --norecurse`: 119 | Don't recurse into directories. 120 | 121 | * `--[no]numbers`: 122 | Print line numbers. Default is to omit line numbers when searching streams. 123 | 124 | * `-o --only-matching`: 125 | Print only the matching part of the lines. 126 | 127 | * `--one-device`: 128 | When recursing directories, don't scan dirs that reside on other storage 129 | devices. This lets you avoid scanning slow network mounts. 130 | This feature is not supported on all platforms. 131 | 132 | * `-p --path-to-ignore STRING`: 133 | Provide a path to a specific .ignore file. 134 | 135 | * `--pager COMMAND`: 136 | Use a pager such as `less`. Use `--nopager` to override. This option 137 | is also ignored if output is piped to another program. 138 | 139 | * `--parallel`: 140 | Parse the input stream as a search term, not data to search. This is meant 141 | to be used with tools such as GNU parallel. For example: 142 | `echo "foo\nbar\nbaz" | parallel "ag {} ."` will run 3 instances of ag, 143 | searching the current directory for "foo", "bar", and "baz". 144 | 145 | * `--print-long-lines`: 146 | Print matches on very long lines (> 2k characters by default). 147 | 148 | * `--passthrough --passthru`: 149 | When searching a stream, print all lines even if they don't match. 150 | 151 | * `-Q --literal`: 152 | Do not parse PATTERN as a regular expression. Try to match it literally. 153 | 154 | * `-r --recurse`: 155 | Recurse into directories when searching. Default is true. 156 | 157 | * `-s --case-sensitive`: 158 | Match case-sensitively. 159 | 160 | * `-S --smart-case`: 161 | Match case-sensitively if there are any uppercase letters in PATTERN, 162 | case-insensitively otherwise. Enabled by default. 163 | 164 | * `--search-binary`: 165 | Search binary files for matches. 166 | 167 | * `--silent`: 168 | Suppress all log messages, including errors. 169 | 170 | * `--stats`: 171 | Print stats (files scanned, time taken, etc). 172 | 173 | * `--stats-only`: 174 | Print stats (files scanned, time taken, etc) and nothing else. 175 | 176 | * `-t --all-text`: 177 | Search all text files. This doesn't include hidden files. 178 | 179 | * `-u --unrestricted`: 180 | Search *all* files. This ignores .ignore, .gitignore, etc. It searches 181 | binary and hidden files as well. 182 | 183 | * `-U --skip-vcs-ignores`: 184 | Ignore VCS ignore files (.gitignore, .hgignore), but still 185 | use .ignore. 186 | 187 | * `-v --invert-match`: 188 | Match every line *not* containing the specified pattern. 189 | 190 | * `-V --version`: 191 | Print version info. 192 | 193 | * `--vimgrep`: 194 | Output results in the same form as Vim's `:vimgrep /pattern/g` 195 | 196 | Here is a ~/.vimrc configuration example: 197 | 198 | `set grepprg=ag\ --vimgrep\ $*` 199 | `set grepformat=%f:%l:%c:%m` 200 | 201 | Then use `:grep` to grep for something. 202 | Then use `:copen`, `:cn`, `:cp`, etc. to navigate through the matches. 203 | 204 | * `-w --word-regexp`: 205 | Only match whole words. 206 | 207 | * `--workers NUM`: 208 | Use NUM worker threads. Default is the number of CPU cores, with a max of 8. 209 | 210 | * `-W --width NUM`: 211 | Truncate match lines after NUM characters. 212 | 213 | * `-z --search-zip`: 214 | Search contents of compressed files. Currently, gz and xz are supported. 215 | This option requires that ag is built with lzma and zlib. 216 | 217 | * `-0 --null --print0`: 218 | Separate the filenames with `\0`, rather than `\n`: 219 | this allows `xargs -0 ` to correctly process filenames containing 220 | spaces or newlines. 221 | 222 | 223 | ## FILE TYPES 224 | 225 | It is possible to restrict the types of files searched. For example, passing 226 | `--html` will search only files with the extensions `htm`, `html`, `shtml` 227 | or `xhtml`. For a list of supported types, run `ag --list-file-types`. 228 | 229 | ## IGNORING FILES 230 | 231 | By default, ag will ignore files whose names match patterns in .gitignore, 232 | .hgignore, or .ignore. These files can be anywhere in the directories being 233 | searched. Binary files are ignored by default as well. Finally, ag looks in 234 | $HOME/.agignore for ignore patterns. 235 | 236 | If you want to ignore .gitignore and .hgignore, but still take .ignore into 237 | account, use `-U`. 238 | 239 | Use the `-t` option to search all text files; `-a` to search all files; and `-u` 240 | to search all, including hidden files. 241 | 242 | ## EXAMPLES 243 | 244 | `ag printf`: 245 | Find matches for "printf" in the current directory. 246 | 247 | `ag foo /bar/`: 248 | Find matches for "foo" in path /bar/. 249 | 250 | `ag -- --foo`: 251 | Find matches for "--foo" in the current directory. (As with most UNIX command 252 | line utilities, "--" is used to signify that the remaining arguments should 253 | not be treated as options.) 254 | 255 | ## ABOUT 256 | 257 | ag was originally created by Geoff Greer. More information (and the latest 258 | release) can be found at http://geoff.greer.fm/ag 259 | 260 | ## SEE ALSO 261 | 262 | grep(1) 263 | -------------------------------------------------------------------------------- /doc/man1/generate_man.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ronn is used to turn the markdown into a manpage. 4 | # Get ronn at https://github.com/rtomayko/ronn 5 | # Alternately, since ronn is a Ruby gem, you can just 6 | # `gem install ronn` 7 | 8 | sed -e 's/\\0/\\\\0/' ag.1.md.tmp 9 | ronn -r ag.1.md.tmp 10 | 11 | rm -f ag.1.md.tmp 12 | -------------------------------------------------------------------------------- /doc/man3/ag_finish.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_finish \- Releases all resources belonging to Ag/libag 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "int ag_finish(void);" 24 | .fi 25 | .SH DESCRIPTION 26 | The 27 | .BR ag_finish () 28 | finishes, i.e, releases all resources that Ag/libag have used during its 29 | execution. Any subsequent call to 30 | .BR ag_search () 31 | will be considered an error, until 32 | .BR ag_init () 33 | (or 34 | .BR ag_init_config ()) 35 | is invoked again. 36 | 37 | .SH RETURN VALUE 38 | Returns 0 if success, -1 otherwise. 39 | 40 | .SH NOTES 41 | Please note that 42 | .BR ag_finish () 43 | do not releases any memory allocated by 44 | .BR ag_search (). 45 | It is up to the user to deallocate that memory, whether by iterating 46 | over the structure, or by using the helper functions: 47 | .BR ag_free_result () 48 | and 49 | .BR ag_free_all_results (). 50 | 51 | .SH SEE ALSO 52 | .BR ag_init (3), 53 | .BR ag_init_config (3), 54 | .BR ag_search (3), 55 | .BR ag_free_result (3), 56 | .BR ag_free_all_results (3) 57 | 58 | .SH AUTHOR 59 | Davidson Francis (davidsondfgl@gmail.com) 60 | -------------------------------------------------------------------------------- /doc/man3/ag_free_all_results.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_free_all_results \- Free all results returned by 19 | .B ag_search 20 | .SH SYNOPSIS 21 | .nf 22 | .B #include 23 | .sp 24 | .BI "int ag_free_all_results(struct ag_result **" results ", size_t " nresults ");" 25 | .fi 26 | .SH DESCRIPTION 27 | .BR ag_free_all_results () 28 | frees all 29 | .I nresults 30 | specified by 31 | .I results 32 | returned from a successful call to 33 | .IR ag_search . 34 | 35 | .SH RETURN VALUE 36 | The function does not return any value. 37 | 38 | .SH SEE ALSO 39 | .BR ag_free_result (3), 40 | .BR ag_search (3) 41 | 42 | .SH AUTHOR 43 | Davidson Francis (davidsondfgl@gmail.com) 44 | -------------------------------------------------------------------------------- /doc/man3/ag_free_result.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_free_result \- Free a single result returned by 19 | .B ag_search 20 | .SH SYNOPSIS 21 | .nf 22 | .B #include 23 | .sp 24 | .BI "int ag_free_result(struct ag_result *" result ");" 25 | .fi 26 | .SH DESCRIPTION 27 | .BR ag_free_result () 28 | frees a single result specified by 29 | .I result 30 | returned from a successful call to 31 | .IR ag_search . 32 | 33 | .SH RETURN VALUE 34 | The function does not return any value. 35 | 36 | .SH SEE ALSO 37 | .BR ag_free_all_results (3), 38 | .BR ag_search (3) 39 | 40 | .SH AUTHOR 41 | Davidson Francis (davidsondfgl@gmail.com) 42 | -------------------------------------------------------------------------------- /doc/man3/ag_get_stats.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_get_stats \- If enabled, retrieve the stats for the latest search 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "int ag_get_stats(struct ag_search_stats *" stats ");" 24 | .fi 25 | .SH DESCRIPTION 26 | If stats are enabled, 27 | .BR ag_get_stats () 28 | function gets the stats for the latest 29 | .I ag_search 30 | call and saves it into 31 | .IR stats . 32 | The stats contains: 33 | 34 | .PP 35 | .RS 2 36 | .IP \(em 2 37 | Total bytes searched 38 | .IP \(em 2 39 | Total files read 40 | .IP \(em 2 41 | Total matches 42 | .IP \(em 2 43 | Total files with matches 44 | .PP 45 | 46 | .SH RETURN VALUE 47 | Returns 0 if success, -1 otherwise. 48 | 49 | .SH AUTHOR 50 | Davidson Francis (davidsondfgl@gmail.com) 51 | -------------------------------------------------------------------------------- /doc/man3/ag_init.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_init \- Initializes libag with default settings 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "int ag_init(void);" 24 | .fi 25 | .SH DESCRIPTION 26 | The 27 | .BR ag_init () 28 | function initializes libag with default settings, including (but not limited to): 29 | smart-case search, recursive search, disabled search of binary files, and etc. 30 | 31 | This routine 32 | .I must 33 | be the first routine to be called. A successful call will also start the worker 34 | threads that will be in idle and wait for work. 35 | 36 | .SH RETURN VALUE 37 | Returns 0 if success, -1 otherwise. 38 | 39 | .SH SEE ALSO 40 | .BR ag_init_config (3), 41 | .BR ag_finish (3), 42 | .BR ag_search (3) 43 | .SH AUTHOR 44 | Davidson Francis (davidsondfgl@gmail.com) 45 | -------------------------------------------------------------------------------- /doc/man3/ag_init_config.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_init_config \- Initializes libag with user-supplied settings 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "int ag_init_config(struct ag_config *" ag_config ");" 24 | .fi 25 | .SH DESCRIPTION 26 | The 27 | .BR ag_init_config () 28 | function initializes libag with user-supplied attributes specified by 29 | .IR ag_config . 30 | The structure 31 | .I ag_config 32 | defines a set of attributes that enables/disables and configure the 33 | specific behavior of libag. The structure looks like: 34 | 35 | .nf 36 | struct ag_config 37 | { 38 | int literal; /* enable/disable literal case search. */ 39 | int stats; /* enable/disable search stats. */ 40 | ... 41 | (please refer to libag.h for a complete description) 42 | }; 43 | .fi 44 | 45 | It is worth to note that all default values are 0, i.e: a zeroed 46 | ag_config structure behaves exactly the same way as a simple call to 47 | .BR ag_init (). 48 | 49 | This allows the user to activate only the fields he/she deems necessary, 50 | instead of all. Also note that this structure must be allocated by the 51 | user, and therefore, it needs to be clean (without garbage values) 52 | before being passed as a parameter. 53 | 54 | .SH RETURN VALUE 55 | Returns 0 if success, -1 otherwise. 56 | 57 | .SH NOTES 58 | User-defined settings can also be set anytime after 59 | .BR ag_init () 60 | with 61 | .BR ag_set_config (). 62 | 63 | .SH SEE ALSO 64 | .BR ag_set_config (3), 65 | .BR ag_init (3), 66 | .BR ag_finish (3), 67 | .BR ag_search (3) 68 | 69 | .SH AUTHOR 70 | Davidson Francis (davidsondfgl@gmail.com) 71 | -------------------------------------------------------------------------------- /doc/man3/ag_search.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "11 June 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_search \- Searches for a given pattern on a given path 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "struct ag_result **ag_search(char *" query ", int " npaths , 24 | .BI " char **" target_paths ", size_t *" nresults ");" 25 | .fi 26 | .SH DESCRIPTION 27 | The 28 | .BR ag_search () 29 | function searches for 30 | .I query 31 | on all paths given in the 32 | .I target_paths 33 | list. The 34 | .I npaths 35 | specify the number of paths (whether files or directories) contained 36 | in 37 | .IR target_paths . 38 | When a successful call to 39 | .BR ag_search () 40 | is made, the number of results is saved in 41 | .IR nresults . 42 | 43 | .SH RETURN VALUE 44 | On success, returns a list of (struct ag_result*) containing all the results 45 | found. It is up to the user to free the results, whether with 46 | .BR ag_free_result () 47 | or 48 | .BR ag_free_all_results (). 49 | On error, returns NULL and 50 | .I nresults 51 | is set to zero. 52 | 53 | .SH NOTES 54 | Please note that this routine is _not_ thread-safe, and should not be called 55 | from multiples threads. For a thread-safe version, please check 56 | .BR ag_search_ts (). 57 | 58 | The current behavior of 59 | .BR ag_search () 60 | can be changed, with the usage of custom config, set on the init via 61 | .BR ag_init_config () 62 | or later, via 63 | .BR ag_set_config (). 64 | 65 | .SH EXAMPLE 66 | .nf 67 | #include 68 | #include 69 | 70 | int main(void) { 71 | struct ag_result **results; 72 | size_t nresults; 73 | 74 | char *query = "foo"; 75 | char *paths[1] = {"."}; 76 | 77 | /* Initiate Ag library with default options. */ 78 | ag_init(); 79 | 80 | /* Searches for foo in the current path. */ 81 | results = ag_search(query, 1, paths, &nresults); 82 | if (!results) { 83 | printf("No result found\n"); 84 | return (1); 85 | } 86 | 87 | printf("%d results found\\n", nresults); 88 | 89 | /* Show them on the screen, if any. */ 90 | for (int i = 0; i < nresults; i++) { 91 | for (int j = 0; j < results[i]->nmatches; j++) { 92 | printf("file: %s, match: %s, start: %d / end: %d\\n", 93 | results[i]->file, results[i]->matches[j]->match, 94 | results[i]->matches[j]->byte_start, 95 | results[i]->matches[j]->byte_end); 96 | } 97 | } 98 | 99 | /* Free all resources. */ 100 | ag_free_all_results(results, nresults); 101 | 102 | /* Release Ag resources. */ 103 | ag_finish(); 104 | return (0); 105 | } 106 | 107 | .SH SEE ALSO 108 | .BR ag_search_ts (3), 109 | .BR ag_free_result (3), 110 | .BR ag_free_all_results (3), 111 | .BR ag_init_config (3), 112 | .BR ag_set_config (3) 113 | 114 | .SH AUTHOR 115 | Davidson Francis (davidsondfgl@gmail.com) 116 | -------------------------------------------------------------------------------- /doc/man3/ag_search_ts.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "11 June 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_search_ts \- Searches for a given pattern on a given path (thread-safe) 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "struct ag_result **ag_search_ts(char *" query ", int " npaths , 24 | .BI " char **" target_paths ", size_t *" nresults ");" 25 | .fi 26 | .SH DESCRIPTION 27 | The 28 | .BR ag_search_ts () 29 | function searches for 30 | .I query 31 | on all paths given in the 32 | .I target_paths 33 | list. The 34 | .I npaths 35 | specify the number of paths (whether files or directories) contained 36 | in 37 | .IR target_paths . 38 | When a successful call to 39 | .BR ag_search () 40 | is made, the number of results is saved in 41 | .IR nresults . 42 | 43 | .SH RETURN VALUE 44 | On success, returns a list of (struct ag_result*) containing all the results 45 | found. It is up to the user to free the results, whether with 46 | .BR ag_free_result () 47 | or 48 | .BR ag_free_all_results (). 49 | On error, returns NULL and 50 | .I nresults 51 | is set to zero. 52 | 53 | .SH NOTES 54 | Please note that this version is the same as 55 | .BR ag_search () 56 | but thread-safe. 57 | 58 | Also note that the thread-safe version here is the most rudimentary 59 | implementation possible: just one lock on the entire function. Which 60 | implies that parallel calls to this function will not be parallel, but 61 | sequential. 62 | 63 | A more efficient implementation is needed. See the issues on GitHub for a 64 | breakdown of this. 65 | 66 | .SH SEE ALSO 67 | .BR ag_search (3), 68 | .BR ag_free_result (3), 69 | .BR ag_free_all_results (3), 70 | .BR ag_init_config (3), 71 | .BR ag_set_config (3) 72 | 73 | .SH AUTHOR 74 | Davidson Francis (davidsondfgl@gmail.com) 75 | -------------------------------------------------------------------------------- /doc/man3/ag_set_config.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_set_config \- Sets the libag behavior with user-supplied settings 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "int ag_set_config(struct ag_config *" ag_config ");" 24 | .fi 25 | .SH DESCRIPTION 26 | The 27 | .BR ag_set_config () 28 | function sets the current libag settings with user-supplied attributes 29 | specified by \fIag_config\fP. The structure \fIag_config\fP defines a 30 | set of attributes that enables/disables and configure the 31 | specific behavior of libag. 32 | 33 | .SH RETURN VALUE 34 | Returns 0 if success (i.e: valid 35 | .I ag_config 36 | and valid attributes), -1 otherwise. 37 | 38 | .SH SEE ALSO 39 | .BR ag_init_config (3) 40 | 41 | .SH AUTHOR 42 | Davidson Francis (davidsondfgl@gmail.com) 43 | -------------------------------------------------------------------------------- /doc/man3/ag_start_workers.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_start_workers \- Start the worker threads for libag 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "int ag_start_workers(void);" 24 | .fi 25 | .SH DESCRIPTION 26 | .BR ag_start_workers () 27 | start the worker threads for libag. 28 | 29 | The 30 | .BR ag_start_workers() 31 | and 32 | .BR ag_stop_workers() 33 | routines are designed for experienced users who want to have refined control 34 | over their worker threads: whether they should start or stop. By default, all 35 | threads start with 36 | .BR ag_init () 37 | and stop with 38 | .BR ag_finish (). 39 | 40 | If the 41 | .I workers_behavior 42 | attribute is set to 43 | .IR LIBAG_MANUAL_WORKERS , 44 | in 45 | .BR ag_init_config (), 46 | worker threads will not be started automatically in init, but later through 47 | this routine. 48 | 49 | If 50 | .I workers_behavior 51 | is set to 52 | .IR LIBAG_ONSEARCH_WORKERS , 53 | the launch and finish of the worker threads will be controlled by 54 | .BR ag_search (). 55 | This reduces the use of resources at the cost of more search time. It can be 56 | useful if the user does not need to search as often. 57 | 58 | .SH RETURN VALUE 59 | Returns 0 on success, -1 otherwise. 60 | 61 | .SH SEE ALSO 62 | .BR ag_stop_workers (3), 63 | .BR ag_init_config (3), 64 | .BR ag_set_config (3), 65 | .BR ag_init (3), 66 | .BR ag_finish (3) 67 | 68 | .SH AUTHOR 69 | Davidson Francis (davidsondfgl@gmail.com) 70 | -------------------------------------------------------------------------------- /doc/man3/ag_stop_workers.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright 2021 Davidson Francis 3 | .\" 4 | .\" Licensed under the Apache License, Version 2.0 (the "License"); 5 | .\" you may not use this file except in compliance with the License. 6 | .\" You may obtain a copy of the License at 7 | .\" 8 | .\" http://www.apache.org/licenses/LICENSE-2.0 9 | .\" 10 | .\" Unless required by applicable law or agreed to in writing, software 11 | .\" distributed under the License is distributed on an "AS IS" BASIS, 12 | .\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | .\" See the License for the specific language governing permissions and 14 | .\" limitations under the License. 15 | .\" 16 | .TH man 3 "29 May 2021" "1.0" "libag man page" 17 | .SH NAME 18 | ag_stop_workers \- Stop the worker threads for libag 19 | .SH SYNOPSIS 20 | .nf 21 | .B #include 22 | .sp 23 | .BI "int ag_stop_workers(void);" 24 | .fi 25 | .SH DESCRIPTION 26 | .BR ag_stop_workers () 27 | stops all the worker threads for libag. 28 | 29 | The 30 | .BR ag_stop_workers() 31 | and 32 | .BR ag_start_workers() 33 | routines are designed for experienced users who want to have refined control 34 | over their worker threads: whether they should start or stop. By default, all 35 | threads start with 36 | .BR ag_init () 37 | and stop with 38 | .BR ag_finish (). 39 | 40 | .SH RETURN VALUE 41 | Returns 0 on success, -1 otherwise. 42 | 43 | .SH SEE ALSO 44 | .BR ag_start_workers (3), 45 | .BR ag_init (3), 46 | .BR ag_finish (3) 47 | 48 | .SH AUTHOR 49 | Davidson Francis (davidsondfgl@gmail.com) 50 | -------------------------------------------------------------------------------- /examples/init_config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Davidson Francis 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | int main(int argc, char **argv) 22 | { 23 | struct ag_result **results; 24 | struct ag_config config; 25 | size_t nresults; 26 | 27 | if (argc < 3) 28 | { 29 | fprintf(stderr, "Usage: %s \"regex\" [paths]\n", argv[0]); 30 | return (1); 31 | } 32 | 33 | /* 4 workers and enable binary files search. */ 34 | memset(&config, 0, sizeof(struct ag_config)); 35 | config.search_binary_files = 1; 36 | config.num_workers = 4; 37 | 38 | /* Initiate Ag library with default options. */ 39 | ag_init_config(&config); 40 | 41 | /* Search. */ 42 | results = ag_search(argv[1], argc - 2, argv + 2, &nresults); 43 | if (!results) 44 | printf("no result found\n"); 45 | else 46 | printf("%zu results found\n", nresults); 47 | 48 | /* Show them on the screen, if any. */ 49 | for (size_t i = 0; i < nresults; i++) 50 | { 51 | for (size_t j = 0; j < results[i]->nmatches; j++) 52 | { 53 | printf("file: %s, match: %s, start: %zu / end: %zu, is_binary: %d\n", 54 | results[i]->file, results[i]->matches[j]->match, 55 | results[i]->matches[j]->byte_start, 56 | results[i]->matches[j]->byte_end, 57 | !!(results[i]->flags & LIBAG_FLG_BINARY)); 58 | } 59 | } 60 | 61 | /* Free all resources. */ 62 | ag_free_all_results(results, nresults); 63 | 64 | /* Release Ag resources. */ 65 | ag_finish(); 66 | return (0); 67 | } 68 | -------------------------------------------------------------------------------- /examples/simple.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Davidson Francis 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | int main(int argc, char **argv) 21 | { 22 | struct ag_result **results; 23 | size_t nresults; 24 | 25 | if (argc < 3) 26 | { 27 | fprintf(stderr, "Usage: %s \"regex\" [paths]\n", argv[0]); 28 | return (1); 29 | } 30 | 31 | /* Initiate Ag library with default options. */ 32 | ag_init(); 33 | 34 | /* Search. */ 35 | results = ag_search(argv[1], argc - 2, argv + 2, &nresults); 36 | if (!results) 37 | printf("no result found\n"); 38 | else 39 | printf("%zu results found\n", nresults); 40 | 41 | /* Show them on the screen, if any. */ 42 | for (size_t i = 0; i < nresults; i++) 43 | { 44 | for (size_t j = 0; j < results[i]->nmatches; j++) 45 | { 46 | printf("file: %s, match: %s, start: %zu / end: %zu\n", 47 | results[i]->file, results[i]->matches[j]->match, 48 | results[i]->matches[j]->byte_start, 49 | results[i]->matches[j]->byte_end); 50 | } 51 | } 52 | 53 | /* Free all resources. */ 54 | ag_free_all_results(results, nresults); 55 | 56 | /* Release Ag resources. */ 57 | ag_finish(); 58 | return (0); 59 | } 60 | -------------------------------------------------------------------------------- /libag.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Davidson Francis 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef LIBAG_H 18 | #define LIBAG_H 19 | 20 | #include 21 | 22 | /* Tag/Release identifier. */ 23 | #define TAG_ID "v2-apache_license" 24 | 25 | /* Num workers. */ 26 | #define NUM_WORKERS 8 27 | 28 | /* Casing. */ 29 | #define LIBAG_CASE_SMART 0 30 | #define LIBAG_CASE_SENSITIVE 1 31 | #define LIBAG_CASE_INSENSITIVE 2 32 | 33 | /* Workers behavior. */ 34 | #define LIBAG_START_WORKERS 0 35 | #define LIBAG_MANUAL_WORKERS 1 36 | #define LIBAG_ONSEARCH_WORKERS 2 37 | 38 | /* Result flags. */ 39 | #define LIBAG_FLG_TEXT 1 40 | #define LIBAG_FLG_BINARY 2 41 | 42 | /** 43 | * Structure that holds a single result, i.e: a file 44 | * that may contains multiples matches. 45 | */ 46 | struct ag_result 47 | { 48 | char *file; 49 | size_t nmatches; 50 | struct ag_match 51 | { 52 | size_t byte_start; 53 | size_t byte_end; 54 | char *match; 55 | } **matches; 56 | int flags; 57 | }; 58 | 59 | /** 60 | * @brief libag search stats 61 | * 62 | * Holds stats relative to the latest search performed by @ref 63 | * ag_search. 64 | * 65 | * You can get this structure by enabling stats via ag_config, 66 | * and retrieving later with @ref ag_get_stats. 67 | * 68 | * Note: 69 | * This is also very similar to the 'ag_stats' structure but 70 | * without the time fields. The reasoning behind the usage of 71 | * this instead of 'ag_stats' is the same of the structure 72 | * below. 73 | */ 74 | struct ag_search_stats 75 | { 76 | size_t total_bytes; /* Amount of bytes read. */ 77 | size_t total_files; /* Amount of files read. */ 78 | size_t total_matches; /* Amount of matches. */ 79 | size_t total_file_matches; /* Amount of files that have match. */ 80 | }; 81 | 82 | /** 83 | * @brief libag configuration structure. 84 | * 85 | * This structure holds the configuration that would be used inside 86 | * Ag, you're free to use this structure inside your program and 87 | * set whatever config you want. 88 | * 89 | * Note 1: 90 | * ------- 91 | * Please note that whenever you change some field here, please 92 | * also call the routine @ref ag_set_config, otherwise, the 93 | * changes will not be reflected. 94 | * 95 | * Note 2: 96 | * ------- 97 | * Beware with trash data inside this structure, do a memset first 98 | * or allocate with calloc. 99 | * 100 | * Note 3: 101 | * ------- 102 | * Zeroed options is the default behavior of ag_init()!. 103 | * 104 | * Technical notes: 105 | * ---------------- 106 | * This structure is very similar to the 'cli_options' struct 107 | * that Ag uses internally. One could say that I should use 108 | * 'cli_options' instead of this one, but here are my thoughts 109 | * about this: 110 | * 111 | * - This header do _not_ exposes internal Ag functionality to the 112 | * user program and modify Ag in order to expose cli_options seems 113 | * wrong to me. The core idea of libag is to allow the user to use 114 | * libag with only this header (libag.h) and the library (libag.so). 115 | * 116 | * - The 'cli_options' structure: 117 | * - Holds some data that are never exposed to the command-line, but 118 | * only used internally; mix internal/external only confuses the 119 | * user. 120 | * 121 | * - Contains a lot of options that: a) will not be supported by 122 | * libag, such as: --color,--context,--after,--before and etc. 123 | * b) are not supported at the moment; allows the user to 124 | * mistakenly think that these options are supported do not 125 | * seems right to me. 126 | * 127 | */ 128 | struct ag_config 129 | { 130 | /* != 0 enable literal search, 0 disable (default) (i.e: uses regex). */ 131 | int literal; 132 | /* != 0 disable folder recursion, 0 enables (default). */ 133 | int disable_recurse_dir; 134 | /* 135 | * Casing: 136 | * 0 (smart case, default): if pattern is all lowercase, do a case 137 | * insensitive search, otherwise, case 138 | * sensitive. 139 | * 1 (case sensitive). 140 | * 2 (case insensitive). 141 | */ 142 | int casing; 143 | /* 144 | * Number of concurrently workers (threads) running at the same time. 145 | * 146 | * 0 (default): uses the amount of cores available (if <= NUM_WORKERS), 147 | * or NUM_WORKERS (if > NUM_WORKERS). 148 | * 149 | * Or: 150 | * 1 <= num_workers <= NUM_WORKERS. 151 | */ 152 | int num_workers; 153 | /* 154 | * By default, libag always start worker threads after a successful 155 | * call to ag_init, however, a user may want to have a fine control 156 | * over the workers, whether by starting/stopping by hand (via 157 | * ag_start_workers/ag_stop_workers or by letting ag_seach 158 | * manages the start/stop). 159 | * 160 | * LIBAG_START_WORKERS 0 - start workers on ag_init (default) 161 | * LIBAG_MANUAL_WORKERS 1 - manual workers management. 162 | * LIBAG_ONSEARCH_WORKERS 2 - start/stop workers on every ag_search. 163 | */ 164 | int workers_behavior; 165 | /* 166 | * Enable a few stats for the last search. 167 | */ 168 | int stats; /* 0 disable (default), != 0 enable. */ 169 | /* 170 | * Search binary files. 171 | */ 172 | int search_binary_files; /* 0 disable (default), != 0 enable. */ 173 | }; 174 | 175 | /* Library forward declarations. */ 176 | extern int ag_start_workers(void); 177 | extern int ag_stop_workers(void); 178 | extern int ag_set_config(struct ag_config *ag_config); 179 | extern int ag_init(void); 180 | extern int ag_init_config(struct ag_config *config); 181 | extern int ag_finish(void); 182 | extern struct ag_result **ag_search(char *query, int npaths, 183 | char **target_paths, size_t *nresults); 184 | extern struct ag_result **ag_search_ts(char *query, int npaths, 185 | char **target_paths, size_t *nresults); 186 | extern int ag_get_stats(struct ag_search_stats *ret_stats); 187 | extern void ag_free_result(struct ag_result *result); 188 | extern void ag_free_all_results(struct ag_result **results, 189 | size_t nresults); 190 | 191 | #endif /* LIBAG_H. */ 192 | --------------------------------------------------------------------------------