1 /* 2 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * * Neither the name of Redis nor the names of its contributors may be used 14 * to endorse or promote products derived from this software without 15 * specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include "redis.h" 31 32 /*----------------------------------------------------------------------------- 33 * Pubsub low level API 34 *----------------------------------------------------------------------------*/ 35 36 void freePubsubPattern(void *p) { 37 pubsubPattern *pat = p; 38 39 decrRefCount(pat->pattern); 40 zfree(pat); 41 } 42 43 int listMatchPubsubPattern(void *a, void *b) { 44 pubsubPattern *pa = a, *pb = b; 45 46 return (pa->client == pb->client) && 47 (equalStringObjects(pa->pattern,pb->pattern)); 48 } 49 50 /* Subscribe a client to a channel. Returns 1 if the operation succeeded, or 51 * 0 if the client was already subscribed to that channel. */ 52 int pubsubSubscribeChannel(redisClient *c, robj *channel) { 53 struct dictEntry *de; 54 list *clients = NULL; 55 int retval = 0; 56 57 /* Add the channel to the client -> channels hash table */ 58 if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) { 59 retval = 1; 60 incrRefCount(channel); 61 /* Add the client to the channel -> list of clients hash table */ 62 de = dictFind(server.pubsub_channels,channel); 63 if (de == NULL) { 64 clients = listCreate(); 65 dictAdd(server.pubsub_channels,channel,clients); 66 incrRefCount(channel); 67 } else { 68 clients = dictGetVal(de); 69 } 70 listAddNodeTail(clients,c); 71 } 72 /* Notify the client */ 73 addReply(c,shared.mbulkhdr[3]); 74 addReply(c,shared.subscribebulk); 75 addReplyBulk(c,channel); 76 addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); 77 return retval; 78 } 79 80 /* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or 81 * 0 if the client was not subscribed to the specified channel. */ 82 int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) { 83 struct dictEntry *de; 84 list *clients; 85 listNode *ln; 86 int retval = 0; 87 88 /* Remove the channel from the client -> channels hash table */ 89 incrRefCount(channel); /* channel may be just a pointer to the same object 90 we have in the hash tables. Protect it... */ 91 if (dictDelete(c->pubsub_channels,channel) == DICT_OK) { 92 retval = 1; 93 /* Remove the client from the channel -> clients list hash table */ 94 de = dictFind(server.pubsub_channels,channel); 95 redisAssertWithInfo(c,NULL,de != NULL); 96 clients = dictGetVal(de); 97 ln = listSearchKey(clients,c); 98 redisAssertWithInfo(c,NULL,ln != NULL); 99 listDelNode(clients,ln); 100 if (listLength(clients) == 0) { 101 /* Free the list and associated hash entry at all if this was 102 * the latest client, so that it will be possible to abuse 103 * Redis PUBSUB creating millions of channels. */ 104 dictDelete(server.pubsub_channels,channel); 105 } 106 } 107 /* Notify the client */ 108 if (notify) { 109 addReply(c,shared.mbulkhdr[3]); 110 addReply(c,shared.unsubscribebulk); 111 addReplyBulk(c,channel); 112 addReplyLongLong(c,dictSize(c->pubsub_channels)+ 113 listLength(c->pubsub_patterns)); 114 115 } 116 decrRefCount(channel); /* it is finally safe to release it */ 117 return retval; 118 } 119 120 /* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */ 121 int pubsubSubscribePattern(redisClient *c, robj *pattern) { 122 int retval = 0; 123 124 if (listSearchKey(c->pubsub_patterns,pattern) == NULL) { 125 retval = 1; 126 pubsubPattern *pat; 127 listAddNodeTail(c->pubsub_patterns,pattern); 128 incrRefCount(pattern); 129 pat = zmalloc(sizeof(*pat)); 130 pat->pattern = getDecodedObject(pattern); 131 pat->client = c; 132 listAddNodeTail(server.pubsub_patterns,pat); 133 } 134 /* Notify the client */ 135 addReply(c,shared.mbulkhdr[3]); 136 addReply(c,shared.psubscribebulk); 137 addReplyBulk(c,pattern); 138 addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); 139 return retval; 140 } 141 142 /* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or 143 * 0 if the client was not subscribed to the specified channel. */ 144 int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) { 145 listNode *ln; 146 pubsubPattern pat; 147 int retval = 0; 148 149 incrRefCount(pattern); /* Protect the object. May be the same we remove */ 150 if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) { 151 retval = 1; 152 listDelNode(c->pubsub_patterns,ln); 153 pat.client = c; 154 pat.pattern = pattern; 155 ln = listSearchKey(server.pubsub_patterns,&pat); 156 listDelNode(server.pubsub_patterns,ln); 157 } 158 /* Notify the client */ 159 if (notify) { 160 addReply(c,shared.mbulkhdr[3]); 161 addReply(c,shared.punsubscribebulk); 162 addReplyBulk(c,pattern); 163 addReplyLongLong(c,dictSize(c->pubsub_channels)+ 164 listLength(c->pubsub_patterns)); 165 } 166 decrRefCount(pattern); 167 return retval; 168 } 169 170 /* Unsubscribe from all the channels. Return the number of channels the 171 * client was subscribed from. */ 172 int pubsubUnsubscribeAllChannels(redisClient *c, int notify) { 173 dictIterator *di = dictGetSafeIterator(c->pubsub_channels); 174 dictEntry *de; 175 int count = 0; 176 177 while((de = dictNext(di)) != NULL) { 178 robj *channel = dictGetKey(de); 179 180 count += pubsubUnsubscribeChannel(c,channel,notify); 181 } 182 /* We were subscribed to nothing? Still reply to the client. */ 183 if (notify && count == 0) { 184 addReply(c,shared.mbulkhdr[3]); 185 addReply(c,shared.unsubscribebulk); 186 addReply(c,shared.nullbulk); 187 addReplyLongLong(c,dictSize(c->pubsub_channels)+ 188 listLength(c->pubsub_patterns)); 189 } 190 dictReleaseIterator(di); 191 return count; 192 } 193 194 /* Unsubscribe from all the patterns. Return the number of patterns the 195 * client was subscribed from. */ 196 int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) { 197 listNode *ln; 198 listIter li; 199 int count = 0; 200 201 listRewind(c->pubsub_patterns,&li); 202 while ((ln = listNext(&li)) != NULL) { 203 robj *pattern = ln->value; 204 205 count += pubsubUnsubscribePattern(c,pattern,notify); 206 } 207 if (notify && count == 0) { 208 /* We were subscribed to nothing? Still reply to the client. */ 209 addReply(c,shared.mbulkhdr[3]); 210 addReply(c,shared.punsubscribebulk); 211 addReply(c,shared.nullbulk); 212 addReplyLongLong(c,dictSize(c->pubsub_channels)+ 213 listLength(c->pubsub_patterns)); 214 } 215 return count; 216 } 217 218 /* Publish a message */ 219 int pubsubPublishMessage(robj *channel, robj *message) { 220 int receivers = 0; 221 struct dictEntry *de; 222 listNode *ln; 223 listIter li; 224 225 /* Send to clients listening for that channel */ 226 de = dictFind(server.pubsub_channels,channel); 227 if (de) { 228 list *list = dictGetVal(de); 229 listNode *ln; 230 listIter li; 231 232 listRewind(list,&li); 233 while ((ln = listNext(&li)) != NULL) { 234 redisClient *c = ln->value; 235 236 addReply(c,shared.mbulkhdr[3]); 237 addReply(c,shared.messagebulk); 238 addReplyBulk(c,channel); 239 addReplyBulk(c,message); 240 receivers++; 241 } 242 } 243 /* Send to clients listening to matching channels */ 244 if (listLength(server.pubsub_patterns)) { 245 listRewind(server.pubsub_patterns,&li); 246 channel = getDecodedObject(channel); 247 while ((ln = listNext(&li)) != NULL) { 248 pubsubPattern *pat = ln->value; 249 250 if (stringmatchlen((char*)pat->pattern->ptr, 251 sdslen(pat->pattern->ptr), 252 (char*)channel->ptr, 253 sdslen(channel->ptr),0)) { 254 addReply(pat->client,shared.mbulkhdr[4]); 255 addReply(pat->client,shared.pmessagebulk); 256 addReplyBulk(pat->client,pat->pattern); 257 addReplyBulk(pat->client,channel); 258 addReplyBulk(pat->client,message); 259 receivers++; 260 } 261 } 262 decrRefCount(channel); 263 } 264 return receivers; 265 } 266 267 /*----------------------------------------------------------------------------- 268 * Pubsub commands implementation 269 *----------------------------------------------------------------------------*/ 270 271 void subscribeCommand(redisClient *c) { 272 int j; 273 274 for (j = 1; j < c->argc; j++) 275 pubsubSubscribeChannel(c,c->argv[j]); 276 } 277 278 void unsubscribeCommand(redisClient *c) { 279 if (c->argc == 1) { 280 pubsubUnsubscribeAllChannels(c,1); 281 } else { 282 int j; 283 284 for (j = 1; j < c->argc; j++) 285 pubsubUnsubscribeChannel(c,c->argv[j],1); 286 } 287 } 288 289 void psubscribeCommand(redisClient *c) { 290 int j; 291 292 for (j = 1; j < c->argc; j++) 293 pubsubSubscribePattern(c,c->argv[j]); 294 } 295 296 void punsubscribeCommand(redisClient *c) { 297 if (c->argc == 1) { 298 pubsubUnsubscribeAllPatterns(c,1); 299 } else { 300 int j; 301 302 for (j = 1; j < c->argc; j++) 303 pubsubUnsubscribePattern(c,c->argv[j],1); 304 } 305 } 306 307 void publishCommand(redisClient *c) { 308 int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); 309 if (server.cluster_enabled) clusterPropagatePublish(c->argv[1],c->argv[2]); 310 addReplyLongLong(c,receivers); 311 } 312