Through the development of the wrapper functions presented earlier, you now have a simple, somewhat elegant approach to creating and utilizing message queues in your applications. Now, we will turn the discussion to directly manipulating the internal structures associated with a given message queue.
To perform control operations on a message queue, you use the msgctl() system call.
SYSTEM CALL: msgctl(); PROTOTYPE: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf ); RETURNS: 0 on success -1 on error: errno = EACCES (No read permission and cmd is IPC_STAT) EFAULT (Address pointed to by buf is invalid with IPC_SET and IPC_STAT commands) EIDRM (Queue was removed during retrieval) EINVAL (msgqid invalid, or msgsz less than 0) EPERM (IPC_SET or IPC_RMID command was issued, but calling process does not have write (alter) access to the queue) NOTES:
Retrieves the msqid_ds structure for a queue, and stores it in the address of the buf argument.
Sets the value of the ipc_perm member of the msqid_ds structure for a queue. Takes the values from the buf argument.
Removes the queue from the kernel.
Recall our discussion about the internal data structure for message queues (msqid_ds). The kernel maintains an instance of this structure for each queue which exists in the system. By using the IPC_STAT command, we can retrieve a copy of this structure for examination. Let's look at a quick wrapper function that will retrieve the internal structure and copy it into a passed address:
int get_queue_ds( int qid, struct msgqid_ds *qbuf ) { if( msgctl( qid, IPC_STAT, qbuf) == -1) { return(-1); } return(0); }
Now that we have a copy of the internal data structure for a queue, what attributes can be manipulated, and how can we alter them? The only modifiable item in the data structure is the ipc_perm member. This contains the permissions for the queue, as well as information about the owner and creator. However, the only members of the ipc_perm structure that are modifiable are mode, uid, and gid. You can change the owner's user id, the owner's group id, and the access permissions for the queue.
Let's create a wrapper function designed to change the mode of a queue. The mode must be passed in as a character array (i.e. ``660'').
int change_queue_mode( int qid, char *mode ) { struct msqid_ds tmpbuf; /* Retrieve a current copy of the internal data structure */ get_queue_ds( qid, &tmpbuf); /* Change the permissions using an old trick */ sscanf(mode, "%ho", &tmpbuf.msg_perm.mode); /* Update the internal data structure */ if( msgctl( qid, IPC_SET, &tmpbuf) == -1) { return(-1); } return(0); }
BE CAREFUL! It is possible to alter the permissions on a queue, and in doing so, inadvertently lock yourself out! Remember, these IPC objects don't go away unless they are properly removed, or the system is rebooted. So, even if you can't see a queue with ipcs doesn't mean that it isn't there.
To illustrate this point, a somewhat humorous anecdote seems to be in order. While teaching a class on UNIX internals at the University of South Florida, I ran into a rather embarrassing stumbling block. I had dialed into their lab server the night before, in order to compile and test the labwork to be used in the week-long class. In the process of my testing, I realized that I had made a typo in the code used to alter the permissions on a message queue. I created a simple message queue, and tested the sending and receiving capabilities with no incident. However, when I attempted to change the mode of the queue from ``660'' to ``600'', the resulting action was that I was locked out of my own queue! As a result, I could not test the message queue labwork in the same area of my source directory. Since I used the ftok() function to create the IPC key, I was trying to access a queue that I did not have proper permissions for. I ended up contacting the local system administrator on the morning of the class, only to spend an hour explaining to him what a message queue was, and why I needed him to run the ipcrm command for me. grrrr.
After successfully retrieving a message from a queue, the message is removed. However, as mentioned earlier, IPC objects remain in the system unless explicitly removed, or the system is rebooted. Therefore, our message queue still exists within the kernel, available for use long after a single message disappears. To complete the life cycle of a message queue, they should be removed with a call to msgctl(), using the IPC_RMID command:
int remove_queue( int qid ) { if( msgctl( qid, IPC_RMID, 0) == -1) { return(-1); } return(0); }