Mandatory locking causes the kernel to check every open, read, and write to verify that the calling process isn't violating a lock on the file being accessed.
In some operating system, mandatory locking is enabled for a particular file by turning on the set-group-ID bit and turning off the group-execute bit.(This is a very stupid way, OS just want to find a unique way, which is not used by any one to mark the file has mandatory locking, if group-execute bit is off, then the file can not be executed by any group number, and then set-group-ID bit is not making any sense, this is the way nobody want to use)
If the file is locked by another process:
1) for blocking descriptor, try to read the file having a read lock of another process, good to go. try to write the file having a read lock of another process, blocks. try to read the file having a write lock of another process, blocks. try to write the file having a write lock of another process, blocks.
2) for nonblocking descriptor, try to read the file having a read lock of another process, good to go. try to write the file having a read lock of another process, return EAGAIN. try to read the file having a write lock of another process, return EAGAIN. try to write the file having a write lock of another process, return EAGAIN.
Normally open system again succeeds on opening the file even if the file has mandatory lock. But if open is called with flag O_TRUNC or O_CREAT, it will return the EAGAIN immediately.
Some special examples of unix application if the file has mandatory locking:
1) "ed" can still write the file content! Because ed use unlink the remove the file and create the new file. unlink is not restricted by mandatory locking.
2) "vi" editor is never able to edit the file.
3) Shell's ">" and ">>" operators to overwrite or append to the file resulted in the error "cannot create".
Mandatory record locking can also be used by a malicious user to hold a read lock on a file that is publicly readable.
2. Example:
lock.c:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/wait.h>
volatile sig_atomic_t sigflag;
sigset_t newmask, oldmask, zeromask;
#define read_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
void sig_usr(int signo)
{
sigflag = 1;
}
void TELL_WAIT(void)
{
// Setup the signal handler
if(signal(SIGUSR1, sig_usr) == SIG_ERR) {
printf("signal error!\n");
exit(5);
}
if(signal(SIGUSR2, sig_usr) == SIG_ERR) {
printf("signal error!\n");
exit(5);
}
// Make signal mask
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
// Block the SIGUSR1, SIGUSR2
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
printf("sigprocmask error!\n");
exit(6);
}
}
void TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2);
}
void WAIT_PARENT(void)
{
// Child process wait for the SIGUSR1 from the parent
while(sigflag == 0)
sigsuspend(&zeromask);
sigflag = 0;
// After receiving the SIGUSR1 from parent
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
printf("sigprocmask error!\n");
exit(6);
}
}
void TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1);
}
void WAIT_CHILD(void)
{
// Parent process wait for the SIGUSR2 from the child
while(sigflag == 0)
sigsuspend(&zeromask);
sigflag = 0;
// After receiving the SIGUSR2 from child
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
printf("sigprocmask error!\n");
exit(6);
}
}
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, int len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return(fcntl(fd, cmd, &lock));
}
int set_fl(int fd, int flag)
{
int val;
if((val == fcntl(fd, F_GETFL, 0)) < 0) {
printf("fcntl error!\n");
exit(12);
}
val |= flag;
if(fcntl(fd, F_SETFL, val) < 0) {
printf("fcntl error!\n");
exit(12);
}
}
int main(int argc, char* argv[])
{
int fd;
pid_t pid;
char buf[5];
struct stat statbuf;
// Open the file descriptor
if((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC)) < 0) {
printf("open error!\n");
exit(1);
}
// Write content to the file
if(write(fd, "abcdef", 6) != 6) {
printf("write error!\n");
exit(2);
}
// Get the file descriptor state
if(fstat(fd, &statbuf) < 0) {
printf("fstat error!\n");
exit(3);
}
// Turn off the group execute bit and turn on the set-group-id bit
// to enable the mandatory locking
if(fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) {
printf("fchmod error!\n");
exit(4);
}
TELL_WAIT();
if((pid = fork()) < 0) {
printf("fork error!\n");
exit(7);
} else if(pid > 0) { // parent
if(write_lock(fd, 0, SEEK_SET, 0) < 0) {
printf("write_lock error!\n");
exit(8);
}
TELL_CHILD(pid);
if(waitpid(pid, NULL, 0) < 0) {
printf("waitpid error!\n");
exit(9);
}
} else { // child
WAIT_PARENT();
set_fl(fd, O_NONBLOCK);
if(read_lock(fd, 0, SEEK_SET, 0) != -1) {
printf("child: read_lock succeeded");
exit(10);
}
printf("read_lock of already-locked region returns %d\n", errno);
if(lseek(fd, 0, SEEK_SET) == -1) {
printf("lseek error!\n");
exit(11);
}
if(read(fd, buf, 2) < 0) {
printf("read failed(mandatory locking works)\n");
}
else {
printf("read ok(no mandatory locking), buf = %2.2s\n", buf);
}
}
exit(0);
}
shell:
The output of above program indicates that there is no mandatory locking in current system. Right now it is advisory locking which means, it assumes all processes are "cooperative" to use read_lock/write_lock to acquire the lock firstly before doing the I/O.
ubuntu@ip-172-31-23-227:~$ ./lock.out test
read_lock of already-locked region returns 11
read ok(no mandatory locking), buf = ab
No comments:
Post a Comment