@@ -63,6 +63,20 @@ typedef struct {
63
63
int type ;
64
64
/* Boolean flag to determine if the current client (`me`) should be filtered. 1 means "skip me", 0 means otherwise. */
65
65
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 ;
66
80
} clientFilter ;
67
81
68
82
static void clientCommandHelp (client * c );
@@ -91,6 +105,11 @@ char *getClientSockname(client *c);
91
105
static int parseClientFiltersOrReply (client * c , int i , clientFilter * filter );
92
106
static int clientMatchesFilter (client * client , clientFilter client_filter );
93
107
sds 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 );
94
113
95
114
int ProcessingEventsWhileBlocked = 0 ; /* See processEventsWhileBlocked(). */
96
115
__thread sds thread_shared_qb = NULL ;
@@ -3687,6 +3706,34 @@ static int parseClientFiltersOrReply(client *c, int i, clientFilter *filter) {
3687
3706
return C_ERR ;
3688
3707
}
3689
3708
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 ;
3690
3737
} else {
3691
3738
addReplyErrorObject (c , shared .syntaxerr );
3692
3739
return C_ERR ;
@@ -3704,11 +3751,126 @@ static int clientMatchesFilter(client *client, clientFilter client_filter) {
3704
3751
if (client_filter .user && client -> user != client_filter .user ) return 0 ;
3705
3752
if (client_filter .skipme && client == server .current_client ) return 0 ; // Skipme check
3706
3753
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 ;
3707
3764
3708
3765
// If all conditions are satisfied, the client matches the filter.
3709
3766
return 1 ;
3710
3767
}
3711
3768
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
+
3712
3874
static void clientCommandHelp (client * c ) {
3713
3875
const char * help [] = {
3714
3876
"CACHING (YES|NO)" ,
@@ -3730,23 +3892,55 @@ static void clientCommandHelp(client *c) {
3730
3892
"KILL <option> <value> [<option> <value> [...]]" ,
3731
3893
" Kill connections. Options are:" ,
3732
3894
" * ADDR (<ip:port>|<unixsocket>:0)" ,
3733
- " Kill connections made from the specified address" ,
3895
+ " Kill connections made from the specified address. " ,
3734
3896
" * LADDR (<ip:port>|<unixsocket>:0)" ,
3735
- " Kill connections made to specified local address" ,
3897
+ " Kill connections made to the specified local address. " ,
3736
3898
" * TYPE (NORMAL|PRIMARY|REPLICA|PUBSUB)" ,
3737
3899
" Kill connections by type." ,
3738
3900
" * USER <username>" ,
3739
3901
" Kill connections authenticated by <username>." ,
3740
3902
" * SKIPME (YES|NO)" ,
3741
- " Skip killing current connection (default: yes)." ,
3903
+ " Skip killing the current connection (default: yes)." ,
3742
3904
" * ID <client-id>" ,
3743
- " Kill connections by client id ." ,
3905
+ " Kill connections by client ID ." ,
3744
3906
" * MAXAGE <maxage>" ,
3745
3907
" 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." ,
3746
3918
"LIST [options ...]" ,
3747
3919
" Return information about client connections. Options:" ,
3748
3920
" * TYPE (NORMAL|PRIMARY|REPLICA|PUBSUB)" ,
3749
3921
" 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." ,
3750
3944
"UNPAUSE" ,
3751
3945
" Stop the current client pause, resuming traffic." ,
3752
3946
"PAUSE <timeout> [WRITE|ALL]" ,
@@ -3798,11 +3992,11 @@ static void clientCommandList(client *c) {
3798
3992
int i = 2 ;
3799
3993
3800
3994
if (parseClientFiltersOrReply (c , i , & filter ) != C_OK ) {
3801
- zfree ( filter . ids );
3995
+ freeClientFilter ( & filter );
3802
3996
return ;
3803
3997
}
3804
3998
response = getAllFilteredClientsInfoString (& filter , 0 );
3805
- zfree ( filter . ids );
3999
+ freeClientFilter ( & filter );
3806
4000
} else if (c -> argc != 2 ) {
3807
4001
addReplyErrorObject (c , shared .syntaxerr );
3808
4002
return ;
@@ -3909,7 +4103,23 @@ static void clientCommandKill(client *c) {
3909
4103
* only after we queued the reply to its output buffers. */
3910
4104
if (close_this_client ) c -> flag .close_after_reply = 1 ;
3911
4105
client_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
+ }
3913
4123
}
3914
4124
3915
4125
0 commit comments