From e177717e9e1f1cad78a8eb00fbd1a849c3c6a76f Mon Sep 17 00:00:00 2001 From: Michael Sartain Date: Tue, 14 Sep 2021 18:20:20 -0600 Subject: [PATCH] Initial commit --- .gitignore | 2 + LICENSE | 23 ++ Makefile | 119 ++++++++ README.md | 150 ++++++++++ inotify-info.cpp | 718 ++++++++++++++++++++++++++++++++++++++++++++++ inotify-info.h | 60 ++++ lfqueue/LICENSE | 25 ++ lfqueue/lfqueue.c | 379 ++++++++++++++++++++++++ lfqueue/lfqueue.h | 84 ++++++ 9 files changed, 1560 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 inotify-info.cpp create mode 100644 inotify-info.h create mode 100644 lfqueue/LICENSE create mode 100644 lfqueue/lfqueue.c create mode 100644 lfqueue/lfqueue.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08376e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +_debug/ +_release/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fc10152 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright 2021 Michael Sartain + +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..52eb67a --- /dev/null +++ b/Makefile @@ -0,0 +1,119 @@ +# $@: name of the target file (one before colon) +# $<: name of first prerequisite file (first one after colon) +# $^: names of all prerequisite files (space separated) +# $*: stem (bit which matches the % wildcard in rule definition) +# +# VAR = val: Normal setting - values within are recursively expand when var used. +# VAR := val: Setting of var with simple expansion of values inside - values are expanded at decl time. +# VAR ?= val: Set var only if it doesn't have a value. +# VAR += val: Append val to existing value (or set if var didn't exist). + +# To use static analyzer: +# http://clang-analyzer.llvm.org/scan-build.html +# Ie: +# scan-build -k -V --use-analyzer ~/bin/clang make + +NAME = inotify-info + +CFG ?= release +ifeq ($(CFG), debug) + ASAN ?= 1 +endif + +LD = $(CC) +RM = rm -f +MKDIR = mkdir -p +VERBOSE ?= 0 + +COMPILER = $(shell $(CC) -v 2>&1 | grep -q "clang version" && echo clang || echo gcc) + +WARNINGS = -Wall -Wextra -Wpedantic -Wmissing-include-dirs -Wformat=2 -Wshadow -Wno-unused-parameter -Wno-missing-field-initializers +ifneq ($(COMPILER),clang) + # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html + WARNINGS += -Wsuggest-attribute=format -Wimplicit-fallthrough=2 +endif + +# Investigate: Improving C++ Builds with Split DWARF +# http://www.productive-cpp.com/improving-cpp-builds-with-split-dwarf/ + +CFLAGS = $(WARNINGS) -march=native -fno-exceptions -gdwarf-4 -g2 -ggnu-pubnames -gsplit-dwarf +CFLAGS += -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64 +CXXFLAGS = -fno-rtti -Woverloaded-virtual -Wno-class-memaccess -Wno-pedantic +LDFLAGS = -march=native -gdwarf-4 -g2 -Wl,--build-id=sha1 +LIBS = -Wl,--no-as-needed -lm -ldl -lpthread -lstdc++ + +ifneq ("$(wildcard /usr/bin/ld.gold)","") + $(info Using gold linker...) + LDFLAGS += -fuse-ld=gold -Wl,--gdb-index +endif + +CFILES = \ + inotify-info.cpp \ + lfqueue/lfqueue.c + +# Useful GCC address sanitizer checks not enabled by default +# https://kristerw.blogspot.com/2018/06/useful-gcc-address-sanitizer-checks-not.html + +ifeq ($(ASAN), 1) + # https://gcc.gnu.org/gcc-5/changes.html + # -fsanitize=float-cast-overflow: check that the result of floating-point type to integer conversions do not overflow; + # -fsanitize=alignment: enable alignment checking, detect various misaligned objects; + # -fsanitize=vptr: enable checking of C++ member function calls, member accesses and some conversions between pointers to base and derived classes, detect if the referenced object does not have the correct dynamic type. + ASAN_FLAGS = -fno-omit-frame-pointer -fno-optimize-sibling-calls + ASAN_FLAGS += -fsanitize=address # fast memory error detector (heap, stack, global buffer overflow, and use-after free) + ASAN_FLAGS += -fsanitize=leak # detect leaks + ASAN_FLAGS += -fsanitize=undefined # fast undefined behavior detector + ASAN_FLAGS += -fsanitize=float-divide-by-zero # detect floating-point division by zero; + ASAN_FLAGS += -fsanitize=bounds # enable instrumentation of array bounds and detect out-of-bounds accesses; + ASAN_FLAGS += -fsanitize=object-size # enable object size checking, detect various out-of-bounds accesses. + CFLAGS += $(ASAN_FLAGS) + LDFLAGS += $(ASAN_FLAGS) +endif + +ifeq ($(CFG), debug) + ODIR=_debug + CFLAGS += -O0 -DDEBUG + CFLAGS += -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC -D_GLIBCXX_SANITIZE_VECTOR -D_LIBCPP_DEBUG=1 +else + ODIR=_release + CFLAGS += -O2 -DNDEBUG +endif + +PROJ = $(ODIR)/$(NAME) +$(info Building $(ODIR)/$(NAME)...) + +ifeq ($(VERBOSE), 1) + VERBOSE_PREFIX= +else + VERBOSE_PREFIX=@ +endif + +C_OBJS = ${CFILES:%.c=${ODIR}/%.o} +OBJS = ${C_OBJS:%.cpp=${ODIR}/%.o} + +all: $(PROJ) + +$(ODIR)/$(NAME): $(OBJS) + @echo "Linking $@..."; + $(VERBOSE_PREFIX)$(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +-include $(OBJS:.o=.d) + +$(ODIR)/%.o: %.c Makefile + $(VERBOSE_PREFIX)echo "---- $< ----"; + @$(MKDIR) $(dir $@) + $(VERBOSE_PREFIX)$(CC) -MMD -MP -std=gnu99 $(CFLAGS) -o $@ -c $< + +$(ODIR)/%.o: %.cpp Makefile + $(VERBOSE_PREFIX)echo "---- $< ----"; + @$(MKDIR) $(dir $@) + $(VERBOSE_PREFIX)$(CXX) -MMD -MP -std=c++11 $(CFLAGS) $(CXXFLAGS) -o $@ -c $< + +.PHONY: clean + +clean: + @echo Cleaning... + $(VERBOSE_PREFIX)$(RM) $(PROJ) + $(VERBOSE_PREFIX)$(RM) $(OBJS) + $(VERBOSE_PREFIX)$(RM) $(OBJS:.o=.d) + $(VERBOSE_PREFIX)$(RM) $(OBJS:.o=.dwo) diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fc8ace --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# inotify-info + +The Linux inotify system has a few issues** and can be difficult to debug. + +This app should hopefully help track down how many inotify watches, instances, and what files are being watched. + +** https://code.visualstudio.com/docs/setup/linux#_visual-studio-code-is-unable-to-watch-for-file-changes-in-this-large-workspace-error-enospc +*** https://unix.stackexchange.com/questions/15509/whos-consuming-my-inotify-resources + +## Build +``` +$ make +Building _release/inotify-info... +---- inotify-info.cpp ---- +---- lfqueue/lfqueue.c ---- +Linking _release/inotify-info... +``` +``` +$ CFG=debug make +Building _debug/inotify-info... +---- inotify-info.cpp ---- +---- lfqueue/lfqueue.c ---- +Linking _debug/inotify-info... +``` + +## Run (Prints Summary) +``` +$ _release/inotify-info +------------------------------------------------------------------------------ +INotify Limits: + max_queued_events: 16384 + max_user_instances: 128 + max_user_watches: 65536 +------------------------------------------------------------------------------ + Pid App Watches Instances + 2632 systemd 23 3 + 2653 pulseaudio 2 2 + 2656 dbus-daemon 2 1 + 2987 dbus-daemon 1 1 + 3056 xfsettingsd 56 1 + 3068 xfdesktop 10 1 + 3072 wrapper-2.0 6 1 + 3091 xfce4-clipman 1 1 + 3099 xiccd 1 1 + 3343 xfce4-terminal 1 1 + 3997 xfce4-appfinder 11 1 + 4048 xdg-desktop-portal 1 1 + 4086 xdg-desktop-portal-gtk 56 1 + 205668 vivaldi-bin 8 1 + 205705 vivaldi-bin 2 1 +------------------------------------------------------------------------------ +Total inotify Watches: 181 +Total inotify Instances: 18 +------------------------------------------------------------------------------ +``` + +## Run with Appname Filter +``` +$ _release/inotify-info xfce4 +------------------------------------------------------------------------------ +INotify Limits: + max_queued_events: 16384 + max_user_instances: 128 + max_user_watches: 65536 +------------------------------------------------------------------------------ + Pid App Watches Instances + 2632 systemd 23 3 + 2653 pulseaudio 2 2 + 2656 dbus-daemon 2 1 + 2987 dbus-daemon 1 1 + 3056 xfsettingsd 56 1 + 3068 xfdesktop 10 1 + 3072 wrapper-2.0 6 1 + 3091 xfce4-clipman 1 1 + 94111050 [10304h] + 3099 xiccd 1 1 + 3343 xfce4-terminal 1 1 + 71048655 [10304h] + 3997 xfce4-appfinder 11 1 + 94111468 [10304h] 15339430 [10304h] 14554799 [10304h] 70254617 [10304h] 70254684 [10304h] 16786993 [10304h] 14551253 [10304h] 14550430 [10304h] 70254647 [10304h] 70254646 [10304h] + 92275589 [10304h] + 4048 xdg-desktop-portal 1 1 + 4086 xdg-desktop-portal-gtk 56 1 + 205668 vivaldi-bin 8 1 + 205705 vivaldi-bin 2 1 +------------------------------------------------------------------------------ +Total inotify Watches: 181 +Total inotify Instances: 18 +------------------------------------------------------------------------------ + +Searching '/' for listed inodes... (8 threads) + 14550430 [10304h] /usr/share/applications/ + 14551253 [10304h] /usr/local/share/ + 14554799 [10304h] /usr/share/xfce4/ + 15339430 [10304h] /usr/share/desktop-directories/ + 16786993 [10304h] /usr/share/xfce4/applications/ + 70254617 [10304h] /home/mikesart/.local/share/ + 70254646 [10304h] /home/mikesart/.config/menus/ + 70254647 [10304h] /home/mikesart/.config/menus/applications-merged/ + 70254684 [10304h] /home/mikesart/.local/share/applications/ + 71048655 [10304h] /home/mikesart/.config/xfce4/terminal/ + 92275589 [10304h] /etc/xdg/menus/ + 94111050 [10304h] /home/mikesart/.config/xfce4/panel/ + 94111468 [10304h] /home/mikesart/.cache/xfce4/xfce4-appfinder/ +``` +## Run with Specific Pid(s) +``` +$ _release/inotify-info 3997 +------------------------------------------------------------------------------ +INotify Limits: + max_queued_events: 16384 + max_user_instances: 128 + max_user_watches: 65536 +------------------------------------------------------------------------------ + Pid App Watches Instances + 2632 systemd 23 3 + 2653 pulseaudio 2 2 + 2656 dbus-daemon 2 1 + 2987 dbus-daemon 1 1 + 3056 xfsettingsd 56 1 + 3068 xfdesktop 10 1 + 3072 wrapper-2.0 6 1 + 3091 xfce4-clipman 1 1 + 3099 xiccd 1 1 + 3343 xfce4-terminal 1 1 + 3997 xfce4-appfinder 11 1 + 94111468 [10304h] 15339430 [10304h] 14554799 [10304h] 70254617 [10304h] 70254684 [10304h] 16786993 [10304h] 14551253 [10304h] 14550430 [10304h] 70254647 [10304h] 70254646 [10304h] + 92275589 [10304h] + 4048 xdg-desktop-portal 1 1 + 4086 xdg-desktop-portal-gtk 56 1 + 205668 vivaldi-bin 8 1 + 205705 vivaldi-bin 2 1 +------------------------------------------------------------------------------ +Total inotify Watches: 181 +Total inotify Instances: 18 +------------------------------------------------------------------------------ + +Searching '/' for listed inodes... (8 threads) + 14550430 [10304h] /usr/share/applications/ + 14551253 [10304h] /usr/local/share/ + 14554799 [10304h] /usr/share/xfce4/ + 15339430 [10304h] /usr/share/desktop-directories/ + 16786993 [10304h] /usr/share/xfce4/applications/ + 70254617 [10304h] /home/mikesart/.local/share/ + 70254646 [10304h] /home/mikesart/.config/menus/ + 70254647 [10304h] /home/mikesart/.config/menus/applications-merged/ + 70254684 [10304h] /home/mikesart/.local/share/applications/ + 92275589 [10304h] /etc/xdg/menus/ + 94111468 [10304h] /home/mikesart/.cache/xfce4/xfce4-appfinder/ +``` diff --git a/inotify-info.cpp b/inotify-info.cpp new file mode 100644 index 0000000..3a423c5 --- /dev/null +++ b/inotify-info.cpp @@ -0,0 +1,718 @@ +/* + * Copyright 2021 Michael Sartain + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "inotify-info.h" +#include "lfqueue/lfqueue.h" + +static size_t g_numthreads = 8; + +static int g_verbose = 0; + +struct procinfo_t +{ + pid_t pid = 0; + + // Count of inotify watches and instances + uint32_t watches = 0; + uint32_t instances = 0; + + // Full executable path + std::string executable; + + // Executable basename + std::string appname; + + // Inotify fdset filenames + std::vector< std::string > fdset_filenames; + + // Inode number and ID of device containing watched file + std::vector< std::pair< ino64_t, dev_t > > watched_inodes; +}; + +class inotifyapp_t +{ +public: + void init( int argc, char *argv[] ); + void shutdown(); + + // Read /proc dir searching for "anon_inode:inotify" fd links + bool init_inotify_proclist(); + void print_inotify_proclist(); + + bool find_files_in_inode_set(); + void print_found_files(); + + // Create unique string from inode number + device ID + static std::string get_inode_sdev_str( const std::pair< ino64_t, dev_t > &inode ) + { + return string_format( "%lu [%lxh]", inode.first, inode.second ); + } + +private: + void parse_cmdline( int argc, char **argv ); + void print_usage( const char *appname ); + + bool is_proc_in_cmdline_applist( const procinfo_t &procinfo ); + + void add_found_filename( const std::string &filename ); + + // Returns -1: queue empty, 0: open error, > 0 success + int parse_dirqueue_entry(); + + static void *parse_dirqueue_threadproc( void *arg ); + +private: + lfqueue_t dirqueue; + + uint32_t total_watches = 0; + uint32_t total_instances = 0; + + // Command line app args + std::vector< std::string > cmdline_applist; + + // List of procs with inotify watches + std::vector< procinfo_t > inotify_proclist; + + // Set of all inotify inodes watched. Note that this is inodes + // only, not device IDs - we parse false positives out when adding. + std::unordered_set< ino64_t > inotify_inode_set; + + // Set of all watched inode + device IDs + std::unordered_set< std::string > inotify_inode_sdevs; + + // All found files which match inotify inodes, along with stat info + struct filename_info_t + { + std::string filename; + struct stat statbuf; + }; + std::vector< filename_info_t > found_files; + + pthread_mutex_t found_files_mutex = PTHREAD_MUTEX_INITIALIZER; +}; + +struct linux_dirent64 +{ + ino64_t d_ino; // Inode number + off64_t d_off; // Offset to next linux_dirent + unsigned short d_reclen; // Length of this linux_dirent + unsigned char d_type; // File type + char d_name[]; // Filename (null-terminated) +}; + +int sys_getdents64( int fd, char *dirp, unsigned int count ) +{ + return syscall( SYS_getdents64, fd, dirp, count ); +} + +void print_separator() +{ + printf( "%s%s%s\n", YELLOW, std::string( 78, '-' ).c_str(), RESET ); +} + +std::string string_formatv( const char *fmt, va_list ap ) +{ + std::string str; + int size = 512; + + for ( ;; ) + { + str.resize( size ); + int n = vsnprintf( ( char * )str.c_str(), size, fmt, ap ); + + if ( ( n > -1 ) && ( n < size ) ) + { + str.resize( n ); + return str; + } + + size = ( n > -1 ) ? ( n + 1 ) : ( size * 2 ); + } +} + +std::string string_format( const char *fmt, ... ) +{ + va_list ap; + std::string str; + + va_start( ap, fmt ); + str = string_formatv( fmt, ap ); + va_end( ap ); + + return str; +} + +static struct stat get_file_statbuf( const char *filename ) +{ + struct stat statbuf; + + if ( stat( filename, &statbuf ) == -1 ) + memset( &statbuf, 0, sizeof( statbuf ) ); + + return statbuf; +} + +static std::string get_link_name( const char *Pathname ) +{ + std::string Result; + char Filename[ PATH_MAX + 1 ]; + + ssize_t ret = readlink( Pathname, Filename, sizeof( Filename ) ); + if ( ( ret > 0 ) && ( ret < ( ssize_t )sizeof( Filename ) ) ) + { + Filename[ ret ] = 0; + Result = Filename; + } + return Result; +} + +static uint64_t get_token_val( const char *line, const char *token ) +{ + char *endptr; + const char *str = strstr( line, token ); + + return str ? strtoull( str + strlen( token ), &endptr, 16 ) : 0; +} + +static uint32_t inotify_parse_fdinfo_file( procinfo_t &procinfo, const char *fdset_name ) +{ + uint32_t watch_count = 0; + + FILE *fp = fopen( fdset_name, "r" ); + if ( fp ) + { + char line_buf[ 256 ]; + + procinfo.fdset_filenames.push_back( fdset_name ); + + for ( ;; ) + { + if ( !fgets( line_buf, sizeof( line_buf ), fp ) ) + { + break; + } + + if ( !strncmp( line_buf, "inotify ", 8 ) ) + { + watch_count++; + + uint64_t inode_val = get_token_val( line_buf, "ino:" ); + uint64_t sdev_val = get_token_val( line_buf, "sdev:" ); + + if ( inode_val ) + { + dev_t sdev = ( sdev_val >> 12 ) | ( sdev_val & 0xff ); + + procinfo.watched_inodes.push_back( { inode_val, sdev } ); + } + } + } + + fclose( fp ); + } + + return watch_count; +} + +static void inotify_parse_fddir( procinfo_t &procinfo ) +{ + std::string filename = string_format( "/proc/%d/fd", procinfo.pid ); + + DIR *dir_fd = opendir( filename.c_str() ); + if ( !dir_fd ) + return; + + for ( ;; ) + { + struct dirent *dp_fd = readdir( dir_fd ); + if ( !dp_fd ) + { + break; + } + + if ( ( dp_fd->d_type == DT_LNK ) && isdigit( dp_fd->d_name[ 0 ] ) ) + { + filename = string_format( "/proc/%d/fd/%s", procinfo.pid, dp_fd->d_name ); + filename = get_link_name( filename.c_str() ); + + if ( filename == "anon_inode:inotify" ) + { + filename = string_format( "/proc/%d/fdinfo/%s", procinfo.pid, dp_fd->d_name ); + + uint32_t count = inotify_parse_fdinfo_file( procinfo, filename.c_str() ); + if ( count ) + { + procinfo.instances++; + procinfo.watches += count; + } + } + } + } + + closedir( dir_fd ); +} + +void inotifyapp_t::print_usage( const char *appname ) +{ + printf( "Usage: %s [--threads=##] [appname | pid...]\n", appname ); + printf( " [-vv]\n" ); + printf( " [-?|-h|--help]\n" ); + + exit( -1 ); +} + +void inotifyapp_t::parse_cmdline( int argc, char **argv ) +{ + static struct option long_opts[] = { + { "verbose", no_argument, 0, 0 }, + { "threads", required_argument, 0, 0 }, + { 0, 0, 0, 0 } + }; + + int c; + int opt_ind = 0; + while ( ( c = getopt_long( argc, argv, "m:s:?hv", long_opts, &opt_ind ) ) != -1 ) + { + switch ( c ) + { + case 0: + if ( !strcasecmp( "verbose", long_opts[ opt_ind ].name ) ) + g_verbose++; + else if ( !strcasecmp( "threads", long_opts[ opt_ind ].name ) ) + g_numthreads = atoi( optarg ); + break; + case 'v': + g_verbose++; + break; + case 'h': + case '?': + default: + print_usage( argv[ 0 ] ); + break; + } + } + + for ( ; optind < argc; optind++ ) + { + cmdline_applist.push_back( argv[ optind ] ); + } +} + +void inotifyapp_t::init( int argc, char *argv[] ) +{ + parse_cmdline( argc, argv ); + + // Init queue and add root dir + lfqueue_init( &dirqueue ); + lfqueue_enq( &dirqueue, strdup( "/" ) ); +} + +void inotifyapp_t::shutdown() +{ + lfqueue_destroy( &dirqueue ); +} + +void inotifyapp_t::add_found_filename( const std::string &filename ) +{ + filename_info_t fname; + + fname.filename = filename; + fname.statbuf = get_file_statbuf( filename.c_str() ); + + // Make sure the inode AND device ID match before adding. + std::string inode_dev_str = get_inode_sdev_str( { fname.statbuf.st_ino, fname.statbuf.st_dev } ); + if ( inotify_inode_sdevs.find( inode_dev_str ) != inotify_inode_sdevs.end() ) + { + pthread_mutex_lock( &found_files_mutex ); + + found_files.push_back( fname ); + + pthread_mutex_unlock( &found_files_mutex ); + } +} + +static bool is_dot_dir( const char *dname ) +{ + if ( dname[ 0 ] == '.' ) + { + if ( !dname[ 1 ] ) + return true; + + if ( ( dname[ 1 ] == '.' ) && !dname[ 2 ] ) + return true; + } + + return false; +} + +// Returns -1: queue empty, 0: open error, > 0 success +int inotifyapp_t::parse_dirqueue_entry() +{ + char __attribute__( ( aligned( 16 ) ) ) buf[ 1024 ]; + + char *path = ( char * )lfqueue_deq( &dirqueue ); + if ( !path ) + return -1; + + int fd = open( path, O_RDONLY | O_DIRECTORY, 0 ); + if ( fd < 0 ) + { + free( path ); + return 0; + } + + size_t pathlen = strlen( path ); + + for ( ;; ) + { + int num = sys_getdents64( fd, buf, sizeof( buf ) ); + if ( num == -1 ) + { + printf( "ERROR: sys_getdents64 failed on '%s'\n", path ); + break; + } + if ( num == 0 ) + break; + + for ( int bpos = 0; bpos < num; ) + { + struct linux_dirent64 *dirp = ( struct linux_dirent64 * )( buf + bpos ); + const char *d_name = dirp->d_name; + + // DT_BLK This is a block device. + // DT_CHR This is a character device. + // DT_DIR This is a directory. + // DT_FIFO This is a named pipe (FIFO). + // DT_LNK This is a symbolic link. + // DT_REG This is a regular file. + // DT_SOCK This is a UNIX domain socket. + // DT_UNKNOWN The file type could not be determined. + if ( dirp->d_type == DT_REG ) + { + if ( inotify_inode_set.find( dirp->d_ino ) != inotify_inode_set.end() ) + { + add_found_filename( std::string( path ) + d_name ); + } + } + else if ( dirp->d_type == DT_DIR ) + { + if ( !is_dot_dir( d_name ) ) + { + if ( inotify_inode_set.find( dirp->d_ino ) != inotify_inode_set.end() ) + { + add_found_filename( std::string( path ) + d_name + std::string( "/" ) ); + } + + size_t len = strlen( dirp->d_name ); + char *newpath = ( char * )malloc( pathlen + len + 2 ); + + if ( newpath ) + { + strcpy( newpath, path ); + strcpy( newpath + pathlen, dirp->d_name ); + newpath[ pathlen + len ] = '/'; + newpath[ pathlen + len + 1 ] = 0; + + lfqueue_enq( &dirqueue, newpath ); + } + } + } + + bpos += dirp->d_reclen; + } + } + + close( fd ); + free( path ); + return 1; +} + +void *inotifyapp_t::parse_dirqueue_threadproc( void *arg ) +{ + inotifyapp_t *papp = ( inotifyapp_t * )arg; + + for ( ;; ) + { + // Break when queue is empty + if ( papp->parse_dirqueue_entry() < 0 ) + break; + } + + return nullptr; +} + +bool inotifyapp_t::init_inotify_proclist() +{ + DIR *dir_proc = opendir( "/proc" ); + + if ( !dir_proc ) + { + printf( "ERROR: opendir /proc failed: %d\n", errno ); + return false; + } + + assert( inotify_proclist.empty() ); + + for ( ;; ) + { + struct dirent *dp_proc = readdir( dir_proc ); + if ( !dp_proc ) + { + break; + } + + if ( ( dp_proc->d_type == DT_DIR ) && isdigit( dp_proc->d_name[ 0 ] ) ) + { + procinfo_t procinfo; + + procinfo.pid = atoll( dp_proc->d_name ); + + std::string executable = string_format( "/proc/%d/exe", procinfo.pid ); + + procinfo.executable = get_link_name( executable.c_str() ); + if ( !procinfo.executable.empty() ) + { + procinfo.appname = basename( procinfo.executable.c_str() ); + + inotify_parse_fddir( procinfo ); + + if ( procinfo.watches ) + { + inotify_proclist.push_back( procinfo ); + } + } + } + } + + closedir( dir_proc ); + return true; +} + +bool inotifyapp_t::is_proc_in_cmdline_applist( const procinfo_t &procinfo ) +{ + for ( const std::string &str : cmdline_applist ) + { + if ( strstr( procinfo.appname.c_str(), str.c_str() ) ) + return true; + + if ( atoll( str.c_str() ) == procinfo.pid ) + return true; + } + + return false; +} + +void inotifyapp_t::print_inotify_proclist() +{ + printf( "%s Pid App Watches Instances%s\n", BCYAN, RESET ); + + for ( procinfo_t &procinfo : inotify_proclist ) + { + bool in_proc_list = is_proc_in_cmdline_applist( procinfo ); + + printf( " % 7d %s%-30s%s %3u %3u\n", procinfo.pid, BYELLOW, procinfo.appname.c_str(), RESET, procinfo.watches, procinfo.instances ); + + if ( g_verbose > 1 ) + { + for ( std::string &fname : procinfo.fdset_filenames ) + { + printf( " %s%s%s\n", CYAN, fname.c_str(), RESET ); + } + } + + if ( in_proc_list ) + { + int count = 0; + + printf( " " ); + for ( size_t i = 0; i < procinfo.watched_inodes.size(); i++ ) + { + std::string inode_dev_str = get_inode_sdev_str( procinfo.watched_inodes[ i ] ); + + printf( " %s%s%s ", BGRAY, inode_dev_str.c_str(), RESET ); + + inotify_inode_sdevs.insert( inode_dev_str ); + inotify_inode_set.insert( procinfo.watched_inodes[ i ].first ); + + if ( !( ++count % 10 ) ) + printf( "\n " ); + } + printf( "\n" ); + } + + total_watches += procinfo.watches; + total_instances += procinfo.instances; + } + + print_separator(); + + printf( "Total inotify Watches: %s%u%s\n", BGREEN, total_watches, RESET ); + printf( "Total inotify Instances: %s%u%s\n", BGREEN, total_instances, RESET ); +} + +bool inotifyapp_t::find_files_in_inode_set() +{ + std::vector< pthread_t > threadids; + + assert( found_files.empty() ); + + if ( inotify_inode_set.empty() ) + return false; + + printf( "\n%sSearching '/' for listed inodes...%s (%lu threads)\n", BCYAN, RESET, g_numthreads ); + + // Add root dir in case someone is watching it + add_found_filename( "/" ); + + // Parse root to add some dirs for threads to chew on + parse_dirqueue_entry(); + + for ( size_t i = 0; i < g_numthreads; i++ ) + { + pthread_t tid; + + if ( pthread_create( &tid, NULL, &inotifyapp_t::parse_dirqueue_threadproc, this ) == 0 ) + { + threadids.push_back( tid ); + } + } + + // Put main thread to work + inotifyapp_t::parse_dirqueue_threadproc( this ); + + for ( size_t i = 0; i < threadids.size(); i++ ) + { + if ( g_verbose > 1 ) + printf( "Waiting for thread #%zu\n", i ); + + void *status = NULL; + int rc = pthread_join( threadids[ i ], &status ); + + if ( g_verbose > 1 ) + printf( "Thread #%zu rc=%d status=%d\n", i, rc, ( int )( intptr_t )status ); + } + + return true; +} + +void inotifyapp_t::print_found_files() +{ + if ( found_files.empty() ) + return; + + struct + { + bool operator()( const filename_info_t &a, const filename_info_t &b ) const + { + if ( a.statbuf.st_dev == b.statbuf.st_dev ) + return a.statbuf.st_ino < b.statbuf.st_ino; + return a.statbuf.st_dev < b.statbuf.st_dev; + } + } filename_info_less_func; + + std::sort( found_files.begin(), found_files.end(), filename_info_less_func ); + + for ( const filename_info_t &fname_info : found_files ) + { + printf( "%s%9lu%s [%lxh] %s\n", BGREEN, fname_info.statbuf.st_ino, RESET, + fname_info.statbuf.st_dev, fname_info.filename.c_str() ); + } +} + +static uint32_t get_inotify_procfs_value( const char *basename ) +{ + uint32_t interface_val = 0; + std::string Filename = string_format( "/proc/sys/fs/inotify/%s", basename ); + + FILE *fp = fopen( Filename.c_str(), "r" ); + if ( fp ) + { + if ( fscanf( fp, "%u", &interface_val ) != 1 ) + { + interface_val = 0; + } + fclose( fp ); + } + + return interface_val; +} + +void print_inotify_limits() +{ + uint32_t max_queued_events = get_inotify_procfs_value( "max_queued_events" ); + uint32_t max_user_instances = get_inotify_procfs_value( "max_user_instances" ); + uint32_t max_user_watches = get_inotify_procfs_value( "max_user_watches" ); + + printf( "%sINotify Limits:%s\n", BCYAN, RESET ); + printf( " max_queued_events: %s%u%s\n", BGREEN, max_queued_events, RESET ); + printf( " max_user_instances: %s%u%s\n", BGREEN, max_user_instances, RESET ); + printf( " max_user_watches: %s%u%s\n", BGREEN, max_user_watches, RESET ); +} + +int main( int argc, char *argv[] ) +{ + inotifyapp_t app; + + app.init( argc, argv ); + + print_separator(); + + print_inotify_limits(); + + print_separator(); + + if ( app.init_inotify_proclist() ) + { + app.print_inotify_proclist(); + + print_separator(); + + if ( app.find_files_in_inode_set() ) + { + app.print_found_files(); + } + } + + app.shutdown(); + + return 0; +} diff --git a/inotify-info.h b/inotify-info.h new file mode 100644 index 0000000..fe42811 --- /dev/null +++ b/inotify-info.h @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Michael Sartain + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define RESET "\x1b[0m" +#define GRAY "\x1b[0;30m" +#define RED "\x1b[0;31m" +#define GREEN "\x1b[0;32m" +#define YELLOW "\x1b[0;33m" +#define BLUE "\x1b[0;34m" +#define MAGENTA "\x1b[0;35m" +#define CYAN "\x1b[0;36m" +#define WHITE "\x1b[0;37m" +#define BGRAY "\x1b[1;30m" +#define BRED "\x1b[1;31m" +#define BGREEN "\x1b[1;32m" +#define BYELLOW "\x1b[1;33m" +#define BBLUE "\x1b[1;34m" +#define BMAGENTA "\x1b[1;35m" +#define BCYAN "\x1b[1;36m" +#define BWHITE "\x1b[1;37m" + +#define TO_STR( x ) #x +#define TO_STR_VALUE( x ) TO_STR( x ) + +#define ATTRIBUTE_PRINTF( _x, _y ) __attribute__( ( __format__( __printf__, _x, _y ) ) ) + +#define GCC_DIAG_STR( s ) #s +#define GCC_DIAG_JOINSTR( x, y ) GCC_DIAG_STR( x##y ) +#define GCC_DIAG_DO_PRAGMA( x ) _Pragma( #x ) +#define GCC_DIAG_PRAGMA( x ) GCC_DIAG_DO_PRAGMA( GCC diagnostic x ) + +#define GCC_DIAG_PUSH_OFF( x ) \ + GCC_DIAG_PRAGMA( push ) \ + GCC_DIAG_PRAGMA( ignored GCC_DIAG_JOINSTR( -W, x ) ) +#define GCC_DIAG_POP() GCC_DIAG_PRAGMA( pop ) + +std::string string_formatv(const char *fmt, va_list ap) ATTRIBUTE_PRINTF(1, 0); +std::string string_format(const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2); + diff --git a/lfqueue/LICENSE b/lfqueue/LICENSE new file mode 100644 index 0000000..76f17ad --- /dev/null +++ b/lfqueue/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2018, Taymindis Woon +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lfqueue/lfqueue.c b/lfqueue/lfqueue.c new file mode 100644 index 0000000..fb0686c --- /dev/null +++ b/lfqueue/lfqueue.c @@ -0,0 +1,379 @@ +/* +* +* BSD 2-Clause License +* +* Copyright (c) 2018, Taymindis Woon +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ +#include +#include +#include +#if defined __GNUC__ || defined __CYGWIN__ || defined __MINGW32__ || defined __APPLE__ + +#include +#include // for usleep +#include + +#define __LFQ_VAL_COMPARE_AND_SWAP __sync_val_compare_and_swap +#define __LFQ_BOOL_COMPARE_AND_SWAP __sync_bool_compare_and_swap +#define __LFQ_FETCH_AND_ADD __sync_fetch_and_add +#define __LFQ_ADD_AND_FETCH __sync_add_and_fetch +#define __LFQ_YIELD_THREAD sched_yield +#define __LFQ_SYNC_MEMORY __sync_synchronize + +#else + +#include +#include +#ifdef _WIN64 +inline BOOL __SYNC_BOOL_CAS(LONG64 volatile *dest, LONG64 input, LONG64 comparand) { + return InterlockedCompareExchangeNoFence64(dest, input, comparand) == comparand; +} +#define __LFQ_VAL_COMPARE_AND_SWAP(dest, comparand, input) \ + InterlockedCompareExchangeNoFence64((LONG64 volatile *)dest, (LONG64)input, (LONG64)comparand) +#define __LFQ_BOOL_COMPARE_AND_SWAP(dest, comparand, input) \ + __SYNC_BOOL_CAS((LONG64 volatile *)dest, (LONG64)input, (LONG64)comparand) +#define __LFQ_FETCH_AND_ADD InterlockedExchangeAddNoFence64 +#define __LFQ_ADD_AND_FETCH InterlockedAddNoFence64 +#define __LFQ_SYNC_MEMORY MemoryBarrier + +#else +#ifndef asm +#define asm __asm +#endif +inline BOOL __SYNC_BOOL_CAS(LONG volatile *dest, LONG input, LONG comparand) { + return InterlockedCompareExchangeNoFence(dest, input, comparand) == comparand; +} +#define __LFQ_VAL_COMPARE_AND_SWAP(dest, comparand, input) \ + InterlockedCompareExchangeNoFence((LONG volatile *)dest, (LONG)input, (LONG)comparand) +#define __LFQ_BOOL_COMPARE_AND_SWAP(dest, comparand, input) \ + __SYNC_BOOL_CAS((LONG volatile *)dest, (LONG)input, (LONG)comparand) +#define __LFQ_FETCH_AND_ADD InterlockedExchangeAddNoFence +#define __LFQ_ADD_AND_FETCH InterlockedAddNoFence +#define __LFQ_SYNC_MEMORY() asm mfence + +#endif +#include +#define __LFQ_YIELD_THREAD SwitchToThread +#endif + +#include "lfqueue.h" +#define DEF_LFQ_ASSIGNED_SPIN 2048 + +#if defined __GNUC__ || defined __CYGWIN__ || defined __MINGW32__ || defined __APPLE__ +#define lfq_time_t long +#define lfq_get_curr_time(_time_sec) \ +struct timeval _time_; \ +gettimeofday(&_time_, NULL); \ +*_time_sec = _time_.tv_sec +#define lfq_diff_time(_etime_, _stime_) _etime_ - _stime_ +#else +#define lfq_time_t time_t +#define lfq_get_curr_time(_time_sec) time(_time_sec) +#define lfq_diff_time(_etime_, _stime_) difftime(_etime_, _stime_) +#endif + +struct lfqueue_cas_node_s { + void * value; + struct lfqueue_cas_node_s *next, *nextfree; + lfq_time_t _deactivate_tm; +}; + +//static lfqueue_cas_node_t* __lfq_assigned(lfqueue_t *); +static void __lfq_recycle_free(lfqueue_t *, lfqueue_cas_node_t*); +static void __lfq_check_free(lfqueue_t *); +static void *_dequeue(lfqueue_t *); +static void *_single_dequeue(lfqueue_t *); +static int _enqueue(lfqueue_t *, void* ); +static inline void* _lfqueue_malloc(void* pl, size_t sz) { + return malloc(sz); +} +static inline void _lfqueue_free(void* pl, void* ptr) { + free(ptr); +} + +static void * +_dequeue(lfqueue_t *lfqueue) { + lfqueue_cas_node_t *head, *next; + void *val; + + for (;;) { + head = lfqueue->head; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->head, head, head)) { + next = head->next; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->tail, head, head)) { + if (next == NULL) { + val = NULL; + goto _done; + } + } + else { + if (next) { + val = next->value; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->head, head, next)) { + break; + } + } else { + val = NULL; + goto _done; + } + } + } + } + + __lfq_recycle_free(lfqueue, head); +_done: + // __asm volatile("" ::: "memory"); + __LFQ_SYNC_MEMORY(); + __lfq_check_free(lfqueue); + return val; +} + +static void * +_single_dequeue(lfqueue_t *lfqueue) { + lfqueue_cas_node_t *head, *next; + void *val; + + for (;;) { + head = lfqueue->head; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->head, head, head)) { + next = head->next; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->tail, head, head)) { + if (next == NULL) { + return NULL; + } + } + else { + if (next) { + val = next->value; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->head, head, next)) { + lfqueue->_free(lfqueue->pl, head); + break; + } + } else { + return NULL; + } + } + } + } + return val; +} + +static int +_enqueue(lfqueue_t *lfqueue, void* value) { + lfqueue_cas_node_t *tail, *node; + node = (lfqueue_cas_node_t*) lfqueue->_malloc(lfqueue->pl, sizeof(lfqueue_cas_node_t)); + if (node == NULL) { + perror("malloc"); + return errno; + } + node->value = value; + node->next = NULL; + node->nextfree = NULL; + for (;;) { + __LFQ_SYNC_MEMORY(); + tail = lfqueue->tail; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&tail->next, NULL, node)) { + // compulsory swap as tail->next is no NULL anymore, it has fenced on other thread + __LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->tail, tail, node); + __lfq_check_free(lfqueue); + return 0; + } + } + + /*It never be here*/ + return -1; +} + +static void +__lfq_recycle_free(lfqueue_t *lfqueue, lfqueue_cas_node_t* freenode) { + lfqueue_cas_node_t *freed; + do { + freed = lfqueue->move_free; + } while (!__LFQ_BOOL_COMPARE_AND_SWAP(&freed->nextfree, NULL, freenode) ); + + lfq_get_curr_time(&freenode->_deactivate_tm); + + __LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->move_free, freed, freenode); +} + +static void +__lfq_check_free(lfqueue_t *lfqueue) { + lfq_time_t curr_time; + if (__LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->in_free_mode, 0, 1)) { + lfq_get_curr_time(&curr_time); + lfqueue_cas_node_t *rtfree = lfqueue->root_free, *nextfree; + while ( rtfree && (rtfree != lfqueue->move_free) ) { + nextfree = rtfree->nextfree; + if ( lfq_diff_time(curr_time, rtfree->_deactivate_tm) > 2) { + // printf("%p\n", rtfree); + lfqueue->_free(lfqueue->pl, rtfree); + rtfree = nextfree; + } else { + break; + } + } + lfqueue->root_free = rtfree; + __LFQ_BOOL_COMPARE_AND_SWAP(&lfqueue->in_free_mode, 1, 0); + } + __LFQ_SYNC_MEMORY(); +} + +int +lfqueue_init(lfqueue_t *lfqueue) { + return lfqueue_init_mf(lfqueue, NULL, _lfqueue_malloc, _lfqueue_free); +} + +int +lfqueue_init_mf(lfqueue_t *lfqueue, void* pl, lfqueue_malloc_fn lfqueue_malloc, lfqueue_free_fn lfqueue_free) { + lfqueue->_malloc = lfqueue_malloc; + lfqueue->_free = lfqueue_free; + lfqueue->pl = pl; + + lfqueue_cas_node_t *base = lfqueue_malloc(pl, sizeof(lfqueue_cas_node_t)); + lfqueue_cas_node_t *freebase = lfqueue_malloc(pl, sizeof(lfqueue_cas_node_t)); + if (base == NULL || freebase == NULL) { + perror("malloc"); + return errno; + } + base->value = NULL; + base->next = NULL; + base->nextfree = NULL; + base->_deactivate_tm = 0; + + freebase->value = NULL; + freebase->next = NULL; + freebase->nextfree = NULL; + freebase->_deactivate_tm = 0; + + lfqueue->head = lfqueue->tail = base; // Not yet to be free for first node only + lfqueue->root_free = lfqueue->move_free = freebase; // Not yet to be free for first node only + lfqueue->size = 0; + lfqueue->in_free_mode = 0; + + return 0; +} + +void +lfqueue_destroy(lfqueue_t *lfqueue) { + void* p; + while ((p = lfqueue_deq(lfqueue))) { + lfqueue->_free(lfqueue->pl, p); + } + // Clear the recycle chain nodes + lfqueue_cas_node_t *rtfree = lfqueue->root_free, *nextfree; + while (rtfree && (rtfree != lfqueue->move_free) ) { + nextfree = rtfree->nextfree; + lfqueue->_free(lfqueue->pl, rtfree); + rtfree = nextfree; + } + if (rtfree) { + lfqueue->_free(lfqueue->pl, rtfree); + } + + lfqueue->_free(lfqueue->pl, lfqueue->tail); // Last free + + lfqueue->size = 0; +} + +int +lfqueue_enq(lfqueue_t *lfqueue, void *value) { + if (_enqueue(lfqueue, value)) { + return -1; + } + __LFQ_ADD_AND_FETCH(&lfqueue->size, 1); + return 0; +} + +void* +lfqueue_deq(lfqueue_t *lfqueue) { + void *v; + if (//__LFQ_ADD_AND_FETCH(&lfqueue->size, 0) && + (v = _dequeue(lfqueue)) + ) { + + __LFQ_FETCH_AND_ADD(&lfqueue->size, -1); + return v; + } + return NULL; +} + +void* +lfqueue_deq_must(lfqueue_t *lfqueue) { + void *v; + while ( !(v = _dequeue(lfqueue)) ) { + // Rest the thread for other thread, to avoid keep looping force + lfqueue_sleep(1); + } + __LFQ_FETCH_AND_ADD(&lfqueue->size, -1); + return v; +} + +/**This is only applicable when only single thread consume only**/ +void* +lfqueue_single_deq(lfqueue_t *lfqueue) { + void *v; + if (//__LFQ_ADD_AND_FETCH(&lfqueue->size, 0) && + (v = _single_dequeue(lfqueue)) + ) { + + __LFQ_FETCH_AND_ADD(&lfqueue->size, -1); + return v; + } + return NULL; +} + +/**This is only applicable when only single thread consume only**/ +void* +lfqueue_single_deq_must(lfqueue_t *lfqueue) { + void *v; + while ( !(v = _single_dequeue(lfqueue)) ) { + // Rest the thread for other thread, to avoid keep looping force + lfqueue_sleep(1); + } + __LFQ_FETCH_AND_ADD(&lfqueue->size, -1); + return v; +} + +size_t +lfqueue_size(lfqueue_t *lfqueue) { + return __LFQ_ADD_AND_FETCH(&lfqueue->size, 0); +} + +void +lfqueue_sleep(unsigned int milisec) { +#if defined __GNUC__ || defined __CYGWIN__ || defined __MINGW32__ || defined __APPLE__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-function-declaration" + usleep(milisec * 1000); +#pragma GCC diagnostic pop +#else + Sleep(milisec); +#endif +} + +#ifdef __cplusplus +} +#endif diff --git a/lfqueue/lfqueue.h b/lfqueue/lfqueue.h new file mode 100644 index 0000000..1eafc7b --- /dev/null +++ b/lfqueue/lfqueue.h @@ -0,0 +1,84 @@ +/* +* +* BSD 2-Clause License +* +* Copyright (c) 2018, Taymindis Woon +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* * Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ + +#ifndef LFQUEUE_H +#define LFQUEUE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct lfqueue_cas_node_s lfqueue_cas_node_t; +typedef void* (*lfqueue_malloc_fn)(void*, size_t); +typedef void (*lfqueue_free_fn)(void*, void*); + +#if defined __GNUC__ || defined __CYGWIN__ || defined __MINGW32__ || defined __APPLE__ +#define lfq_bool_t int +#else +#ifdef _WIN64 +#define lfq_bool_t int64_t +#else +#define lfq_bool_t int +#endif +#endif + +typedef struct { + lfqueue_cas_node_t *head, *tail, *root_free, *move_free; + volatile size_t size; + volatile lfq_bool_t in_free_mode; + lfqueue_malloc_fn _malloc; + lfqueue_free_fn _free; + void *pl; +} lfqueue_t; + +extern int lfqueue_init(lfqueue_t *lfqueue); +extern int lfqueue_init_mf(lfqueue_t *lfqueue, void* pl, lfqueue_malloc_fn lfqueue_malloc, lfqueue_free_fn lfqueue_free); +extern int lfqueue_enq(lfqueue_t *lfqueue, void *value); +extern void* lfqueue_deq(lfqueue_t *lfqueue); +extern void* lfqueue_single_deq(lfqueue_t *lfqueue); + +/** loop until value been dequeue, it sleeps 1ms if not found to reduce cpu high usage **/ +extern void* lfqueue_deq_must(lfqueue_t *lfqueue); +extern void* lfqueue_single_deq_must(lfqueue_t *lfqueue); + +extern void lfqueue_destroy(lfqueue_t *lfqueue); +extern size_t lfqueue_size(lfqueue_t *lfqueue); +extern void lfqueue_sleep(unsigned int milisec); + + +#ifdef __cplusplus +} +#endif + +#endif +