0

I have one application (let's say App A) that I have no control over and I cannot modify, writes to a file and has the ability to rename and delete it.

I have another application (let's say App B) which is reading from the file written by App A. I have full control over the code in App B.

What should I do (i.e. what permissions to set to the file, or what lock should I use in App B when it opens the file) such that App A can continue writing to the file but cannot rename or delete it.

I tried to use fcntl() in App B to set read and write locks on the file but the App A can still rename the file.

Here's the sample code to acquire locking from App B (fd is the file descriptor to the file):

int fd = open(filePath, O_RDONLY);
struct flock lock_it;
lock_it.l_type = F_WRLCK; // I've tried F_RDLCK as well.
lock_it.l_whence = SEEK_SET;
lock_it.l_start = 0;
lock_it.l_len = 0; // I want to lock the entire file. 
int lockingResult = fcntl(fd,F_SETLK,&lock_it);
cout << "Got a fcntl lock: " << lockingResult << " on  FD: " << fd << endl;

But the above code doesn't prevent App A from renaming the file.

Any ideas what should I do? My code is in C++ and my application is running on RedHat Linux.

melpomene
  • 84,125
  • 8
  • 85
  • 148
BlueChips23
  • 1,861
  • 5
  • 34
  • 53

2 Answers2

2

To write to a file you must have write permission on the file but to delete or rename a file you must have write permission on the directory.

Note: even if you cannot delete a file you can still truncate it, unless your file system supports the concept of append only files.

Gaius
  • 2,556
  • 1
  • 24
  • 43
2

Caveat: The following assumes that appB is fast enough to do this (i.e. there is a bit of a race condition).

appB can create a hard link to the file entry under a different name. This will prevent appA from deleting the file (see man 2 link):

link("appAfile","appBfile");

Now, if appA deletes [or renames] the file, it is still accessible via appBfile

When a file is created for the first time, it creates a directory entry for the file and what is called an inode.

Inodes have a reference count. When an inode is created initially, it has a reference count of 1. When a program opens a file, the inode's reference count is incremented, and when a program closes the file descriptor, the inode's reference count is decremented.

When appA deletes the file (via unlink(2)), the directory entry is removed and the inode's reference count is decremented. If appA still has the file open, the inode's contents are not removed until appA closes the open file descriptor (i.e. the reference count must go to 0).

The directory entry is [more or less] just: filename|inode-number. It is the inode that has information such as file size, list of blocks that are the data for the file, permissions, etc.

inodes are in a separate table in the filesystem, indexed by inode-number.

If appB is able to open appAfile before appA renames or deletes it (more properly, it unlinks the directory entry, which is why the syscall is unlink instead of (e.g.) delete), the data is still accessible because when appB did the open, the inode's reference count was incremented. That is, the refcount is now 2. If appA deletes the file, the inode's refcount is decremented to 1. It is still non-zero, so the data is readable by appB.

As long as appB holds an open descriptor, the data will remain. But, no other app could access it because the directory entry is lost. And, when appB closes the file, the inode's refcount goes to 0 and the data blocks are reclaimed.

When appB does the hardlink operation, it creates a second directory entry with the same inode number and increments that inode's refcount. That is, for a given inode, if no programs hold open file descriptors, the inode's refcount is the number of directory entries that have links to it. This is normally 1, but after appB creates a hard link, the refcount will be 2. Many such hardlinks can be created (using different "alias" names) and the inode's refcount is incremented accordingly.

This allows the file to persist even if appA has deleted it, closed the file descriptor, and appB has closed its file descriptor. The inode will have a refcount of 1 that comes from appBfile. If appB had an open descriptor, the refcount would be 2 (which goes back to 1 when appB closes the file).

Note that if appA renames the directory entry (e.g. rename(appAfile,appAfile2)), the rename does not decrement the refcount. appB might have trouble finding it under the new name, but the data would still exist (i.e. the inode has not been deleted).

So, the inode remains as long as an app has an open descriptor, or there is a directory entry with the inode's inode-number. In other words, at any given time, an inode's refcount is the sum of the number of directory entries that are linked to it + the number of file descriptors that are open on it. To delete/remove the inode data, the refcount must go to 0.

Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • this is just a variation on @melpomene's comment above. Once you have the file open you can continue reading from the file descriptor after the file is removed (and of course renamed). I don't see what hard link buys you. – MK. Aug 20 '18 at 22:01
  • @MK. I'm doing a full write up on this – Craig Estey Aug 20 '18 at 22:16