@@ -63,6 +63,20 @@ typedef struct {
6363 int type ;
6464 /* Boolean flag to determine if the current client (`me`) should be filtered. 1 means "skip me", 0 means otherwise. */
6565 int skipme ;
66+ /* Client name to filter. If NULL, no name filtering is applied. */
67+ char * name ;
68+ /* Minimum idle time (in seconds) of a client connection for filtering.
69+ * Connections with idle time more than this value will match.
70+ * A value of 0 means no idle time filtering. */
71+ long long min_idle ;
72+ /* Client flags for filtering. If NULL, no filtering is applied. */
73+ char * flags ;
74+ /* Client pattern for filtering. If NULL, no filtering is applied. */
75+ robj * pattern ;
76+ /* Client channel for filtering. If NULL, no filtering is applied. */
77+ robj * channel ;
78+ /* Client shard channel for filtering. If NULL, no filtering is applied. */
79+ robj * shard_channel ;
6680} clientFilter ;
6781
6882static void clientCommandHelp (client * c );
@@ -91,6 +105,11 @@ char *getClientSockname(client *c);
91105static int parseClientFiltersOrReply (client * c , int i , clientFilter * filter );
92106static int clientMatchesFilter (client * client , clientFilter client_filter );
93107sds getAllFilteredClientsInfoString (clientFilter * client_filter , int hide_user_data );
108+ static int clientMatchesFlagFilter (client * c , const char * flag_filter );
109+ static int clientSubscribedToChannel (client * client , robj * channel );
110+ static int clientSubscribedToShardChannel (client * client , robj * channel );
111+ static int clientSubscribedToPattern (client * client , robj * pattern );
112+ static void freeClientFilter (clientFilter * filter );
94113
95114int ProcessingEventsWhileBlocked = 0 ; /* See processEventsWhileBlocked(). */
96115__thread sds thread_shared_qb = NULL ;
@@ -3687,6 +3706,34 @@ static int parseClientFiltersOrReply(client *c, int i, clientFilter *filter) {
36873706 return C_ERR ;
36883707 }
36893708 i += 2 ;
3709+ } else if (!strcasecmp (c -> argv [i ]-> ptr , "minidle" ) && moreargs ) {
3710+ long long tmp ;
3711+
3712+ if (getLongLongFromObjectOrReply (c , c -> argv [i + 1 ], & tmp ,
3713+ "minidle is not an integer or out of range" ) != C_OK )
3714+ return C_ERR ;
3715+ if (tmp <= 0 ) {
3716+ addReplyError (c , "minidle should be greater than 0" );
3717+ return C_ERR ;
3718+ }
3719+
3720+ filter -> min_idle = tmp ;
3721+ i += 2 ;
3722+ } else if (!strcasecmp (c -> argv [i ]-> ptr , "flags" ) && moreargs ) {
3723+ filter -> flags = c -> argv [i + 1 ]-> ptr ;
3724+ i += 2 ;
3725+ } else if (!strcasecmp (c -> argv [i ]-> ptr , "name" ) && moreargs ) {
3726+ filter -> name = c -> argv [i + 1 ]-> ptr ;
3727+ i += 2 ;
3728+ } else if (!strcasecmp (c -> argv [i ]-> ptr , "pattern" ) && moreargs ) {
3729+ filter -> pattern = createObject (OBJ_STRING , sdsnew (c -> argv [i + 1 ]-> ptr ));
3730+ i += 2 ;
3731+ }else if (!strcasecmp (c -> argv [i ]-> ptr , "channel" ) && moreargs ) {
3732+ filter -> channel = createObject (OBJ_STRING , sdsnew (c -> argv [i + 1 ]-> ptr ));
3733+ i += 2 ;
3734+ }else if (!strcasecmp (c -> argv [i ]-> ptr , "shardchannel" ) && moreargs ) {
3735+ filter -> shard_channel = createObject (OBJ_STRING , sdsnew (c -> argv [i + 1 ]-> ptr ));
3736+ i += 2 ;
36903737 } else {
36913738 addReplyErrorObject (c , shared .syntaxerr );
36923739 return C_ERR ;
@@ -3704,11 +3751,126 @@ static int clientMatchesFilter(client *client, clientFilter client_filter) {
37043751 if (client_filter .user && client -> user != client_filter .user ) return 0 ;
37053752 if (client_filter .skipme && client == server .current_client ) return 0 ; // Skipme check
37063753 if (client_filter .max_age != 0 && (long long )(commandTimeSnapshot () / 1000 - client -> ctime ) < client_filter .max_age ) return 0 ;
3754+ if (client_filter .min_idle != 0 && (long long )(commandTimeSnapshot () / 1000 - client -> last_interaction ) < client_filter .min_idle ) return 0 ;
3755+ if (client_filter .flags && clientMatchesFlagFilter (client , client_filter .flags ) == 0 ) return 0 ;
3756+ if (client_filter .name ) {
3757+ if (!client -> name || !client -> name -> ptr || strcmp (client -> name -> ptr , client_filter .name ) != 0 ) {
3758+ return 0 ;
3759+ }
3760+ }
3761+ if (client_filter .pattern && !clientSubscribedToPattern (client , client_filter .pattern )) return 0 ;
3762+ if (client_filter .channel && !clientSubscribedToChannel (client , client_filter .channel )) return 0 ;
3763+ if (client_filter .shard_channel && !clientSubscribedToShardChannel (client , client_filter .shard_channel )) return 0 ;
37073764
37083765 // If all conditions are satisfied, the client matches the filter.
37093766 return 1 ;
37103767}
37113768
3769+ /* Function to check if the client has the required flags as per the filter string */
3770+ static int clientMatchesFlagFilter (client * c , const char * flag_filter ) {
3771+ // Iterate through the provided flag filter string
3772+ for (int i = 0 ; flag_filter [i ] != '\0' ; i ++ ) {
3773+ const char flag = flag_filter [i ];
3774+
3775+ // Check each flag
3776+ switch (flag ) {
3777+ case 'O' :
3778+ if (!(c -> flag .replica && c -> flag .monitor )) return 0 ;
3779+ break ;
3780+ case 'S' : // Replica flag
3781+ if (!c -> flag .replica ) return 0 ;
3782+ break ;
3783+ case 'M' : // Primary flag
3784+ if (!c -> flag .primary ) return 0 ;
3785+ break ;
3786+ case 'P' : // PubSub flag
3787+ if (!c -> flag .pubsub ) return 0 ;
3788+ break ;
3789+ case 'x' : // Multi flag
3790+ if (!c -> flag .multi ) return 0 ;
3791+ break ;
3792+ case 'b' : // Blocked flag
3793+ if (!c -> flag .blocked ) return 0 ;
3794+ break ;
3795+ case 't' : // Tracking flag
3796+ if (!c -> flag .tracking ) return 0 ;
3797+ break ;
3798+ case 'R' : // Invalid Client flag
3799+ if (!c -> flag .tracking_broken_redir ) return 0 ;
3800+ break ;
3801+ case 'B' : // Tracking Bcast flag
3802+ if (!c -> flag .tracking_bcast ) return 0 ;
3803+ break ;
3804+ case 'd' : // Dirty CAS flag
3805+ if (!c -> flag .dirty_cas ) return 0 ;
3806+ break ;
3807+ case 'c' : // Close after reply flag
3808+ if (!c -> flag .close_after_reply ) return 0 ;
3809+ break ;
3810+ case 'u' : // Unblocked flag
3811+ if (!c -> flag .unblocked ) return 0 ;
3812+ break ;
3813+ case 'A' : // Close ASAP flag
3814+ if (!c -> flag .close_asap ) return 0 ;
3815+ break ;
3816+ case 'U' : // Unix socket flag
3817+ if (!c -> flag .unix_socket ) return 0 ;
3818+ break ;
3819+ case 'r' : // Readonly flag
3820+ if (!c -> flag .readonly ) return 0 ;
3821+ break ;
3822+ case 'e' : // No evict flag
3823+ if (!c -> flag .no_evict ) return 0 ;
3824+ break ;
3825+ case 'T' : // No touch flag
3826+ if (!c -> flag .no_touch ) return 0 ;
3827+ break ;
3828+ case 'I' : // Import source flag
3829+ if (!c -> flag .import_source ) return 0 ;
3830+ break ;
3831+ case 'N' : // Check for no flags
3832+ if (!c -> flag .replica && !c -> flag .primary && !c -> flag .pubsub &&
3833+ !c -> flag .multi && !c -> flag .blocked && !c -> flag .tracking &&
3834+ !c -> flag .tracking_broken_redir && !c -> flag .tracking_bcast &&
3835+ !c -> flag .dirty_cas && !c -> flag .close_after_reply &&
3836+ !c -> flag .unblocked && !c -> flag .close_asap &&
3837+ !c -> flag .unix_socket && !c -> flag .readonly &&
3838+ !c -> flag .no_evict && !c -> flag .no_touch &&
3839+ !c -> flag .import_source ) {
3840+ return 1 ; // Matches 'N'
3841+ }
3842+ break ;
3843+ default :
3844+ // Invalid flag, return false
3845+ return 0 ;
3846+ }
3847+ }
3848+ // If the loop completes, the client matches the flag filter
3849+ return 1 ;
3850+ }
3851+
3852+ static int clientSubscribedToChannel (client * client , robj * channel ) {
3853+ if (client == NULL || client -> pubsub_channels == NULL ) {
3854+ return 0 ;
3855+ }
3856+ return dictFind (client -> pubsub_channels , channel ) != NULL ;
3857+ }
3858+
3859+ static int clientSubscribedToShardChannel (client * client , robj * channel ) {
3860+ if (client == NULL || client -> pubsubshard_channels == NULL ) {
3861+ return 0 ;
3862+ }
3863+ return dictFind (client -> pubsubshard_channels , channel ) != NULL ;
3864+ }
3865+
3866+ static int clientSubscribedToPattern (client * client , robj * pattern ) {
3867+ if (client == NULL || client -> pubsub_patterns == NULL ) {
3868+ return 0 ;
3869+ }
3870+ return dictFind (client -> pubsub_patterns , pattern ) != NULL ;
3871+ }
3872+
3873+
37123874static void clientCommandHelp (client * c ) {
37133875 const char * help [] = {
37143876 "CACHING (YES|NO)" ,
@@ -3730,23 +3892,55 @@ static void clientCommandHelp(client *c) {
37303892 "KILL <option> <value> [<option> <value> [...]]" ,
37313893 " Kill connections. Options are:" ,
37323894 " * ADDR (<ip:port>|<unixsocket>:0)" ,
3733- " Kill connections made from the specified address" ,
3895+ " Kill connections made from the specified address. " ,
37343896 " * LADDR (<ip:port>|<unixsocket>:0)" ,
3735- " Kill connections made to specified local address" ,
3897+ " Kill connections made to the specified local address. " ,
37363898 " * TYPE (NORMAL|PRIMARY|REPLICA|PUBSUB)" ,
37373899 " Kill connections by type." ,
37383900 " * USER <username>" ,
37393901 " Kill connections authenticated by <username>." ,
37403902 " * SKIPME (YES|NO)" ,
3741- " Skip killing current connection (default: yes)." ,
3903+ " Skip killing the current connection (default: yes)." ,
37423904 " * ID <client-id>" ,
3743- " Kill connections by client id ." ,
3905+ " Kill connections by client ID ." ,
37443906 " * MAXAGE <maxage>" ,
37453907 " Kill connections older than the specified age." ,
3908+ " * FLAGS <flags>" ,
3909+ " Kill connections that include the specified flags." ,
3910+ " * NAME <client-name>" ,
3911+ " Kill connections with the specified name." ,
3912+ " * PATTERN <pattern>" ,
3913+ " Kill connections subscribed to a matching pattern." ,
3914+ " * CHANNEL <channel>" ,
3915+ " Kill connections subscribed to a matching channel." ,
3916+ " * SHARD-CHANNEL <shard-channel>" ,
3917+ " Kill connections subscribed to a matching shard channel." ,
37463918 "LIST [options ...]" ,
37473919 " Return information about client connections. Options:" ,
37483920 " * TYPE (NORMAL|PRIMARY|REPLICA|PUBSUB)" ,
37493921 " Return clients of specified type." ,
3922+ " * USER <username>" ,
3923+ " Return clients authenticated by <username>." ,
3924+ " * ADDR <ip:port>" ,
3925+ " Return clients connected from the specified address." ,
3926+ " * LADDR <ip:port>" ,
3927+ " Return clients connected to the specified local address." ,
3928+ " * ID <client-id>" ,
3929+ " Return clients with the specified IDs." ,
3930+ " * SKIPME (YES|NO)" ,
3931+ " Exclude the current client from the list (default: no)." ,
3932+ " * FLAGS <flags>" ,
3933+ " Return clients with the specified flags." ,
3934+ " * NAME <client-name>" ,
3935+ " Return clients with the specified name." ,
3936+ " * MIN-IDLE <min-idle>" ,
3937+ " Return clients with idle time greater than or equal to <min-idle> seconds." ,
3938+ " * PATTERN <pattern>" ,
3939+ " Return clients subscribed to a matching pattern." ,
3940+ " * CHANNEL <channel>" ,
3941+ " Return clients subscribed to the specified channel." ,
3942+ " * SHARD-CHANNEL <shard-channel>" ,
3943+ " Return clients subscribed to the specified shard channel." ,
37503944 "UNPAUSE" ,
37513945 " Stop the current client pause, resuming traffic." ,
37523946 "PAUSE <timeout> [WRITE|ALL]" ,
@@ -3798,11 +3992,11 @@ static void clientCommandList(client *c) {
37983992 int i = 2 ;
37993993
38003994 if (parseClientFiltersOrReply (c , i , & filter ) != C_OK ) {
3801- zfree ( filter . ids );
3995+ freeClientFilter ( & filter );
38023996 return ;
38033997 }
38043998 response = getAllFilteredClientsInfoString (& filter , 0 );
3805- zfree ( filter . ids );
3999+ freeClientFilter ( & filter );
38064000 } else if (c -> argc != 2 ) {
38074001 addReplyErrorObject (c , shared .syntaxerr );
38084002 return ;
@@ -3909,7 +4103,23 @@ static void clientCommandKill(client *c) {
39094103 * only after we queued the reply to its output buffers. */
39104104 if (close_this_client ) c -> flag .close_after_reply = 1 ;
39114105client_kill_done :
3912- zfree (client_filter .ids );
4106+ freeClientFilter (& client_filter );
4107+ }
4108+
4109+ static void freeClientFilter (clientFilter * filter ) {
4110+ zfree (filter -> ids );
4111+ if (filter -> pattern ) {
4112+ decrRefCount (filter -> pattern );
4113+ filter -> pattern = NULL ;
4114+ }
4115+ if (filter -> shard_channel ) {
4116+ decrRefCount (filter -> shard_channel );
4117+ filter -> shard_channel = NULL ;
4118+ }
4119+ if (filter -> channel ) {
4120+ decrRefCount (filter -> channel );
4121+ filter -> channel = NULL ;
4122+ }
39134123}
39144124
39154125
0 commit comments