Editing an /etc/fstab entry in C++ - c++

I'm trying to edit the /etc/fstab file on a CentOS installation using C++. The idea being that based on another config file I will add entries that don't exist in the fstab, or edit entries in the fstab file where the mount point is the same. This lets us set the system up properly on initial bootup.
I've found setmntent() and getmntent() for iterating over the exiting entries so I can easily check whether an entry in fstab also exists in my config file. And I can then use addmntent() to add any entry that doesn't already exist - the documentation says nothing about this being able to edit an entry, only add a new entry to the end of the file. There seems to be no way to edit an existing entry or delete an entry. It seems odd that this feature doesn't exist, only the CR and not the UD of CRUD.
I'd rather not have to write my own parser if I can at all help it.
My other alternative is to:
open the file using setmntent()
read the whole of fstab into memory using getmentent() and perform any additions and/or edits
close the file using endmntent()
open /etc/fstab for writing
close /etc/fstab (thus emptying the file)
open the fstab using setmntent()
loop through the entries I read in previously and write them out using addmntent()
Which although probably fine, just seems a bit messy.

When modifying system configuration files such as /etc/fstab keep in mind that these are critical state and, should your "edit" be interrupted by a power loss, might result in a failure to reboot.
The way to deal with this is:
create an empty output:
FILE* out = setmntent("/etc/fstab.new", "rw");
open the original for input:
FILE* in = setmntent("/etc/fstab", "r");
copy the contents:
while (m = getmntent(in)) { addmntent(out, m); }
make sure the output has it all:
fflush(out); endmntent(out); endmntent(in);
atomically replace /etc/fstab:
rename("/etc/fstab.new", "/etc/fstab");
It's left as an exercise to the reader to change the body of the while loop to make a modification to an existing element, to substitute a specifically crafted mntent or whatever. If you have specific questions on that please ask.
UN*X semantics for rename() guarantee that even in the case of power loss, you'll have either the original version or your new updated one.
There's a reason why there is no modifymntent() - because that would encourage bad programming / bad ways of changing system critical files. You say at the end of your post "... probably fine ..." - not. The only safe way to change a system configuration file is to write a complete modified copy, sync that to safe storage, and then use rename to replace the old one.

Related

Linux : Can a directory rename be partially executed due to a power/transient mechanical failure in Server/Disk?

C/C++ rename function can be used to rename a directory.
Assume below situation (for Linux)
Directory X has files A, B and C.
X is renamed to Y (using C/C++ rename function). While the operation is in progress Server/Disk's power goes out. Then it is restarted.
Now is there a possibility of few files being in a directory X while others in Y.
e.g.
X : B
Y : A, C
Rename is only changing a name. The "id" of the file, and in linux a directory is a file which contains the "links" to other files and directories, stays the same!
As a file system is always directly relying to the physical block storage on any kind of hardware, always the complete block where the label is stored, must be rewritten and linked in the file system structure.
If a power fail happens in between, the "directory file" can be corrupted. This means, that more than the single renaming operations is involved!
BUT:
Modern file systems have many options to detect and repair such situations. E.G. ext4 has a journal in background. If any access may be interrupted, the journal has the information that these operation has started but not completed. By mounting such a partition/fs, the repair takes place automatically. If this is not possible, a fschk can do that job.
The situation, that only "some files" have been moved, is definitely never possible, because renaming a directory is not creating a new directory and moving the file names/links to the node id into the new directory, it is only a new name for an existing directory.
As a user: Simply use modern file systems and mostly all power down failures can be recovered by restart. You may find your filesystem in the "old" or the "new" version, but not in between.
It depends on implementation but for all file systems I know renaming is just replacing a label. So renaming of a directory doesn't differ from file rename operation.
And when you rename a file and turn off power you never end up with two files X and Y each having half of the file content.

exception of boostlog when date changed to next day

I use boost log by this config.
[Sinks.2]
Filter="%Severity% >= 2"
Destination=TextFile
AutoFlush=true
Format="[%TimeStamp%] [%ThreadID%] <%Severity%> %Message%"
Asynchronous=false
Target="logs"
FileName="logs/quo.%Y%m%dT%H%M%S.%a.%5N.log.detail"
RotationTimePoint="00:00:00"
RotationSize=104857600
MinFreeSpace=4294967296
MaxSize=4294967296
ScanForFiles=All
when date change to next day. my program crash by exception:
terminate called after throwing an instance of
'boost::filesystem::filesystem_error'
what(): boost::filesystem::last_write_time: No such file or directory: "/root/work/hy-trade/bin/debug/logs/quo.20181027T173106.Sat.00000.log.detail"
I check my disk space, find the free space less than MinFreeSpace in config and the file quo.20181027T173106.Sat.00000.log.detail not exists.
how to avoid this exception?
version of boost is 1.67
thank you
It looks like someone had already deleted the log file before it was rotated. It may have been an external process, or Boost.Log.
With Boost.Log, this can happen if you have multiple file sinks that write log files into the same directory, which is also used as the target directory for the rotated files (i.e. the FileName parameter includes the path specified in the Target parameter, and there are multiple sinks that use that path). The problem is because, according to ScanForFiles=All, the library scans the target directory for any files but does not update the file counter to be used for creating new files. This means that if the file "quo.20181027T173106.Sat.00000.log.detail" was present in that directory when your process started then it would be considered as an old file, even if upon starting your process would be still writing new logs to that file. Then, when a file rotation happens and storage limits are exceeded (e.g. if MinFreeSpace is not satisfied), that file may be deleted. The rotation has to happen on another sink that still stores files into the same "logs" directory.
To solve the problem you can do one of the following:
Use ScanForFiles=Matching in your settings so that the file counter is updated after scanning. This will make sure that new log files have unique names and don't get deleted prematurely.
Write log files to a different directory from your target storage. I.e. specify FileName so that it doesn't point to the same directory as Target.
Also, you may want to add exception handling to avoid crashing in case of errors (which may still happen for whatever reason on filesystem operations). See here and here for more info (also, follow the links in those sections).

How I can monitor the output files and move/rename in desired directory

A program generates a text file after every 15 iterations. It overwrites the output.txt (formed at 15th step) with a new output.txt (formed at 30th step), due to using the same name. I can't modify the file name within the program. Can I run some script concurrently with the program on my Ubuntu system that monitors my directory and moves the output.txt file to a desired directory when it is formed or changes the output file name?
I can't modify the file name within the program.
(I take this to mean you are required to not change the file name, not that you don't know how.)
You've marked this posting as C++.
While it is possible to run some script to monitor a directory, coordinating the name change and running a thread or another process (from C++) can be much more challenging than other choices.
How about a simpler approach:
I suggest using std::stringstream to generate a unique pfn (path-file-name) for each time you want to write a file. For instance, an incrementing number can be appended to the unmodifiable-file-name.
Something like:
std::string uniqueFileName(void)
{
std::stringstream ss;
// vvvvvvvvvv -- unmodifiable-file-name is not changed
ss << "output.txt" << ++fileCount;
uniqueFileName = ss.str();
return(uniqueFileName);
}
Good luck.
PS
If you feel you must write the file first in the correct file name, and then change the file name to something unique ... yes, you can rename the file from within this program (i.e. trivial synchronization)
I would use popen() as I feel it provides more feedback, and I've used it before.
Others prefer something like system() (there are about 6 of these).
In either case, use the command to rename the existing file (to each you provide a bash command, like mv fromPfn toPfn, or maybe you'll need cp.
For each, your code must not proceed until the command has completed.

QSettings - Sync issue between two process

I am using Qsettings for non gui products to store its settings into xml files. This is written as a library which gets used in C, C++ programs. There will be 1 xml file file for each product. Each product might have more than one sub products and they are written into xml by subproduct grouping as follows -
File: "product1.xml"
<product1>
<subproduct1>
<settings1>..</settings1>
....
<settingsn>..</settingsn>
</subproduct1>
...
<subproductn>
<settings1>..</settings1>
....
<settingsn>..</settingsn>
</subproductn>
</product1>
File: productn.xml
<productn>
<subproduct1>
<settings1>..</settings1>
....
<settingsn>..</settingsn>
</subproduct1>
...
<subproductn>
<settings1>..</settings1>
....
<settingsn>..</settingsn>
</subproductn>
</productn>
The code in one process does the following -
settings = new QSettings("product1.xml", XmlFormat);
settings.setValue("settings1",<value>)
sleep(20);
settings.setValue("settings2", <value2>)
settings.sync();
When the first process goes to sleep, I start another process which does the following -
settings = new QSettings("product1.xml", XmlFormat);
settings.remove("settings1")
settings.setValue("settings3", <value3>)
settings.sync();
I would expect the settings1 to go away from product1.xml file but it still persist in the file - product1.xml at the end of above two process. I am not using QCoreApplication(..) in my settings library. Please point issues if there is anything wrong in the above design.
This is kind of an odd thing that you're doing, but one thing to note is that the sync() call is what actually writes the file to disk. In this case if you want your second process to actually see the changes you've made, then you'll need to call sync() before your second process accesses the file in order to guarantee that it will actually see your modifications. Thus I would try putting a settings.sync() call right before your sleep(20)
Maybe you have to do delete settings; after the sync() to make sure it is not open, then do the writing in the other process?
Does this compile? What implementation of XmlFormat are you using and which OS? There must be some special code in your project for storing / reading to and from Xml - there must be something in this code which works differently from what you expect.

Read from a file when a new line 's been written to it by another process

What is the fastest method in C++, to read a new line from a file which is written by another process. Or how my program can be notified that there is a new line in file so read it? (in linux)
The fastest method is to use pipes or events (for Windows apps).
If you still want use files, first of all that you really need, making sure, that a file has been really modified (use seek and compare it with prew value). Than go to the 'last val of seek' and read it.
And it will be better use mutex (if you read data from file).
Assuming the OS supports concurrent file access, all you should need to do is seek to EOF, wait for the stat to change then try to read from the file. You might want to add in a sleep to slow down the loop.
The 'tail' command on POISX (with the -f option) implements this - source code is available.
From the top of my head, did u tried something like this:
Count the lines in a file, store it.
Get the size of the file (google it, i dont want to ruin the fun :D ).
Then try to read from the last line u stored when size of the file changes... and again and again.
Have fun :)
Use inotify to get notification about file changes and then reread from your last pos if the file is now larger then before.