By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
429,101 Members | 1,335 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 429,101 IT Pros & Developers. It's quick & easy.

Finding out Segmentation Fault Problem

P: 11
Dear Bytes community,

I would like to get your opinion on how to identify a segmentation fault problem in my code which is written in C language. I would share my code for your valuable comments,

Kindly regards.

Expand|Select|Wrap|Line Numbers
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <stddef.h>
  5. #include <signal.h>
  6. #include <assert.h>
  7. #include <hiredis/hiredis.h>
  8. #include <hiredis/async.h>
  9. #include <hiredis/adapters/libevent.h>
  10. #include <event.h>
  11. #include <unistd.h>
  12. #include <time.h>
  13. #include <stdbool.h>
  14. #define PACKETSIZE sizeof(cloudRANMessage)
  15. #define COMMANDSIZE 256
  16.  
  17. typedef struct cloudRANMessage
  18. {
  19.     unsigned int      station_id;
  20.     unsigned int      location_area;
  21.     unsigned int      counterRedis;
  22.     char              clientHostName[1024];
  23.     char              command[COMMANDSIZE];
  24.  
  25. }cloudRANMessage;
  26.  
  27. char *accessKey;
  28. char *accessHash;
  29.  
  30. void callbackDeserialize();
  31. void serialize();
  32.  
  33. void printMyMessage(cloudRANMessage *message)
  34. {
  35.     printf("%d\n", message->location_area);
  36.     printf("%d\n", message->station_id);
  37.     printf("%s\n", message->command);
  38.     printf("%s\n", message->counterRedis);
  39.     printf("%s\n", message->clientHostName);
  40. }
  41.  
  42. void serialize(cloudRANMessage *message, char *data)
  43. {
  44.     assert(data != NULL);
  45.     memcpy(data, message, sizeof *message);
  46. }
  47.  
  48.  
  49. void deserialize(char *data, cloudRANMessage *tempMessage)
  50. {
  51.     memset(tempMessage, 0, sizeof(cloudRANMessage));
  52.     memcpy(tempMessage, data, sizeof(cloudRANMessage));
  53.     printMyMessage(tempMessage);
  54. }
  55.  
  56. void deserializeLocal(char *data, cloudRANMessage *tempMessageLocal)
  57. {
  58.     memset(tempMessageLocal, 0, sizeof(cloudRANMessage));
  59.     memcpy(tempMessageLocal, data, sizeof(cloudRANMessage));
  60.     printMyMessage(tempMessageLocal);
  61. }
  62.  
  63. void getCallback(redisAsyncContext *c, void *r, void *privdata)
  64. {
  65.     redisReply *reply = r;
  66.     printf("%s\n", reply->str);                   // Call deserializaton function for the data retrieval.;
  67.     /* Disconnect after receiving the reply to GET */
  68.     redisAsyncDisconnect(c);
  69. }
  70.  
  71. void replyParsing(void *reply)  // Parsing will be used to handle subscripton message reply to get the hash-key pair of the data that is written.
  72. {
  73.     redisReply *parsing = reply;
  74.     printf("parsing array %s", parsing->element[2]->str);
  75.     char *parsingArray = parsing->element[2]->str;
  76.     char *p;
  77.     p = strtok(parsingArray,"[ ""].");
  78.     int i= 0;
  79.     while(p !=NULL)
  80.     {
  81.         p = strtok(NULL,"[ ""].");
  82.         if(i == 7)
  83.         {
  84.             accessHash = p;
  85.             printf("%s\n",p);
  86.         }
  87.         else if(i == 8)
  88.         {
  89.             accessKey = p;
  90.             printf("%s\n",p);
  91.         }
  92.     i++;
  93.     }
  94.      // send pointer here !
  95.     // GET command here with the appropiate keys
  96. }
  97. // for the key hash value in the char. [8&9]
  98.  
  99. void listenChannel(redisAsyncContext *c, void *reply, void *privdata)
  100. {
  101.     struct event_base *base = (struct event_base*)privdata;
  102.     char isExists = malloc(sizeof(isExists));
  103.     isExists = "eNB";
  104.     bool executeParsing = false;
  105.     redisReply *r = reply;
  106.     if (reply == NULL)
  107.     return;
  108.  
  109.     printf("Client successfully subscribed to channel !\n");
  110.     if(r->type == REDIS_REPLY_ARRAY){
  111.  
  112.         for(int j =0; j<r->elements;j++)
  113.         {
  114.             printf("Printing Redis Reply: %u) %s\n",j,r->element[j]->str);
  115.             if (strstr(r->element[j]->str,isExists) != NULL)
  116.                 executeParsing = true;
  117.             else
  118.                 executeParsing = false;
  119.         }
  120.  
  121.     }
  122.         if (executeParsing){
  123.         replyParsing(r);
  124.         printf("test received key !\n");
  125.         event_base_loopexit(base,NULL);
  126.     }
  127.         else
  128.         event_base_loopcontinue(base);
  129. }
  130.  
  131. void callbackDeserialize(redisAsyncContext *c, void *r, cloudRANMessage *tempMessage) {
  132.     redisReply *reply = r;
  133.     if (reply == NULL) return;
  134.     printf("%s\n", reply->str);                   // Call deserializaton function for the data retrieval.
  135.     char *stringReply = reply->str;
  136.     deserialize(stringReply, tempMessage);
  137.     /* Disconnect after receiving the reply to GET */
  138.     //
  139. }
  140.  
  141. void callbackDeserializeLocal(redisAsyncContext *c, void *r, cloudRANMessage *tempMessageLocal) {
  142.     redisReply *reply = r;
  143.     if (reply == NULL) return;
  144.     printf("%s\n", reply->str);                   // Call deserializaton function for the data retrieval.
  145.     char *stringReply = reply->str;
  146.     deserializeLocal(stringReply, tempMessageLocal);
  147.     /* Disconnect after receiving the reply to GET */
  148.     //
  149. }
  150.  
  151.  
  152. void connectCallback(const redisAsyncContext *c, int status) {
  153.     if (status != REDIS_OK) {
  154.         printf("Error: %s\n", c->errstr);
  155.         return;
  156.     }
  157.     printf("Connected...\n");
  158. }
  159.  
  160. void disconnectCallback(const redisAsyncContext *c, int status) {
  161.     if (status != REDIS_OK) {
  162.         printf("Error: %s\n", c->errstr);
  163.         return;
  164.     }
  165.     printf("Disconnected...\n");
  166.  
  167. }
  168.  
  169. void connectCallback2(const redisAsyncContext *c2, int status) {
  170.     if (status != REDIS_OK) {
  171.         printf("Error: %s\n", c2->errstr);
  172.         return;
  173.     }
  174.     printf("Connected...\n");
  175. }
  176.  
  177. void disconnectCallback2(const redisAsyncContext *c2, int status) {
  178.     if (status != REDIS_OK) {
  179.         printf("Error: %s\n", c2->errstr);
  180.         return;
  181.     }
  182.     printf("Disconnected...\n");
  183.  
  184. }
  185. void connectCallback3(const redisAsyncContext *c3, int status) {
  186.     if (status != REDIS_OK) {
  187.         printf("Error: %s\n", c3->errstr);
  188.         return;
  189.     }
  190.     printf("Connected...\n");
  191. }
  192.  
  193. void disconnectCallback3(const redisAsyncContext *c3, int status) {
  194.     if (status != REDIS_OK) {
  195.         printf("Error: %s\n", c3->errstr);
  196.         return;
  197.     }
  198.     printf("Disconnected...\n");
  199.  
  200. }
  201.  
  202. void exitCallback(redisAsyncContext *c, void *reply, void *privdata){
  203.  
  204.     struct event_base *base = (struct event_base*)privdata;
  205.     event_base_loopexit(base,NULL);
  206. }
  207.  
  208. int main (int argc, char **argv) {
  209.  
  210.     cloudRANMessage *cloudRANptr = malloc(sizeof(cloudRANMessage));
  211.     cloudRANMessage *receivedMsg = malloc(sizeof(cloudRANMessage));
  212.     cloudRANMessage *initialMessage = malloc(sizeof(cloudRANMessage));
  213.     void *data = calloc(1,sizeof(cloudRANMessage));
  214.  
  215.     accessKey = malloc(sizeof(char));
  216.     accessHash = malloc(sizeof(char));
  217.  
  218.     struct timeval start;
  219.  
  220.     initialMessage->location_area = 7214;
  221.     initialMessage->station_id = 45632;
  222.     initialMessage->counterRedis = 0;
  223.     gethostname(initialMessage->clientHostName,1023);
  224.     strcpy(initialMessage->command, "HANDOVER\0");
  225.     gethostname(initialMessage->clientHostName,1023); // Gets the hostname of the compiled computer and puts in struct.
  226.     printf("%s\n",initialMessage->clientHostName);
  227.  
  228.         signal(SIGPIPE, SIG_IGN);
  229.         struct event_base *base = event_base_new();
  230.         struct event_base *base3 = event_base_new();
  231.         struct event_base *base2 = event_base_new();
  232.         struct event_base *base4 = event_base_new();
  233.  
  234.         redisAsyncContext *localCon = redisAsyncConnect("localhost", 6379); // Connection will be used for get operation
  235.         if (localCon->err) {
  236.         printf("Error on localhost connection: %s\n", localCon->errstr);
  237.         return 1;
  238.         }
  239.  
  240.         redisAsyncContext *clientCon = redisAsyncConnect("192.168.1.103", 6379); // Connection will be used to write data on master
  241.         if (clientCon->err) {
  242.         printf("Error on master connection: %s\n", clientCon->errstr);
  243.         return 1;
  244.         }
  245.  
  246.         redisAsyncContext *subCon = redisAsyncConnect("localhost", 6379); // Connection will be used for SUBSCRIBE & UNSUBSCRIBE command
  247.         if (subCon->err) {
  248.         printf("Error on subscribe connection: %s\n", subCon->errstr);
  249.         return 1;
  250.         }
  251.         unsigned int counter = malloc(sizeof(unsigned int));
  252.         counter = 0;
  253.  
  254.         redisLibeventAttach(subCon, base2);
  255.  
  256.         redisAsyncSetConnectCallback(subCon,connectCallback3);
  257.         redisAsyncSetDisconnectCallback(subCon,disconnectCallback3);
  258.         counter++;
  259.         redisAsyncCommand(subCon,listenChannel,base2,"SUBSCRIBE cloudRAN");
  260.         WAITSUBSCRIBE:
  261.         event_base_dispatch(base2);
  262.         //initialize data on master !
  263.         serialize(initialMessage,data);
  264.         redisLibeventAttach(clientCon,base4);
  265.         // Should run once to set the data !
  266.         redisAsyncCommand(clientCon,exitCallback,base4,"HSET TA_1 eNB_1 %b",data,sizeof(cloudRANMessage));
  267.  
  268.         event_base_dispatch(base4);
  269.  
  270.         if(counter != 1){
  271.         redisLibeventAttach(localCon, base);
  272.         redisAsyncSetConnectCallback(localCon,connectCallback);
  273.         redisAsyncSetDisconnectCallback(localCon,disconnectCallback);
  274.         // Make an initial SET operation to the MASTER !
  275.         // Access the data locally
  276.  
  277.         redisAsyncCommand(localCon,callbackDeserialize,receivedMsg,"HGET %s %s",accessHash,accessKey);
  278.         printf("Execution in UNIX time for HGET:%ld\n", (start.tv_sec * 1000000 + start.tv_usec));
  279.         event_base_loop(base,EVLOOP_ONCE);
  280.         printf("Hash and Key value %s %s",accessHash,accessKey);
  281.  
  282.          if(initialMessage->clientHostName != receivedMsg->clientHostName){
  283.  
  284.             redisLibeventAttach(clientCon, base3);
  285.  
  286.             redisAsyncSetConnectCallback(clientCon,connectCallback2);
  287.             redisAsyncSetDisconnectCallback(clientCon,disconnectCallback2);
  288.  
  289.             cloudRANptr = receivedMsg;   // Now access and change the data after verification
  290.             cloudRANptr->counterRedis++;
  291.             cloudRANptr->location_area= 56789; // Assign arbitrary location area value on this client
  292.             serialize(cloudRANptr,data); // try with a different data pointer as well !!
  293.             gettimeofday(&start, NULL);
  294.             printf("Execution in UNIX time for HSET:%ld\n", (start.tv_sec * 1000000 + start.tv_usec));
  295.             redisAsyncCommand(clientCon,exitCallback,base3,"HSET %s %s %b",accessHash,accessKey,data,sizeof(cloudRANMessage));
  296.  
  297.             event_base_dispatch(base3);
  298.             goto WAITSUBSCRIBE;
  299.         }
  300.     }
  301.         else
  302.         goto WAITSUBSCRIBE;
  303.     //redisAsyncCommand(c,NULL, NULL, "PUBLISH cloudRAN %b", data, sizeof(cloudRANMessage));
  304.     return 0;
  305. }
  306.  
  307.  
  308.  
Feb 10 '16 #1
Share this Question
Share on Google+
16 Replies


weaknessforcats
Expert Mod 5K+
P: 9,197
The only way to find these kinds of problems is to use your debugger and step through the code verifying every step of the way that your variable and memory contents are what you expect.

I do notice use of memcpy which I never advise. The mem functions are the quickest way to corrupt memory that I can think of.

I also notice that your memcpy copies from a char* 1292 bytes to a address pointing to a cloudRANMessage. 1) does the char* point to 1292 bytes and not one byte less, 2) does the target cloudRANMessage pointer point to 1292 bytes, 3) is the memory pointed at by the char* actually pointing at a cloudRANMessage? I would expect compiler warnings here.
Feb 10 '16 #2

Expert 100+
P: 2,398
Line 38 uses "%s" to print message->counterRedis. However, counterRedis is an unsigned int not a pointer.

What else could go wrong?

Function printMyMessage does not check if message is NULL before dereferencing it.
Function printMyMessage prints the message->command string without knowing if the string is properly terminated.
Function printMyMessage prints the message->clientHostName string without knowing if the string is properly terminated.

Function serialize does not check if message is NULL before dereferencing it.
Function serialize does know if *data is smaller than *message. Why not declare data as pointing to cloudRANMessage too? If both are the same type then you can simply use assignment statement instead of memcpy.

Function deserializeLocal does not check if data and tempMessage are NULL before dereferencing them.
Function deserialize does not know if data is big enough to pull that many bytes out of.

Function getCallback does not check if r is NULL before dereferencing it.
Function getCallback prints reply->str without knowing if the string is properly terminated.

And so on. Some of the more common causes of segmentation faults are dereferencing a NULL pointer, reading or writing past the end of a variable or buffer, and accessing memory after it has been freed.
Feb 10 '16 #3

P: 11
I really appreciate your answers that helped to understand a lot. Finally i realized to find out where the program gives memory dump it is here:

Expand|Select|Wrap|Line Numbers
  1.  if (reply == NULL)
  2.     return;
  3.  
  4.     printf("Client successfully subscribed to channel !\n");
  5.     if(r->type == REDIS_REPLY_ARRAY){
  6.  
  7.         for(int j =0; j<r->elements;j++)
  8.         {
  9.             printf("Printing Redis Reply: %u) %s\n",j,r->element[j]->str);
  10.             if (strstr(r->element[j]->str,isExists) != NULL)
  11.                 executeParsing = true;
  12.             else
  13.                 executeParsing = false;
  14.         }
  15.  
  16.     }
strstr causes a memory dump which i didn't understand the reason. Because when i comment out that part i can successfully receive the message response from the server which is being handled by printf line in the loop.

Thanks.
Feb 11 '16 #4

weaknessforcats
Expert Mod 5K+
P: 9,197
Maybe you could post additional code explaining the variables used by the strstr.
Feb 11 '16 #5

P: 11
isExists is simply "eNB" string whereas the pointer with elements array is described as like this in this API. When we get a response with the type of REDIS_REPLY_ARRAY, we use that expression. I would also put the API's website just in case.

https://github.com/redis/hiredis

REDIS_REPLY_ARRAY:

A multi bulk reply. The number of elements in the multi bulk reply is stored in reply->elements. Every element in the multi bulk reply is a redisReply object as well and can be accessed via reply->element[..index..]. Redis may reply with nested arrays but this is fully supported.

Many thanks.
Feb 11 '16 #6

weaknessforcats
Expert Mod 5K+
P: 9,197
So you are saying that each argument of strstr is a const char* to a null terminated string?

If that's the case strstr won't crash.

But it is crashing so I think there is something wrong with those arguments OR the strstr is not the one in the C library.
Feb 11 '16 #7

P: 11
Actually when i'm going to dig into the API library i found out that this structure holds a struct for the reply that is coming for the answer.
So i guess arguments of the each element is not a const char according to this structure below. You think being not a const as a problem when it is being used by strstr ?


typedef struct redisReply {
int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
int len; /* Length of string */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
}redisReply;
Feb 11 '16 #8

Expert 100+
P: 2,398
Starting from line 102...
Expand|Select|Wrap|Line Numbers
  1. char isExists = malloc(sizeof(isExists));
  2. isExists = "eNB";
  3. ...
  4. if (strstr(r->element[j]->str,isExists) != NULL)
isExists is a char, not the char* that strstr expects. You should have gotten a compiler error.
What compiler are you using?
Are you setting a command line option that suppresses function prototype checking?
Feb 12 '16 #9

weaknessforcats
Expert Mod 5K+
P: 9,197
This code crashes:

Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     redisReply var;
  4.     redisReply* r = &var;
  5.  
  6.     strstr(r->element[0]->str, "hello");
  7.  
  8. }
From what I see both arguments to strstr are char* values. The crash is because the elements pointer is invalid and the str pointer is invalid.

Compiles and links fine but since these variables are screwed up the strstr never gets a chance to work because the crash occurs trying to access the string pointed at by
r->element[0]->str.

Now I think your redisReply variables are not correct. For example is elements correct for the number of element in the array? Where is this checked? It would have to be checked every time an element was added to or removed from the array.

Finally, the const char* arguments to strstr to not require your char* to be const. The const char* argument means that strstr will treat the argument as const (won't try to change it). Therefore, if your char* used to call strstr is invalid it's not strstr that did it.
Feb 12 '16 #10

P: 11
I'm using GCC compiler on ubuntu with an option of std=C99.

In fact it is weird that when the API gets value (where r is a pointer of message reply) it prints out the response without any problem with this loop whenever i want to use another function like strstr it crashes.

void listenChannel(redisAsyncContext *c, void *reply,void *privdata)
{
redisReply *r = reply;
if (reply == NULL) return;

if(r->type == REDIS_REPLY_ARRAY)
{
for(int j =0; j<r->elements;j++)
{
printf("%u) %s\n",j,r->element[j]->str);
}
}
}
Feb 12 '16 #11

Expert 100+
P: 2,398
Please confirm lines 102, 103, and 115 appear in your source code exactly as they are shown in your original post, namely
Expand|Select|Wrap|Line Numbers
  1.     char isExists = malloc(sizeof(isExists));
  2.     isExists = "eNB";
  3. ...
  4.             if (strstr(r->element[j]->str,isExists) != NULL)
Especially, that line 102 is not
Expand|Select|Wrap|Line Numbers
  1.     char *isExists = malloc(sizeof(isExists));
If the original post is correct, then please examine your compiler output to see if there are any errors or warnings for lines 103 or 115 -- because your compiler ought to be complaining.
Feb 12 '16 #12

weaknessforcats
Expert Mod 5K+
P: 9,197
@akadayifci post #11:

The printf is being told to display an unsigned integer (%u) rather than a string (%s).

You might try changing the printf to %s and see what happens. The %s will display characters until the first null. The %u will just display the bytes occupied by an unsigned integer.

This function has void* arguments so it's just the function who says the address is a redisReply address.

Let me know what you find out.
Feb 12 '16 #13

P: 11
I've changed malloc(sizeof(isExists)) to malloc(sizeof(char));
There is no error for these lines actually.

When i run the code without comments (İ'm trying this string search function now it runs one time and gives a segfault)

if(r->type == REDIS_REPLY_ARRAY){
memcpy(buffer,r,sizeof(redisReply));
//printf("%d",r->elements);
for(int j =0; j<r->elements;j++)
{
//buffer = r->element[j]->str;
printf("Printing Redis Reply: %u) %s\n",j,r->element[j]->str);
//pch = (char*) memchr(r->element[j]->str,isExists,strlen(r->element[j]->str));
//if (pch != NULL)
//executeParsing = true;
//else{
//executeParsing = false;
//printf("testtest");
//}
}
}
Normally, if i remove the string search condition it will wait for the future responses in the event loop but this somehow causes a segfault after the for loop completition.
Feb 12 '16 #14

weaknessforcats
Expert Mod 5K+
P: 9,197
I'm not sure what you are trying to do. redisReply has got pointers for members. After a memcpy the buffer and the redisReply variable now each point to the same arrays. Whichever of these variables changes one of those pointers screws up the other one.

There's a warning in your loop because you compare signed and unsigned integers. size_t is unsigned.

Where is pch defined?

This code:
Expand|Select|Wrap|Line Numbers
  1. pch = (char*) memchr(r->element[j]->str,isExists,strlen(r->element[j]->str));
says to look for an integer in memory starting at isExists that is equal to the integer returned by strlen?

Isn't isExists a char*? How is it pointing to an integer?

This code:

Expand|Select|Wrap|Line Numbers
  1. buffer = r->element[j]->str;
trashes buffer by putting the address of r->element[j]->str at the beginning.

As you can see I'm having considerable difficulty in understanding this logic.
Feb 12 '16 #15

P: 11
Maybe i wrote it in the search function in a wrong manner, i simply want to search for the strings that are in the r->element[j]->str by looking to isExists string. I've defined the pch as char *pch in the same function. Since r->element[j] contains the responses that i want to look for i want to make a comparison with every element.

Could you have any suggestion to to that because it is my first time to use these string manipulation functions in C, I used to work on C++. I guess I've tried several things at once that's what makes you confused to understand the code, I'm sorry because of that.

Thanks for your help again.
Feb 13 '16 #16

weaknessforcats
Expert Mod 5K+
P: 9,197
Stop using those mem... functions.

This code does not crash:

Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     redisReply var[10];
  4.     redisReply* r = var;
  5.  
  6.     char* result = 0;
  7.  
  8.     r->element = (redisReply**)malloc(sizeof(redisReply*));
  9.  
  10.     r->element[0] = (redisReply*)malloc(sizeof(redisReply));
  11.  
  12.     r->element[0]->str = malloc(100);
  13.  
  14.     strcpy(r->element[0]->str, "123hello456");
  15.  
  16.     result = strstr(r->element[0]->str, "hello");
  17.  
  18.     printf("result %p\n", result);
  19.  
  20.     free(r->element[0]->str);
  21.     free(r->element[0]);
  22.     free(r->element);
  23.  
  24. }
1) First I create an array of redisReply variables
2) Next, I assign the address of the array to an redisReply pointer. This is to simulate your code. r points to var[0].
3) Next I will I need to allocate memory for an redisReply* that the redisReply** in r points to.
4) Next I will need to allocate memory for an redisReply* that r->element points to. This allocation is for r->element[0], which is all I will use in this demo code.
5) Next I will allocate memory for the string that is a member of r->element[0]. That string is r->element[0]->str.
6) Next I copy some contents to r->element[0]->str.
7) Next I search those contents for hello and capture the result. strstr returns a char* to the location where the h of hello is located when hello is found. If hello is not found, strstr returns a 0.
8) Next I display that address.
9) Next I release memory in the EXACT REVERSE ORDER of how it was allocated.

If any of these memory allocations are not done, you crash.
If any of these memory allocations are overrun or you have a pointer pointing to an address outside these allocatons, you crash.
If you copy contents to the str string that is larger than strlen(contents +1), you crash. That 1 is for the null termination which strlen does not count.
If you fail to free the allocations in the exact reverse order of the allocations, you leak.

I am convinced that there is a memory corruption problem surrounding your redisReply variables which is causing strstr to crash. I also believe this is why the printf crashes in the listenChannel function.

Let me know what you think. I can offer a solution but only if you agree the problem is not strstr but is instead a memory management issue in the code itself.
Feb 13 '16 #17

Post your reply

Sign in to post your reply or Sign up for a free account.