How to enqueue this char[] array in C? - c

I have to use a buffer of size max_cache_req_len to read in the value received from mq_receive. Here is my code that is receiving a value from shared memory and then placing it on a queue:
size_t received_bytes = 0;
char buffer[MAX_CACHE_REQUEST_LEN];
received_bytes = 0;
memset(&buffer, 0, MAX_CACHE_REQUEST_LEN);
received_bytes = mq_receive(mq, buffer, MAX_CACHE_REQUEST_LEN, NULL);
if (received_bytes != -1)
{
item_type *item = malloc(sizeof(item_type));
item->path = buffer;
pthread_mutex_lock(&queue_lock);
steque_enqueue(&queue, item);
pthread_cond_signal(&queue_cond);
pthread_mutex_unlock(&queue_lock);
}
Here is my code that is taking the item off the queue, and placing it into a char* value. When I print the path, I get "".
void *worker(void *arg)
{
item_type *new_item;
char *path;
int fd;
while (1)
{
pthread_mutex_lock(&queue_lock);
while (steque_isempty(&queue) == 1)
pthread_cond_wait(&queue_cond, &queue_lock);
new_item = steque_pop(&queue);
path = new_item->path;
free(new_item);
new_item = NULL;
pthread_mutex_unlock(&queue_lock);
fd = simplecache_get(path);
sleep(cache_delay);
printf("%d\n", fd);
printf("%s\n", path);
// MAKE WORKER THREAD TAKE
if (fd == CACHE_FAILURE)
{
}
else
{
}
}
}
If I hardcode something like:
item->path = "buffer";
Then it prints buffer from within my worker function. This is a multithreaded application, I am just unsure what to do with my char[size] array to transform it into a char* and allow it to transfer.
Nutshell:
(char*)&char[size] queued -> queue turns it into a void* -> popped off queue, turned into a char* and value is lost

Related

Producer Consumer stuck in deadlock with consumer

I'm trying to implement the producer consumer with conditional variables so I can learn about synchronization. I'm using the github to guide me and solved some seg faults but now it seems that my consumer never gets executed or is stuck in deadlock. I'm not sure what could be the error. I have included printf statements in the producer to execute everytime it runs and it completes producing any string in messages.txt less than 5. The consumer though does not and is stuck in a deadlock.
#define max 5
int par = 0;
// Data structure for queue
struct queue
{
char *items; // array to store queue elements
int maxsize; // maximum capacity of the queue
int front; // front points to front element in the queue (if any)
int rear; // rear points to last element in the queue
int size; // current capacity of the queue
pthread_mutex_t mutex; // needed to add/remove data from the buffer
pthread_cond_t can_produce; // signaled when items are removed
pthread_cond_t can_consume; // signaled when items are added
};
// Utility function to initialize queue
struct queue* newQueue(int size)
{
struct queue *pt = NULL;
pt = (struct queue*)malloc(sizeof(struct queue));
pt->items = (char*)malloc(size * sizeof(char));
pt->maxsize = size;
pt->front = 0;
pt->rear = -1;
pt->size = 0;
pthread_mutex_init(&pt->mutex, NULL);
pthread_cond_init(&pt->can_produce, NULL);
pthread_cond_init(&pt->can_consume, NULL);
return pt;
}
// Utility function to return the size of the queue
int size(struct queue *pt)
{
return pt->size;
}
// Utility function to check if the queue is empty or not
int isEmpty(struct queue *pt)
{
return !size(pt);
}
// Utility function to return front element in queue
char front(struct queue *pt)
{
if (isEmpty(pt))
{
//printf("UnderFlow\nProgram Terminated\n");
}
return pt->items[pt->front];
}
// Utility function to add an element x in the queue
void enqueue(struct queue *pt, char x)
{
if (size(pt) == pt->maxsize)
{
//printf("OverFlow\nProgram Terminated\n");
}
//printf("Inserting %c\t", x);
pt->rear = (pt->rear + 1) % pt->maxsize; // circular queue
pt->items[pt->rear] = x;
pt->size++;
//printf("front = %c, rear = %c\n", pt->front, pt->rear);
}
// Utility function to remove element from the queue
void dequeue(struct queue *pt)
{
if (isEmpty(pt)) // front == rear
{
//printf("UnderFlow\nProgram Terminated\n");
}
//printf("Removing %c\t", front(pt));
pt->front = (pt->front + 1) % pt->maxsize; // circular queue
pt->size--;
//printf("front = %d, rear = %c\n", pt->front, pt->rear);
}
void consumer_f(void *arg)
{
struct queue *pt = (struct queue*)arg;
while (par==0 && !isEmpty(pt))
{
pthread_mutex_lock(&pt->mutex);
if (pt->size == 0)
{ // empty
// wait for new items to be appended to the buffer
pthread_cond_wait(&pt->can_consume, &pt->mutex);
}
printf("%c", pt->front);
dequeue(pt);
pthread_cond_signal(&pt->can_produce);
pthread_mutex_unlock(&pt->mutex);
}
}
void producer_f(void *arg)
{
struct queue *pt = (struct queue*)arg;
char tmp;
FILE *fp;
fp = fopen("messages.txt", "r");
if (fp == NULL)
{
//fprintf(stderr, "error opening messages.txt");
return -1;
}
while ((tmp = fgetc(fp)) != EOF)
{
pthread_mutex_lock(&pt->mutex);
if (pt->size == max)
pthread_cond_wait(&pt->can_produce, &pt->mutex);
enqueue(pt, tmp);
printf("sent");
pthread_cond_signal(&pt->can_consume);
pthread_mutex_unlock(&pt->mutex);
}
par = 1; //denotes EOF for consumer */
}
int main()
{
printf("nop");
struct queue *pt = newQueue(5);
pthread_t producer;
pthread_t consumer;
printf("got here");
if (pthread_create(&producer, NULL, &producer_f, pt))
{
//fprintf(stderr, "Error creating producer thread\n");
return -1;
}
if (pthread_create(&consumer, NULL, &consumer_f, pt))
{
//fprintf(stderr, "Error creating consumer thread\n");
return -1;
}
if (pthread_join(producer_f, NULL))
{
//fprintf(stderr, "Error joining proucer thread\n");
return -1;
}
if (pthread_join(consumer_f, NULL))
{
//fprintf(stderr, "Error joinging consumer thread\n");
return -1;
}
return 0;
}
The consumer thread is not going to deadlock state but it is exiting without consuming as EOS is reached before consumer started consuming.
As you know, threads can get scheduled in a random way by OS (I mean, at least you can assume that they get scheduled in a random way). With this assumption, the producer might have started and read all bytes and enabled eos flag i.e. par=1. If consumer thread starts after par=1, it is not consuming at all.
To handle this case, you need to update the consumer_f() function.
while (1) //Run the loop always
{
pthread_mutex_lock(&pt->mutex);
if ((pt->size == 0) && (par == 0)) //Make sure that EOS is not already reached.
{ // empty
// wait for new items to be appended to the buffer
pthread_cond_wait(&pt->can_consume, &pt->mutex);
}
if(par && isEmpty(pt))
{
//If Array is empty and eos is reached, unlock and exit loop
pthread_mutex_lock(&pt->mutex);
break;
}
//Other code
}
Similarly, you need to enable eos flag inside mutex in producer_f().
while ((tmp = fgetc(fp)) != EOF)
{
//Your code
}
pthread_mutex_lock(&pt->mutex);
par = 1;
pthread_cond_signal(&pt->can_consume); //To make sure that consumer thread wakesup
pthread_mutex_unlock(&pt->mutex);
PS: pt->size == 0 can be replaced with isEmpty(pt) for more readability.

Char Linked list with Buffer Ring in C

I have a server and a client. The client can send request like ADD to list by entering ADD:Joe:2 it should add to list. Key = Joe Value =2.
I'm trying to ADD a key:value into a linked list that uses a buffer ring of max 5 elements. When the linked list reaches the max size of elements(5) and a new key:value is entered, the oldest element is overwritten with the new element.
Every time I adding it seems to add fine, but when the max size is reached and tries to delete the first KEY:VALUE it crashes.
Add to LIST:
void push_item(struct item** Front,struct item** Rear,char *new_key,char *new_value,int newsockfd)
{
char buffer [50] = {0};
struct item* new_node = malloc(sizeof(struct item));
strcpy(new_node->value,new_value);
strcpy(new_node->key,new_key);
//new_node->next = NULL;
if(p_size==5)
{
char *itemVal;
char *itemKey;
/* if queue is empty */
if ( *Front == NULL )
{
printf ( "List is empty");
}
else
{
if (*Front == *Rear)
{
strcpy(itemVal,(*Front)->value);
strcpy(itemKey,(*Front)->key);
free(*Front);
*Front = NULL ;
*Rear = NULL ;
}
else
{
//delete node
new_node = *Front;
strcpy(itemVal,new_node->value);
strcpy(itemKey,new_node->key);
*Front=(*Front)->next;
(*Rear)->next=*Front;
free(new_node);
}
printf("Node deleted Key: %s Value: %s",itemKey,itemVal);
}
}
else
{
*Rear=new_node;
(*Rear)->next=new_node;
if(*Front==NULL)
{
(*Front)=new_node;
p_size++;
}
else
{
(*Rear)->next=new_node;
p_size++;
printf("Elements in List: %d\n",p_size);
}
}
sprintf(buffer,"Added");
int num_bytes = write(newsockfd, buffer, strlen(buffer));
if (num_bytes < 0)
{
fprintf(stderr, "Thread ERROR: write() failed\n");
}
}
Server:
struct item *Front = NULL;
struct item *Rear = NULL;
char buffer[BUFFER_SIZE] = {0};
void* handle_client(void *socket)
{
int newsockfd = (int)socket;
pthread_t thread_id = pthread_self();
printf("----------\nThread %lu using socket %x\n", (unsigned long)thread_id, newsockfd);
/* Start communicating */
int num_bytes = read(newsockfd, buffer, BUFFER_SIZE-1);
if (num_bytes < 0) {
fprintf(stderr, "Thread %lu ERROR: read() failed\n", (unsigned long)thread_id);
return NULL;
}
printf("Thread %lu recieved request\n", (unsigned long)thread_id);
if(strncmp(buffer,"ADD",3)==0)
{
char* key = buffer+4;
int charIndex;
char *new_value = strchr(buffer+4 , ':')+1;
charIndex = (int)(new_value-buffer)-1;
buffer[charIndex] = '\0';
push_item(&Front,&Rear,key,new_value,newsockfd);
printf("Value:%s Key:%s\n",new_value,key);
}
EDIT:
Images of CMD :
Left cmd is the client and the right one is the server
The p_size represents the current number of elements in the linked list.
It Crashes after it enters the if condition P_size == 5.
When looking on the push_item() function for the problem located in case of if(p_size==5), the error is when writing data of the node to be removed.
Error 1 - temporary itemVal and itemKey are declared as char * but never allocated to an array.
// To do
strcpy(itemVal,(*Front)->value);
strcpy(itemKey,(*Front)->key);
// or
strcpy(itemVal,new_node->value);
strcpy(itemKey,new_node->key);
Both char *itemVal; and char *itemKey; shall be allocated.
// Local variables (STR_SIZE has to be adjusted)
char itemVal[STR_SIZE+1];
char itemKey[STR_SIZE+1];
Warning 1 - but solving that issue will reveal a malfunction in the Ring Buffer algorithm when using linked-list.
In the push_item() function, the writing/push pointer *Front and
the reading/pop pointer *Rear are managed independently. When the
Ring Buffer is full (if(p_size==5)) the new item is deleted instead
of the oldest one.

Unknown segmentation fault likely related to memory allocation

After searching through many threads on similar issues, I've been unable to determine why I've been getting a seg fault with my program. I have two files: buffer.c where I create a circular buffer and deposit/remove values from it and a main file where several threads call the operations on the circular buffer with user input. Semaphores are used to prevent concurrent access.
Here are the relevant parts of my main program:
int main (int argc, char const *argv[]) {
st_init();
Buffer *bufferA,*bufferB,*bufferC;
createBuffer(bufferA,128);
createBuffer(bufferB,128);
createBuffer(bufferC,128);
// Create the struct used to initialize threads.
ThreadInit initA = {
bufferA,
bufferA
};
ThreadInit initB = {
bufferA,
bufferB
};
ThreadInit initC = {
bufferB,
bufferC
};
ThreadInit initD = {
bufferC,
bufferC
};
// Create threads
if (st_thread_create(getInputStream, &initA, 0, 0) == NULL) {
perror("Thread a creation failure.");
exit(EXIT_FAILURE);
}
if (st_thread_create(convertCR, &initB, 0, 0) == NULL) {
perror("Thread b creation failure.");
exit(EXIT_FAILURE);
}
if (st_thread_create(squashChar, &initC, 0, 0) == NULL) {
perror("Thread c creation failure.");
exit(EXIT_FAILURE);
}
if (st_thread_create(printOutput, &initD, 0, 0) == NULL) {
perror("Thread d creation failure.");
exit(EXIT_FAILURE);
}
// Exit from main via ST.
st_thread_exit(NULL);
return 0;
}
void *getInputStream(void *state) {
ThreadInit *threadInit = state;
char inputChar = getchar();
while (inputChar != EOF) {
deposit(inputChar, threadInit->produceBuff); //where segfault occurs
inputChar = getchar();
st_usleep(SLEEP_TIME);
}
st_thread_exit(NULL);
}
and buffer.c
void createBuffer(Buffer *buff, int buffSize){
buff = (Buffer*) calloc(1, sizeof(Buffer));
semaphore mutex,emptyBuffers,fullBuffers;
buff->mutex = calloc(1,sizeof(semaphore));
buff->emptyBuffers = calloc(1,sizeof(semaphore));
buff->fullBuffers = calloc(1,sizeof(semaphore));
createSem(buff->mutex,1);
createSem(buff->emptyBuffers,buffSize);
createSem(buff->fullBuffers,0);
buff->charBuff = malloc(sizeof(char) * buffSize);
buff->nextIn = 0;
buff->nextOut = 0;
buff->buffSize = buffSize;
}
The seg fault occurs the first time an operation is done on the semaphores in my buffers, which leads me to believe their memory is improperly allocated, though I included the code from my main in case I'm wrong in that assumption. Also, in case it's not clear from my code, I'm quite new to C, so I'd appreciate any guidance. Thanks!
Here is the error
void createBuffer(Buffer *buff, int buffSize){
buff = (Buffer*) calloc(1, sizeof(Buffer));
you need to return the pointer of the buffer otherwise you are not returning the changed pointer to the caller
void createBuffer(Buffer **buff, int buffSize){
*buff = calloc(1, sizeof(Buffer));
a bit simplified : it is similar to
int foo(int a)
{
a = 1; // 1 not visible outside foo
}
and
int foo(int *a)
{
*a = 1; // 1 is visible outside foo
}
also in C you don't cast what is returned from calloc/malloc only if you are compiling with a C++ compiler but then you should use new instead
In c, function parameter is passed by value so your createBuffer() function didn't really create anything; it just leaked memory instead.
One easy fix is to allocate the memory in main():
bufferA = (Buffer*) calloc(1, sizeof(Buffer));
and remove this line:
buff = (Buffer*) calloc(1, sizeof(Buffer));
I don't see how your createSem() is implemented by you may want to check it too.

How to account for a circular buffer size of 1 when using pthreads / cond_signal / cond_wait?

I'm trying to implement a program that uses a thread to read data from a file and write to a buffer of an arbitrary size, while two other threads read info from this buffer. Everything works fine, except for when I specify a buffer size of 1. When I do that, everything locks up. I'm more or less adapting the classic "consumer/producer" example from here Here's my code:
Struct I'm using:
struct prodcons {
char** buffer; pthread_mutex_t lock;
pthread_cond_t notempty; pthread_cond_t notfull;
int readpos, writepos, finished;
};
My "add to buffer" thread
static void buf_add(struct prodcons *b, char* data) {
/* Add the data after locking the buffer */
pthread_mutex_lock(&b-> lock);
printf("Reader adding %s\n", data);
int err;
/*Wait until buffer is not full*/
while ((b->writepos + 1) % numlines == b->readpos) {
err = pthread_cond_wait(&b->notfull, &b->lock);
if (err != 0) { fprintf(stderr, "cond wait");}
}
/* Needed to stop writes */
if (data != NULL) {
b->buffer[b->writepos] = strdup(data);
} else {
//fprintf(stderr, "End of file reached, adding NULL\n");
b->buffer[b->writepos] = NULL;
}
/* Increments the writing position */
(*b).writepos++;
if ((*b).writepos >= numlines) {
printf("Resetting write position\n");
(*b).writepos = 0;
}
/* Unlock */
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
Here's what output looks like:
Reader adding 64.233.173.85
And then it just hangs. It's clear that it's never getting beyond that first while loop. It works with any other size, but not with 1. what would be the best way of implement a fix for this? If this helps, here is my "get from buffer" method.
static void *read_from_buffer(struct prodcons *b) {
pthread_mutex_lock(&b -> lock);
/* We have to wait for the buffer to have something in it */
while ((*b).writepos == (*b).readpos) {
pthread_cond_wait(&b->notempty, &b->lock);
}
/* Set the thread delay */
thread_delay.tv_sec = threaddelay / 100000;
thread_delay.tv_nsec = 1000*threaddelay%100000;
char *t = NULL;
/* Read the data and advance the reader pointer */
if ((*b).buffer[(*b).readpos] != NULL) {
t = (char*)malloc(strlen ((*b).buffer[(*b).readpos] ) + 1);
strcpy(t, (*b).buffer[(*b).readpos]);
printf("Consumer %u reading from buffer: got %s\n", (unsigned int)pthread_self(), t);
/*At this point, we should probably check is FQDN*/
if (strcmp(t, "-1") == 0) {
(*b).finished = 1;
} else {
nanosleep(&thread_delay, &thread_delay_rem);
check_cache(t, &cache);
}
}
/* We have to adjust the reading position */
(*b).readpos++;
if ( (*b).readpos >= numlines) {
(*b).readpos = 0;
}
/*Need to signal and unlock */
pthread_cond_signal (&b->notfull);
pthread_mutex_unlock(&b->lock);
return t;
}
I'm sure there is a fairly simple fix to handle this edge case, but I can't seem to figure it out. Any suggestions would be much appreciated!
EDIT: I also initialize my buffer like so:
static void init(struct prodcons *temp) {
(*temp).buffer = (char**)malloc(numlines * sizeof(char*));
Not having stepped through your code, but you're writing a terminating NUL '\0' byte, and that would take up an entire 1-byte buffer. The writer waits forever for space in the buffer.
while ((b->writepos + 1) % numlines == b->readpos) { /* always satisfied */

Producer/consumer with bounded buffer

Could someone check my code and tell me if I am on the right track.. It seems like I am a bit lost.. if you see my errors, please let me know them..
What I am trying to do is to solve bounded buffer using my own semaphores as well as GCD.
Thanks in advance..
sema.c
void procure( Semaphore *semaphore ) {
pthread_mutex_lock(semaphore->mutex1);
while(semaphore->value <= 0)
pthread_cond_wait(&semaphore->condition, semaphore->mutex1);
semaphore->value--;
pthread_mutex_unlock(semaphore->mutex1);
}
void vacate( Semaphore *semaphore ) {
pthread_mutex_lock(semaphore->mutex1);
semaphore->value++;
pthread_cond_signal(&semaphore->condition);
pthread_mutex_unlock(semaphore->mutex1);
}
void init ( Semaphore *semaphore ){
semaphore->value = 1;
pthread_mutex_t myMutex;
semaphore->mutex1 = &myMutex;
pthread_mutex_init( semaphore->mutex1, NULL);
}
void destroy ( Semaphore *semaphore ) {
pthread_mutex_destroy(semaphore->mutex1);
}
and main.c
struct variables {
Semaphore *sem;
};
struct variables vars;
void constructer (int *buffer, int *in, int *out) {
init(vars.sem);
}
void deconstructer () {
destroy(vars.sem);
}
int rand_num_gen() {
uint_fast16_t buffer;
int file;
int *rand;
file = open("/dev/random", O_RDONLY);
while( 1 ) {
read(file, &buffer, sizeof(buffer));
printf("16 bit number: %hu\n", buffer );
*rand = (int) buffer;
close(file);
break;
}
return *rand;
}
void put_buffer( int* buffer, int* in, int* out ) {
buffer[*in] = rand_num_gen(); // produce
procure(vars.sem); // wait here
*in = (*in + 1) % BUF_SIZE;
vacate(vars.sem);
}
void get_buffer( int* buffer, int* in, int* out ) {
int value;
procure(vars.sem);
value = buffer[*out];
vacate(vars.sem);
*out = (*out + 1) % BUF_SIZE;
}
int main (void) {
int *in, *out, *buffer;
constructer(buffer, in, out);
dispatch_queue_t producer, consumer;
producer = dispatch_queue_create("put_buffer", NULL);
consumer = dispatch_queue_create("get_buffer", NULL);
dispatch_async(producer,
^{
int i;
do
{
put_buffer( buffer, in, out );
dispatch_async(consumer,
^{
get_buffer( buffer, in, out );
if (i == RUN_LENGTH) exit(EXIT_SUCCESS);
});
}
while (i < RUN_LENGTH);
});
dispatch_main();
deconstructer();
exit (0);
}
Your code has a bug. In the init function you assign the address of a local variable to semaphore->mutex1, and when the function returns this address will be invalid. Later you still use this address, so this leads to undefined behavior.
You must either allocate the memory for the mutex directly in the semaphore (without a pointer) or allocate the memory via malloc.
Update:
Your program has so many bugs that you should definitely pick an easier topic to learn the basic concepts about memory management, how to allocate, use and reference a buffer, do proper error handling, etc. Here is a slightly edited version of your code. It still won't work, but probably has some ideas that you should follow.
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void procure(Semaphore *semaphore) {
pthread_mutex_lock(semaphore->mutex1);
while (semaphore->value <= 0)
pthread_cond_wait(&semaphore->condition, semaphore->mutex1);
semaphore->value--;
pthread_mutex_unlock(semaphore->mutex1);
}
void vacate(Semaphore *semaphore) {
pthread_mutex_lock(semaphore->mutex1);
semaphore->value++;
pthread_cond_signal(&semaphore->condition);
pthread_mutex_unlock(semaphore->mutex1);
}
struct variables {
mutex_t sem_mutex;
Semaphore sem;
};
struct variables vars;
void constructor(int *buffer, int *in, int *out) {
vars.sem.value = 1;
vars.sem.mutex1 = &vars.sem_mutex;
pthread_mutex_init(vars.sem.mutex1, NULL);
}
void deconstructor() {
pthread_mutex_destroy(&semaphore->mutex1);
}
int rand_num_gen() {
const char *randomfile = "/dev/random";
unsigned char buffer[2]; // Changed: always treat files as byte sequences.
FILE *f = fopen(randomfile, "rb");
// Changed: using stdio instead of raw POSIX file access,
// since the API is much simpler; you don't have to care
// about interrupting signals or partial reads.
if (f == NULL) { // Added: error handling
fprintf(stderr, "E: cannot open %s\n", randomfile);
exit(EXIT_FAILURE);
}
if (fread(buffer, 1, 2, f) != 2) { // Added: error handling
fprintf(stderr, "E: cannot read from %s\n", randomfile);
exit(EXIT_FAILURE);
}
fclose(f);
int number = (buffer[0] << CHAR_BIT) | buffer[1];
// Changed: be independent of the endianness of the system.
// This doesn't matter for random number generators but is
// still an important coding style.
printf("DEBUG: random number: %x\n", (unsigned int) number);
return number;
}
void put_buffer( int* buffer, int* in, int* out ) {
buffer[*in] = rand_num_gen(); // produce
procure(&vars.sem); // wait here
*in = (*in + 1) % BUF_SIZE;
vacate(&vars.sem);
}
void get_buffer( int* buffer, int* in, int* out ) {
int value;
procure(&vars.sem);
value = buffer[*out];
vacate(&vars.sem);
*out = (*out + 1) % BUF_SIZE;
}
int main (void) {
int inindex = 0, outindex = 0;
int buffer[BUF_SIZE];
constructor(buffer, &inindex, &outindex);
// Changed: provided an actual buffer and actual variables
// for the indices into the buffer.
dispatch_queue_t producer, consumer;
producer = dispatch_queue_create("put_buffer", NULL);
consumer = dispatch_queue_create("get_buffer", NULL);
dispatch_async(producer, ^{
int i;
do {
put_buffer(buffer, &inindex, &outindex);
dispatch_async(consumer, ^{
get_buffer(buffer, &inindex, &outindex);
if (i == RUN_LENGTH) exit(EXIT_SUCCESS);
});
} while (i < RUN_LENGTH);
});
dispatch_main();
deconstructor();
exit (0);
}
As I said, I didn't catch all the bugs.

Resources