I am developing a command line application in C (linux environment) to edit a particular file format. This file format is a plain XML file, which is compressed, then encrypted, then cryptographically signed.
I'd like to offer an option to the user to edit this kind of file in an easy way, without the hassle of manualy extracting the file, editing it, and then compressing, encrypting and signing it.
Ideally, when called, my application should do the following:
Open the encrypted/compressed file and extract it to a temporary location (like /tmp)
Call an external text editor like nano or sublime-text or gedit depending on which is installed and maybe the user preferences. Wait until the user have edited the file and closed the text editor.
Read the modified temporary file and encrypt/compress it, replacing the old encrypted/compressed file
How can I achieve point no. 2?
I thought about calling nano with system() and waiting for it to return, or placing an inotify() on the temp file to know when it is modified by the graphical text editor.
Which solution is better?
How can i call the default text editor of the user?
Anything that can be done in a better way?
First, consider not writing an actual application or wrapper yourself, which calls another editor, but rather writing some kind of plugin for some existing editor which is flexible enough to support additional formats and passing its input through decompression.
That's not the only solution, of course, but it might be easier for you.
With your particular approach, you could:
Use the EDITOR and/or VISUAL command-line variables (as also pointed out by #KamilCuk) to determine which editor to use.
Run the editor as a child process so that you know when it ends execution, rather than having to otherwise communicate with it. Being notified of changes to the file, or even to its opening or closing, is not good enough, since the editor may make changes multiple files, and some editors don't even keep the file open while you work on it in them.
Remember to handle the cases of the editor failing to come up; or hanging; or you getting some notification to stop waiting for the editor; etc.
Call an external text editor like nano or sublime-text or gedit depending on which is installed and maybe the user preferences. Wait until the user have edited the file and closed the text editor.
Interesting question. One way to open the xml file with the user's default editor is using the xdg-open, but it doesn't give the pid of the application, in which user will edit the file.
You can use xdg-mime query default application/xml to find out the .desktop file of the default editor, but then you have to parse this file to figure out the executable path of the program - this is exactly how xdg-open actually works, in the search_desktop_file() function the line starting with Exec= entry is simply extracted from the *.desktop to call the editor executable and pass the target file as argument... What I am trying to say, is, after you find the editor executable, you can start it, and wait until it's closed, and then check if the file content has been changed. Well, this looks like a lot of unnecessary work...
Instead, you can try a fixed well-known editor, such as gedit, to achieve the desired workflow. You can also provide user a way (i.e. a prompt or config file) to set a default xml editor, i.e. /usr/bin/sublime_text, which then can be used in your programm on next run.
However, the key is here to open an editor that blocks the calling process, until user closes the editor. After the editor is closed, you can simply check if the file has been changed and if so, perform further operations.
To find out, if the file contents have been modified, you can use the stat system call to get the inode change time of the file, before you open the file, and then compare the timestamp value with the current one once it is closed.
i.e.:
stat -c %Z filename
Output: 1558650334
Wrapping up:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void execute_command(char* cmd, char* result) {
FILE *fp;
fp = popen(cmd, "r");
fscanf (fp, "%s" , result);
}
int get_changetime(char* filename) {
char cmd[4096];
char output[10];
sprintf(cmd, "stat -c %%Z %s", filename);
execute_command(cmd, output);
return atoi(output);
}
int main() {
char cmd[4096];
char* filename = "path/to/xml-file.xml";
uint ctime = get_changetime(filename);
sprintf(cmd, "gedit %s", filename);
execute_command(cmd, NULL);
if (ctime != get_changetime(filename)) {
printf("file modified!");
// do your work here...
}
return 0;
}
Related
This code does not open file properly, it returns no such file or directory, although the path and privilege are there and no other program is using the file. How can I fix the error? I tried swapping the path and moving the file, the error is there still.
char string[105];
FILE* file = fopen("C:\\Users\\Public\\Documents\\a.txt", "r");
while (fgets(string, 100, file)) {
printf("%s", string);
}
It can be surprisingly tricky to open a simple file! Lots of things can go wrong. I recommend writing slightly more verbose code, like this:
#include <string.h>
#include <errno.h>
char* filename = "C:\\Users\\Public\\Documents\\a.txt";
char string[105];
FILE* file = fopen(filename, "r");
if (file == NULL) {
fprintf(stderr, "can't open %s: %s\n", filename, strerror(errno));
exit(1);
}
while (fgets(string, 100, file)) {
printf("%s", string);
}
The point is that the error message prints out both the name of the file it tried but failed to open, and the actual reason it couldn't open it. (Also, by storing the filename in a variable, you make it absolutely certain that the filename it prints in the error message is the filename it tried but failed to open.) It sounds like you already know that the error was "no such file or directory", but I'm not sure how you know this.
Even though I've been programming in C for a long time, sometimes I still have this problem. One thing I'll sometimes do is use my mouse to copy the exact filename string printed in the error message, the file the program said it couldn't open, the file I'm sure is really there, and paste it into a terminal window (or CMD or Powershell if you're on Windows), along with a directory-listing command, to see if the operating system can actually see the file. That is, for your example, the command I'd run is
dir C:\Users\Public\Documents\a.txt
but the point is that I would not actually type the pathname "C:\Users\Public\Documents\a.txt", instead I would copy and paste it out of the error message that the program printed. Sometimes there are surprising little impossible-to-spot differences between the filename you thought it was trying to open, versus the filename it was actually trying to open, and this exercise is a good way to let the computer help you find those differences.
Remember, too, that you'll get the error "No such file or directory" if there's no file by that name in the directory, or if the directory isn't there at all. For example, if you're trying to open the path
C:\Users\Public\Documents\a.txt
and the file a.txt keeps not being there, and you keep checking your home directory
C:\Users\Donkey\Documents
and you keep seeing that the file is there, it can be surprisingly easy to overlook what the real problem is. :-)
Addendum: You might be having an issue with the different Unix/Linux versus Windows file path separators, that is, / versus \. Usually, on a Windows machine, it's safest to use \, as you've done. (One very frequent mistake is to forget to double the backslashes, but it looks like you got that right.) Depending on your programming environment, if there's some level of Unix emulation going on, you can sometimes use Unix-style /, and it will automatically translate to \ for you. I've never heard of a situation where using \ made it not work (which is a possibility being explored in the comments), but you might experiment with that, perhaps trying
char* filename = "/c/Users/Public/Documents/a.txt";
or (less likely)
char* filename = "C:/Users/Public/Documents/a.txt";
I have a set of configuration files (10 or more), and if user opens any of these file using any editor (e.g vim,vi,geany,qt,leafpad..). How would I come to know that which file is opened and if some writing process is done, then it is saved or not (using C code).
For the 1st part of your question, please refer e.g. to How to check if a file has been opened by another application in C++?
One way described there is to use a system tool like lsof and call this via a system() call.
For the 2nd part, about knowing whether a file has been modified, you will have to create a backup file to check against. Most editors already do that, but their naming scheme is different, so you might want to take care of that yourself. How to do that? Just automatically create a (hidden) file .mylogfile.txt if it does not exist by simply copying mylogfile.txt. If .mylogfile.txt exists, is having an older timestamp than mylogfile.txt, and differs in size and/or hash-value (using e.g. md5sum) your file was modified.
But before re-implementing this, take a look at How do I make my program watch for file modification in C++?
Here is a simple C program for illustration:
#include <windows.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
//MoveFile(argv[0], "dst.exe");
getchar();
return 0;
}
make an test.exe from code above.
Now execute test.exe, the test.exe hangs there due to getchar(), then I can cut and paste this exe freely.
But when I uncomment that MoveFile(argv[0], "dst.exe");, I was hoping it could move itself to dst.exe, it turns out to have a dst.exe, while program.exe is still there, just like CopyFile() does.
From what I know, in Windows, when exe is running I can rename it, move it, but not deleting it, that MoveFile() behaves as a combination of CopyFile() and DeleteFile()
And also see this from Microsoft doc MoveFileEx.
BOOL WINAPI MoveFileEx(
_In_ LPCTSTR lpExistingFileName,
_In_opt_ LPCTSTR lpNewFileName,
_In_ DWORD dwFlags
);
dwFlags has an option MOVEFILE_COPY_ALLOWED
the file is to be moved to a different volume, the function simulates the move by using the CopyFile and DeleteFile functions.
If the file is successfully copied to a different volume and the original file is unable to be deleted, the function succeeds leaving the source file intact.
This value cannot be used with MOVEFILE_DELAY_UNTIL_REBOOT.
Further confirming my guess, and I tested with MoveFileEx() with option MOVEFILE_REPLACE_EXISTING , recompiled the program, run it, now MoveFileEx() just returned as fail, not even dst.exe generated.
But I can definitely cut and paste that exe while running, MoveFileEx() should does so, why???
If they can't, what should I do to make it just like cut and paste.
If the target destination is on the same volume, MoveFile just updates the corresponding directory entries. The file's MFT record is not changed, its index remained the same, its contents is not touched. Because the file is not affected at all, you can move it within the same directory (i.e. rename) or within the same volume even if the file is in use (note: this is true for files being executed; in general, this is true only if the file was open with FILE_SHARE_DELETE).
If the target directory is on another volume, the system needs to copy it (will fail if the file is open in exclusive mode) and delete it on the old volume (will fail unconditionally if the file is in use).
Cut&paste works ok within the same volume and does not on different volumes. The reason is that the file clipboard operations use a different technique than the text ones.
When you select a text and press Ctrl-X, the text string is moved to an allocated global memory block and the block is passed to Windows. The program does not own it anymore. The text is physically in the Windows Clipboard and you can paste it as many times as you wish.
When you press Ctrl-X on a file, it is not moved to the Clipboard. The Clipboard will receive a file descriptor, which contains info about the file and the requested operation (this technique is known as delayed rendering). When you press Ctrl-C, the Clipboard will simply ask the object owner (i.e. Windows Explorer) to perform the requested operation. And the Explorer will perform it using the very same MoveFile.
Note that you can paste a cut file only once because the first Ctrl-C will invalidate the descriptor in the Clipboard. A copied file can be pasted several times.
I have a method (parse) that processes data from an input file, which may have been opened in binary mode. However in some subclasses it would be easier to process the data if the file were opened in text mode. So my question is if theres an easy way to wrap any file to get something that acts as a text mode file.
Note that the solution in "Convert binary input stream to text mode" does not really make it as it only produces an iterator (and not a file-like object). Also note that opening the file in text mode in the first place is not an option.
If it simplifies the solution one can assume that the input file is indeed opened in binary mode.
It appears as the buffer argument in io.TextIOWrapper is actually an io.BufferedReader object (ie file opened in binary mode). This is however not obvious from reading the documentation.
This seem to work if the file is known to be opened in binary mode (instance of io.RawIOBase or io.BufferedIOBase):
srctxt = io.TextIOWrapper(src)
It doesn't work however if src is already opened in text mode, but it could be tested by checking if it is is an io.TextIOBase:
if isinstance(src, io.TextIOBase):
srctxt = src
else:
srctxt = io.TextIOWrapper(src)
I'm writing a text editor in gtk+ 2.0 & gtksourceview 2.0. Currently I'm using gtk_text_buffer_get_text and g_file_set_contents to save the textbuffer to a file. I see in the docs for g_file_set_contents that it says:
Also since the file is recreated, existing permissions, access control lists, metadata etc. may be lost.
I looked around devhelp and Google and can't find an alternative way to save the textbuffer to a file while preserving file permissions. Any ideas on how to accomplish this? Thanks.
As #ptomato suggested using a method that truncates the file, then writing out the text buffer worked as desired. Here is a snippet of code which worked for me:
gtk_text_buffer_get_end_iter(tbuffer,&end_iter);
gtk_text_buffer_get_start_iter(tbuffer,&start_iter);
text = gtk_text_buffer_get_text(tbuffer,&start_iter,&end_iter,FALSE);
FILE *fp;
fp=fopen(path, "w");
fprintf(fp, "%s", text);
fclose(fp);