diff --git a/src/commands.def b/src/commands.def index cd919a80e1..48a03a36d6 100644 --- a/src/commands.def +++ b/src/commands.def @@ -1289,7 +1289,7 @@ commandHistory CLIENT_KILL_History[] = { {"6.2.0","`LADDR` option."}, {"8.0.0","`MAXAGE` option."}, {"8.0.0","Replaced `master` `TYPE` with `primary`. `master` still supported for backward compatibility."}, -{"8.1.0","`ID` option accepts multiple IDs."}, +{"8.1.0","`ID` option accepts multiple IDs. Added filters NAME, MINIDLE, FLAGS, SUBSCRIBED-PATTERN, SUBSCRIBED-CHANNEL, SUBSCRIBED-SHARD-CHANNEL, LIB-NAME, LIB-VER, DB, TOTAL-NET-IN and TOTAL-NET-OUT"}, }; #endif @@ -1328,12 +1328,23 @@ struct COMMAND_ARG CLIENT_KILL_filter_new_format_Subargs[] = { {MAKE_ARG("laddr",ARG_TYPE_STRING,-1,"LADDR",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"}, {MAKE_ARG("skipme",ARG_TYPE_ONEOF,-1,"SKIPME",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_KILL_filter_new_format_skipme_Subargs}, {MAKE_ARG("maxage",ARG_TYPE_INTEGER,-1,"MAXAGE",NULL,"8.0.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("name",ARG_TYPE_STRING,-1,"NAME",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("minidle",ARG_TYPE_INTEGER,-1,"MINIDLE",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("flags",ARG_TYPE_STRING,-1,"FLAGS",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("subscribed-pattern",ARG_TYPE_STRING,-1,"SUBSCRIBED-PATTERN",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("subscribed-channel",ARG_TYPE_STRING,-1,"SUBSCRIBED-CHANNEL",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("subscribed-shard-channel",ARG_TYPE_STRING,-1,"SUBSCRIBED-SHARD-CHANNEL",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("lib-name",ARG_TYPE_STRING,-1,"LIB-NAME",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("lib-ver",ARG_TYPE_STRING,-1,"LIB-VER",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("db",ARG_TYPE_INTEGER,-1,"DB",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("total-net-in",ARG_TYPE_INTEGER,-1,"TOTAL-NET-IN",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("total-net-out",ARG_TYPE_INTEGER,-1,"TOTAL-NET-OUT",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /* CLIENT KILL filter argument table */ struct COMMAND_ARG CLIENT_KILL_filter_Subargs[] = { {MAKE_ARG("old-format",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,"2.8.12"),.display_text="ip:port"}, -{MAKE_ARG("new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,7,NULL),.subargs=CLIENT_KILL_filter_new_format_Subargs}, +{MAKE_ARG("new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,18,NULL),.subargs=CLIENT_KILL_filter_new_format_Subargs}, }; /* CLIENT KILL argument table */ @@ -1353,7 +1364,7 @@ commandHistory CLIENT_LIST_History[] = { {"7.0.0","Added `resp`, `multi-mem`, `rbs` and `rbp` fields."}, {"7.0.3","Added `ssub` field."}, {"8.0.0","Replaced `master` `TYPE` with `primary`. `master` still supported for backward compatibility."}, -{"8.1.0","Added filters USER, ADDR, LADDR, SKIPME, and MAXAGE"}, +{"8.1.0","Added filters USER, ADDR, LADDR, SKIPME, MAXAGE, NAME, MINIDLE, FLAGS, SUBSCRIBED-PATTERN, SUBSCRIBED-CHANNEL, SUBSCRIBED-SHARD-CHANNEL, LIB-NAME, LIB-VER, DB, TOTAL-NET-IN and TOTAL-NET-OUT"}, }; #endif @@ -1392,6 +1403,17 @@ struct COMMAND_ARG CLIENT_LIST_Args[] = { {MAKE_ARG("laddr",ARG_TYPE_STRING,-1,"LADDR",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"}, {MAKE_ARG("skipme",ARG_TYPE_ONEOF,-1,"SKIPME",NULL,"8.1.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_LIST_skipme_Subargs}, {MAKE_ARG("maxage",ARG_TYPE_INTEGER,-1,"MAXAGE",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("name",ARG_TYPE_STRING,-1,"NAME",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("minidle",ARG_TYPE_INTEGER,-1,"MINIDLE",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("flags",ARG_TYPE_STRING,-1,"FLAGS",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("subscribed-pattern",ARG_TYPE_STRING,-1,"SUBSCRIBED-PATTERN",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("subscribed-channel",ARG_TYPE_STRING,-1,"SUBSCRIBED-CHANNEL",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("subscribed-shard-channel",ARG_TYPE_STRING,-1,"SUBSCRIBED-SHARD-CHANNEL",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("lib-name",ARG_TYPE_STRING,-1,"LIB-NAME",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("lib-ver",ARG_TYPE_STRING,-1,"LIB-VER",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("db",ARG_TYPE_INTEGER,-1,"DB",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("total-net-in",ARG_TYPE_INTEGER,-1,"TOTAL-NET-IN",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("total-net-out",ARG_TYPE_INTEGER,-1,"TOTAL-NET-OUT",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** CLIENT NO_EVICT ********************/ @@ -1674,7 +1696,7 @@ struct COMMAND_STRUCT CLIENT_Subcommands[] = { {MAKE_CMD("import-source","Mark this client as an import source when server is in import mode.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_IMPORT_SOURCE_History,0,CLIENT_IMPORT_SOURCE_Tips,0,clientImportSourceCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_IMPORT_SOURCE_Keyspecs,0,NULL,1),.args=CLIENT_IMPORT_SOURCE_Args}, {MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientInfoCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)}, {MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,8,CLIENT_KILL_Tips,0,clientKillCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args}, -{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,8,CLIENT_LIST_Tips,1,clientListCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,7),.args=CLIENT_LIST_Args}, +{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,8,CLIENT_LIST_Tips,1,clientListCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,18),.args=CLIENT_LIST_Args}, {MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientNoEvictCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args}, {MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientNoTouchCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args}, {MAKE_CMD("pause","Suspends commands processing.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,1,CLIENT_PAUSE_Tips,0,clientPauseCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_PAUSE_Keyspecs,0,NULL,2),.args=CLIENT_PAUSE_Args}, diff --git a/src/commands/client-kill.json b/src/commands/client-kill.json index 0ae3579534..5887260c1f 100644 --- a/src/commands/client-kill.json +++ b/src/commands/client-kill.json @@ -38,7 +38,7 @@ ], [ "8.1.0", - "`ID` option accepts multiple IDs." + "`ID` option accepts multiple IDs. Added filters NAME, MINIDLE, FLAGS, SUBSCRIBED-PATTERN, SUBSCRIBED-CHANNEL, SUBSCRIBED-SHARD-CHANNEL, LIB-NAME, LIB-VER, DB, TOTAL-NET-IN and TOTAL-NET-OUT" ] ], "command_flags": [ @@ -162,6 +162,83 @@ "type": "integer", "optional": true, "since": "8.0.0" + }, + { + "token": "NAME", + "name": "name", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "MINIDLE", + "name": "minidle", + "type": "integer", + "optional": true, + "since": "8.1.0" + }, + { + "token": "FLAGS", + "name": "flags", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "SUBSCRIBED-PATTERN", + "name": "subscribed-pattern", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "SUBSCRIBED-CHANNEL", + "name": "subscribed-channel", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "SUBSCRIBED-SHARD-CHANNEL", + "name": "subscribed-shard-channel", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "LIB-NAME", + "name": "lib-name", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "LIB-VER", + "name": "lib-ver", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "DB", + "name": "db", + "type": "integer", + "optional": true, + "since": "8.1.0" + }, + { + "token": "TOTAL-NET-IN", + "name": "total-net-in", + "type": "integer", + "optional": true, + "since": "8.1.0" + }, + { + "token": "TOTAL-NET-OUT", + "name": "total-net-out", + "type": "integer", + "optional": true, + "since": "8.1.0" } ] } diff --git a/src/commands/client-list.json b/src/commands/client-list.json index 05e4de2419..bfc39ddc0b 100644 --- a/src/commands/client-list.json +++ b/src/commands/client-list.json @@ -38,7 +38,7 @@ ], [ "8.1.0", - "Added filters USER, ADDR, LADDR, SKIPME, and MAXAGE" + "Added filters USER, ADDR, LADDR, SKIPME, MAXAGE, NAME, MINIDLE, FLAGS, SUBSCRIBED-PATTERN, SUBSCRIBED-CHANNEL, SUBSCRIBED-SHARD-CHANNEL, LIB-NAME, LIB-VER, DB, TOTAL-NET-IN and TOTAL-NET-OUT" ] ], "command_flags": [ @@ -144,6 +144,83 @@ "type": "integer", "optional": true, "since": "8.1.0" + }, + { + "token": "NAME", + "name": "name", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "MINIDLE", + "name": "minidle", + "type": "integer", + "optional": true, + "since": "8.1.0" + }, + { + "token": "FLAGS", + "name": "flags", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "SUBSCRIBED-PATTERN", + "name": "subscribed-pattern", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "SUBSCRIBED-CHANNEL", + "name": "subscribed-channel", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "SUBSCRIBED-SHARD-CHANNEL", + "name": "subscribed-shard-channel", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "LIB-NAME", + "name": "lib-name", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "LIB-VER", + "name": "lib-ver", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "DB", + "name": "db", + "type": "integer", + "optional": true, + "since": "8.1.0" + }, + { + "token": "TOTAL-NET-IN", + "name": "total-net-in", + "type": "integer", + "optional": true, + "since": "8.1.0" + }, + { + "token": "TOTAL-NET-OUT", + "name": "total-net-out", + "type": "integer", + "optional": true, + "since": "8.1.0" } ] } diff --git a/src/networking.c b/src/networking.c index 0ff0fed4c0..1eaeb28c86 100644 --- a/src/networking.c +++ b/src/networking.c @@ -64,6 +64,30 @@ typedef struct { int type; /* Boolean flag to determine if the current client (`me`) should be filtered. 1 means "skip me", 0 means otherwise. */ int skipme; + /* Client name to filter. If NULL, no name filtering is applied. */ + char *name; + /* Minimum idle time (in seconds) of a client connection for filtering. + * Connections with idle time more than this value will match. + * A value of 0 means no idle time filtering. */ + long long min_idle; + /* Client flags for filtering. If NULL, no filtering is applied. */ + char *flags; + /* Client subscribed pattern for filtering. If NULL, no filtering is applied. */ + robj *subscribed_pattern; + /* Client subscribed channel for filtering. If NULL, no filtering is applied. */ + robj *subscribed_channel; + /* Client subscribed shard channel for filtering. If NULL, no filtering is applied. */ + robj *subscribed_shard_channel; + /* Library name to filter. If NULL, no library name filtering is applied. */ + robj *lib_name; + /* Library version to filter. If NULL, no library version filtering is applied. */ + robj *lib_ver; + /* Database index to filter. If set to -1, no DB number filtering is applied. */ + int db_number; + /* Total network input bytes to filter. If set to -1, no filtering is applied. */ + unsigned long long tot_net_in; + /* Total network output bytes to filter. If set to -1, no filtering is applied. */ + unsigned long long tot_net_out; } clientFilter; static void setProtocolError(const char *errstr, client *c); @@ -73,6 +97,11 @@ char *getClientSockname(client *c); static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter); static int clientMatchesFilter(client *client, clientFilter client_filter); static sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide_user_data); +static int clientMatchesFlagFilter(client *c, const char *flag_filter); +static int clientSubscribedToChannel(client *client, robj *channel); +static int clientSubscribedToShardChannel(client *client, robj *channel); +static int clientSubscribedToPattern(client *client, robj *pattern); +static void freeClientFilter(clientFilter *filter); int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */ __thread sds thread_shared_qb = NULL; @@ -3585,6 +3614,73 @@ static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter) return C_ERR; } index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "minidle") && moreargs) { + long long tmp; + + if (getLongLongFromObjectOrReply(c, c->argv[index + 1], &tmp, + "minidle is not an integer or out of range") != C_OK) + return C_ERR; + if (tmp <= 0) { + addReplyError(c, "minidle should be greater than 0"); + return C_ERR; + } + + filter->min_idle = tmp; + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "flags") && moreargs) { + filter->flags = c->argv[index + 1]->ptr; + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "name") && moreargs) { + filter->name = c->argv[index + 1]->ptr; + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "subscribed-pattern") && moreargs) { + filter->subscribed_pattern = createObject(OBJ_STRING, sdsnew(c->argv[index + 1]->ptr)); + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "subscribed-channel") && moreargs) { + filter->subscribed_channel = createObject(OBJ_STRING, sdsnew(c->argv[index + 1]->ptr)); + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "subscribed-shard-channel") && moreargs) { + filter->subscribed_shard_channel = createObject(OBJ_STRING, sdsnew(c->argv[index + 1]->ptr)); + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "lib-name") && moreargs) { + filter->lib_name = createObject(OBJ_STRING, sdsnew(c->argv[index + 1]->ptr)); + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "lib-ver") && moreargs) { + filter->lib_ver = createObject(OBJ_STRING, sdsnew(c->argv[index + 1]->ptr)); + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "db") && moreargs) { + int tmp; + if (getIntFromObjectOrReply(c, c->argv[index + 1], &tmp, + "db is not an integer or out of range") != C_OK) + return C_ERR; + if (tmp < 0 || tmp >= server.dbnum) { + addReplyErrorFormat(c, "db number should be between 0 and %d", server.dbnum - 1); + return C_ERR; + } + filter->db_number = tmp; + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "tot-net-in") && moreargs) { + long long tmp; + if (getLongLongFromObjectOrReply(c, c->argv[index + 1], &tmp, + "tot-net-in is not an integer or out of range") != C_OK) + return C_ERR; + if (tmp < 0) { + addReplyError(c, "tot-net-in should be non-negative"); + return C_ERR; + } + filter->tot_net_in = tmp; + index += 2; + } else if (!strcasecmp(c->argv[index]->ptr, "tot-net-out") && moreargs) { + long long tmp; + if (getLongLongFromObjectOrReply(c, c->argv[index + 1], &tmp, + "tot-net-out is not an integer or out of range") != C_OK) + return C_ERR; + if (tmp < 0) { + addReplyError(c, "tot-net-out should be non-negative"); + return C_ERR; + } + filter->tot_net_out = tmp; + index += 2; } else { addReplyErrorObject(c, shared.syntaxerr); return C_ERR; @@ -3602,11 +3698,130 @@ static int clientMatchesFilter(client *client, clientFilter client_filter) { if (client_filter.user && client->user != client_filter.user) return 0; if (client_filter.skipme && client == server.current_client) return 0; if (client_filter.max_age != 0 && (long long)(commandTimeSnapshot() / 1000 - client->ctime) < client_filter.max_age) return 0; + if (client_filter.min_idle != 0 && (long long)(commandTimeSnapshot() / 1000 - client->last_interaction) < client_filter.min_idle) return 0; + if (client_filter.flags && clientMatchesFlagFilter(client, client_filter.flags) == 0) return 0; + if (client_filter.name) { + if (!client->name || !client->name->ptr || strcmp(client->name->ptr, client_filter.name) != 0) { + return 0; + } + } + if (client_filter.subscribed_pattern && !clientSubscribedToPattern(client, client_filter.subscribed_pattern)) return 0; + if (client_filter.subscribed_channel && !clientSubscribedToChannel(client, client_filter.subscribed_channel)) return 0; + if (client_filter.subscribed_shard_channel && !clientSubscribedToShardChannel(client, client_filter.subscribed_shard_channel)) return 0; + if (client_filter.lib_name && (!client->lib_name || compareStringObjects(client->lib_name, client_filter.lib_name) != 0)) return 0; + if (client_filter.lib_ver && (!client->lib_ver || compareStringObjects(client->lib_ver, client_filter.lib_ver) != 0)) return 0; + if (client_filter.db_number != -1 && client->db->id != client_filter.db_number) return 0; + if (client->net_input_bytes < client_filter.tot_net_in) return 0; + if (client->net_output_bytes < client_filter.tot_net_out) return 0; /* If all conditions are satisfied, the client matches the filter. */ return 1; } +static int clientMatchesFlagFilter(client *c, const char *flag_filter) { + /* Iterate through the provided flag filter string */ + for (int i = 0; flag_filter[i] != '\0'; i++) { + const char flag = flag_filter[i]; + + /* Check each flag */ + switch (flag) { + case 'O': + if (!(c->flag.replica && c->flag.monitor)) return 0; + break; + case 'S': /* Replica flag */ + if (!c->flag.replica) return 0; + break; + case 'M': /* Primary flag */ + if (!c->flag.primary) return 0; + break; + case 'P': /* PubSub flag */ + if (!c->flag.pubsub) return 0; + break; + case 'x': /* Multi flag */ + if (!c->flag.multi) return 0; + break; + case 'b': /* Blocked flag */ + if (!c->flag.blocked) return 0; + break; + case 't': /* Tracking flag */ + if (!c->flag.tracking) return 0; + break; + case 'R': /* Invalid Client flag */ + if (!c->flag.tracking_broken_redir) return 0; + break; + case 'B': /* Tracking Bcast flag */ + if (!c->flag.tracking_bcast) return 0; + break; + case 'd': /* Dirty CAS flag */ + if (!c->flag.dirty_cas) return 0; + break; + case 'c': /* Close after reply flag */ + if (!c->flag.close_after_reply) return 0; + break; + case 'u': /* Unblocked flag */ + if (!c->flag.unblocked) return 0; + break; + case 'A': /* Close ASAP flag */ + if (!c->flag.close_asap) return 0; + break; + case 'U': /* Unix socket flag */ + if (!c->flag.unix_socket) return 0; + break; + case 'r': /* Readonly flag */ + if (!c->flag.readonly) return 0; + break; + case 'e': /* No evict flag */ + if (!c->flag.no_evict) return 0; + break; + case 'T': /* No touch flag */ + if (!c->flag.no_touch) return 0; + break; + case 'I': /* Import source flag */ + if (!c->flag.import_source) return 0; + break; + case 'N': /* Check for no flags */ + if (!c->flag.replica && !c->flag.primary && !c->flag.pubsub && + !c->flag.multi && !c->flag.blocked && !c->flag.tracking && + !c->flag.tracking_broken_redir && !c->flag.tracking_bcast && + !c->flag.dirty_cas && !c->flag.close_after_reply && + !c->flag.unblocked && !c->flag.close_asap && + !c->flag.unix_socket && !c->flag.readonly && + !c->flag.no_evict && !c->flag.no_touch && + !c->flag.import_source) { + return 1; /* Matches 'N' */ + } + break; + default: + /* Invalid flag, return false */ + return 0; + } + } + /* If the loop completes, the client matches the flag filter */ + return 1; +} + +static int clientSubscribedToChannel(client *client, robj *channel) { + if (client == NULL || client->pubsub_data == NULL || client->pubsub_data->pubsub_channels == NULL) { + return 0; + } + return dictFind(client->pubsub_data->pubsub_channels, channel) != NULL; +} + +static int clientSubscribedToShardChannel(client *client, robj *channel) { + if (client == NULL || client->pubsub_data == NULL || client->pubsub_data->pubsubshard_channels == NULL) { + return 0; + } + return dictFind(client->pubsub_data->pubsubshard_channels, channel) != NULL; +} + +static int clientSubscribedToPattern(client *client, robj *pattern) { + if (client == NULL || client->pubsub_data == NULL || client->pubsub_data->pubsub_patterns == NULL) { + return 0; + } + return dictFind(client->pubsub_data->pubsub_patterns, pattern) != NULL; +} + + void clientHelpCommand(client *c) { const char *help[] = { "CACHING (YES|NO)", @@ -3628,9 +3843,9 @@ void clientHelpCommand(client *c) { "KILL