11#define _POSIX_C_SOURCE 200809L
22#include "commands.h"
33#include "persistence.h"
4+ #include "replication.h"
45#include <string.h>
56#include <strings.h>
67#include <ctype.h>
910#include <unistd.h>
1011#include <time.h>
1112#include <sys/time.h>
13+ #include <arpa/inet.h>
14+ #include <pthread.h>
15+ #include "crimsoncache.h"
16+
17+ extern void track_command_change (void );
18+ extern volatile sig_atomic_t server_running ;
1219
1320// fallback implementations if not available
1421#ifndef HAVE_STRDUP
@@ -45,6 +52,10 @@ static command_def commands[] = {
4552 {"ttl" , ttl_command , 2 , 2 },
4653 {"save" , save_command , 1 , 1 },
4754 {"bgsave" , bgsave_command , 1 , 1 },
55+ {"replicaof" , replicaof_command , 3 , 3 },
56+ {"role" , role_command , 1 , 1 },
57+ {"incr" , incr_command , 2 , 2 },
58+ {"replconf" , replconf_command , 2 , -1 },
4859 {NULL , NULL , 0 , 0 } // sentinel to mark end of array
4960};
5061
@@ -219,14 +230,23 @@ cmd_result execute_command(int client_sock, char *input, dict *db) {
219230 result = CMD_ERR ;
220231 }
221232
222- // track changes for write commands
223- if (result == CMD_OK && (
224- strcasecmp (argv [0 ], "set" ) == 0 ||
225- strcasecmp (argv [0 ], "del" ) == 0 ||
226- strcasecmp (argv [0 ], "expire" ) == 0 )) {
227- track_command_change ();
233+ // Add this after applying the commands but before free_tokens
234+ // Propagate write commands to replicas
235+ if (result == CMD_OK && server_repl .role == ROLE_PRIMARY && client_sock >= 0 ) {
236+ // Commands that modify data
237+ if (strcasecmp (argv [0 ], "set" ) == 0 ||
238+ strcasecmp (argv [0 ], "del" ) == 0 ||
239+ strcasecmp (argv [0 ], "expire" ) == 0 ||
240+ strcasecmp (argv [0 ], "incr" ) == 0 ) {
241+
242+ // Track for persistence
243+ track_command_change ();
244+
245+ // Propagate to replicas with proper formatting
246+ replication_feed_slaves (input , strlen (input ));
247+ }
228248 }
229-
249+
230250 free_tokens (argv , argc );
231251 return result ;
232252}
@@ -437,4 +457,173 @@ cmd_result bgsave_command(int client_sock, int argc, char **argv, dict *db) {
437457 reply_error (client_sock , "ERR failed to start background save" );
438458 return CMD_ERR ;
439459 }
440- }
460+ }
461+
462+ // replicaof command - configure server as replica of another or as primary
463+ cmd_result replicaof_command (int client_sock , int argc , char * * argv , dict * db ) {
464+ (void )argc ; // unused
465+ (void )db ; // unused
466+
467+ // replicaof no one = become primary
468+ if (strcasecmp (argv [1 ], "NO" ) == 0 && strcasecmp (argv [2 ], "ONE" ) == 0 ) {
469+ replication_unset_primary ();
470+ reply_string (client_sock , "OK" );
471+ return CMD_OK ;
472+ }
473+
474+ // replicaof host port = become replica
475+ const char * host = argv [1 ];
476+ int port = atoi (argv [2 ]);
477+
478+ if (port <= 0 || port > 65535 ) {
479+ reply_error (client_sock , "ERR invalid port" );
480+ return CMD_ERR ;
481+ }
482+
483+ if (replication_set_primary (host , port )) {
484+ reply_string (client_sock , "OK" );
485+ return CMD_OK ;
486+ } else {
487+ reply_error (client_sock , "ERR couldn't connect to primary" );
488+ return CMD_ERR ;
489+ }
490+ }
491+
492+ // role command - return role of server (primary or replica)
493+ cmd_result role_command (int client_sock , int argc , char * * argv , dict * db ) {
494+ (void )argc ; // unused
495+ (void )argv ; // unused
496+ (void )db ; // unused
497+
498+ char response [1024 ];
499+
500+ if (server_repl .role == ROLE_PRIMARY ) {
501+ // format: *3\r\n$6\r\nmaster\r\n:<repl_offset>\r\n*<num_replicas>\r\n...
502+ int written = snprintf (response , sizeof (response ),
503+ "*3\r\n$6\r\nmaster\r\n:%lu\r\n*%d\r\n" ,
504+ server_repl .repl_offset ,
505+ count_replicas ());
506+
507+ // add replica info
508+ pthread_mutex_lock (& server_repl .replicas_mutex );
509+ replica_t * curr = server_repl .replicas ;
510+ while (curr && written < (int )sizeof (response ) - 100 ) {
511+ written += snprintf (response + written , sizeof (response ) - written ,
512+ "*3\r\n$%zu\r\n%s\r\n:%d\r\n:%ld\r\n" ,
513+ strlen (curr -> ip ), curr -> ip , curr -> port ,
514+ time (NULL ) - curr -> last_ack_time );
515+ curr = curr -> next ;
516+ }
517+ pthread_mutex_unlock (& server_repl .replicas_mutex );
518+
519+ } else {
520+ // format: *5\r\n$5\r\nslave\r\n$<host_len>\r\n<host>\r\n:<port>\r\n$<state_len>\r\n<state>\r\n:<offset>\r\n
521+ snprintf (response , sizeof (response ),
522+ "*5\r\n$5\r\nslave\r\n$%zu\r\n%s\r\n:%d\r\n$%zu\r\n%s\r\n:%lu\r\n" ,
523+ strlen (server_repl .primary_host ), server_repl .primary_host ,
524+ server_repl .primary_port ,
525+ strlen (server_repl .state == REPL_STATE_CONNECTED ? "connected" : "connecting" ),
526+ server_repl .state == REPL_STATE_CONNECTED ? "connected" : "connecting" ,
527+ server_repl .repl_offset );
528+ }
529+
530+ write (client_sock , response , strlen (response ));
531+ return CMD_OK ;
532+ }
533+
534+ // INCR command implementation
535+ cmd_result incr_command (int client_sock , int argc , char * * argv , dict * db ) {
536+ (void )argc ; // unused parameter
537+
538+ const char * key = argv [1 ];
539+
540+ // Get current value
541+ cc_obj * obj = dict_get (db , key );
542+ long long value = 0 ;
543+
544+ if (obj ) {
545+ // Key exists, check if it's a string we can convert to number
546+ if (obj -> type != CC_STRING ) {
547+ reply_error (client_sock , "ERR value is not an integer or out of range" );
548+ return CMD_ERR ;
549+ }
550+
551+ // Try to parse as integer
552+ char * endptr ;
553+ value = strtoll ((char * )obj -> ptr , & endptr , 10 );
554+
555+ if (* endptr != '\0' ) {
556+ reply_error (client_sock , "ERR value is not an integer or out of range" );
557+ return CMD_ERR ;
558+ }
559+ }
560+
561+ // Increment the value
562+ value ++ ;
563+
564+ // Convert back to string
565+ char new_val [32 ];
566+ snprintf (new_val , sizeof (new_val ), "%lld" , value );
567+
568+ // Create new object
569+ cc_obj * new_obj = malloc (sizeof (cc_obj ));
570+ if (!new_obj ) {
571+ reply_error (client_sock , "ERR out of memory" );
572+ return CMD_ERR ;
573+ }
574+
575+ new_obj -> ptr = strdup (new_val );
576+ if (!new_obj -> ptr ) {
577+ free (new_obj );
578+ reply_error (client_sock , "ERR out of memory" );
579+ return CMD_ERR ;
580+ }
581+
582+ new_obj -> type = CC_STRING ;
583+ new_obj -> expire = obj ? obj -> expire : 0 ; // Preserve expiry if exists
584+ new_obj -> size = strlen (new_val ) + 1 ;
585+ new_obj -> last_access = current_time_ms ();
586+
587+ if (dict_add (db , key , new_obj )) {
588+ reply_integer (client_sock , value );
589+ return CMD_OK ;
590+ } else {
591+ free (new_obj -> ptr );
592+ free (new_obj );
593+ reply_error (client_sock , "ERR could not set key" );
594+ return CMD_ERR ;
595+ }
596+ }
597+
598+ // implement the REPLCONF command handler
599+ cmd_result replconf_command (int client_sock , int argc , char * * argv , dict * db ) {
600+ (void )db ; // unused
601+
602+ // handle REPLCONF listening-port <port>
603+ if (argc >= 3 && strcasecmp (argv [1 ], "listening-port" ) == 0 ) {
604+ int port = atoi (argv [2 ]);
605+
606+ printf ("Received REPLCONF listening-port %d from replica\n" , port );
607+
608+ // get client IP address
609+ struct sockaddr_in addr ;
610+ socklen_t addr_len = sizeof (addr );
611+ getpeername (client_sock , (struct sockaddr * )& addr , & addr_len );
612+ char ip [INET_ADDRSTRLEN ];
613+ inet_ntop (AF_INET , & addr .sin_addr , ip , INET_ADDRSTRLEN );
614+
615+ // add replica to the list (which will perform initial sync)
616+ add_replica (client_sock , ip , port );
617+
618+ reply_string (client_sock , "OK" );
619+ return CMD_OK ;
620+ }
621+
622+ // handle other REPLCONF commands
623+ reply_string (client_sock , "OK" );
624+ return CMD_OK ;
625+ }
626+
627+
628+
629+
0 commit comments