LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   [C] Command line and file deletion (https://www.linuxquestions.org/questions/programming-9/%5Bc%5D-command-line-and-file-deletion-924412/)

Taiki 01-18-2012 07:35 AM

[C] Command line and file deletion
 
Hello, I'm actually porting my Windows software on Linux.
I coded it with the SDL so the main part of the code directly works with UNIX based system.
I experiment two issues and I would like to know if you may solve them...
1. I need to re-launch my software via command line... For windows, I use ShellExecute, for Mac OSX, open with the -n flag but for linux, I have still not find a way =/

2. For check if the new instance already run, I try to delete a file which is opened by the new instance (which will not run the same code and I don't want that a new instance start to run this code) with windows, if the file was used, remove() don't work but it seems that Linux can't do that. Also, I can't write something in it in case of crash/other thing which won't allow the new instance to say that it doesn't run anymore (and allow a other instance to do that).

Sorry for my bad english

Taiki

catkin 01-18-2012 08:24 AM

SDL? Which language are you programming in?

Taiki 01-18-2012 08:26 AM

SDL is a C graphical library.
http://www.libsdl.org/
I coded the rest in C

MTK358 01-18-2012 10:17 AM

Quote:

Originally Posted by Taiki (Post 4577803)
1. I need to re-launch my software via command line... For windows, I use ShellExecute

I don't know what ShellExecute is, but assuming it runs a command in the shell, system() is the equivalent function in *nix.

Quote:

Originally Posted by Taiki (Post 4577803)
I try to delete a file which is opened by the new instance with windows, if the file was used, remove() don't work but it seems that Linux can't do that.

Huh?

Anyway, to delete a file, use the unlink() function (for deleting a directory, you should use the rmdir() function instead).

Taiki 01-18-2012 10:27 AM

I also use system() (that's why i'm looking for a command line ;)) I'm just wondering which command to use...

In fact, I want to test if the file is open somewhere. windows doesn't allow to delete a file if it's in use so I try to delete it and I test if it's still here. I would like to know if there is a way for test that (maybe with an other way) with linux/darwin...

Nominal Animal 01-18-2012 10:58 AM

Quote:

Originally Posted by Taiki (Post 4577803)
I need to re-launch my software via command line...

Code:

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    :

    if (need to restart this program) {
        execlp(argv[0], argv[0], NULL);
        fprintf(stderr, "%s: Cannot restart: %s.\n", argv[0], strerror(errno));
        return 1;
    }

    :
}

If you want, you can add command line parameters as separate strings before the NULL, i.e. execlp(argv[0],argv[0],"foo","bar",NULL); would run the program with two command line parameters, foo and bar. The first string is the command name, the second one is what the program sees it was called with (e.g. argv[0]), followed by the rest of the arguments.

Code:

For check if the new instance already run, I try to delete a file which is opened by the new instance
No, that only works in Windows, nowhere else. Use an advisory lock on a (configuration) file instead.

Most programs and games create a configuration directory .name/ under the user's home directory, and put the configuration file(s), plugins, high scores and such stuff there. You need very little code for this:
Code:

#ifndef GAMEDIR
#define GAMEDIR ".mygame"
#endif

{  const char *const home = getenv("HOME");
    const char *const gamedir = GAMEDIR;
    int              result;

    if (home && *home)
        do {
            result = mkdir(gamedir, 0700); /* drwx------ */
        } while (result == -1 && errno == EINTR);

    do {
        result = chdir(gamedir);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        fprintf(stderr, "Missing directory '%s': %s.\n", gamedir, strerror(errno));
        exit(1);
    }

    /* Working directory is now the per-user game directory,
    * so you can use relative paths for per-user files,
    * and compile-time absolute paths for fixed data files,
    * for example /usr/share/games/mygame/.
    */
}

Here is an example function to get an exclusive advisory lock. If successful, it will return a file handle to the file; closing the file will release the advisory lock too. If an error occurs, the function will return NULL, with errno set to EAGAIN if the file is already locked. For all other errors you can use strerror(errno) to obtain a descriptive string (same as perror() shows).
Code:

#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

/* Open the named file in read-write mode, creating the file
 * (mode -rw-------) if necessary, and apply for an advisory
 * exclusive lock.
 *
 * If successful, the function will return an open file handle.
 * Otherwise, it will return NULL with errno set to indicate
 * the error; EAGAIN if the file is already locked.
 *
 * To release the lock, just close the file.
 *
 * The lock will persist over an exec(). You will want to
 * release the lock before an exec() call.
 *
 * When the program exits (this or the program exec()'d!)
 * the lock will be automatically released.
 *
 * If you fork child processes, they will share the file handle.
 * The lock will be released when all processes have either closed
 * the handle, or exited; i.e. when there are no open handles left.
*/
FILE *fopen_exclusive(const char *const filename)
{
    int  descriptor, result, saved_errno;
    FILE *handle;

    if (!filename || !*filename) {
        /* Empty or NULL filename. */
        errno = EINVAL;
        return NULL;
    }

    /* Save errno, so we keep it unchanged if we are successful. */
    saved_errno = errno;

    /* Open the file. Create it (-rw-------) if necessary. */
    do {
        descriptor = open(filename, O_RDWR | O_NOCTTY | O_CREAT, 0600);
    } while (descriptor == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
    if (descriptor == -1)
        return NULL;

    /* Apply for an exclusive advisory lock. */
    do {
        result = flock(fd, LOCK_EX | LOCK_NB);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EACCES)
            saved_errno = EAGAIN;
        else
            saved_errno = errno;

        do {
            result = close(descriptor);
        } while (result == -1 && errno == EINTR);

        errno = saved_errno;
        return NULL;
    }

    /* Convert descriptor to actual file handle. */
    do {
        handle = fdopen(descriptor, "r+b");
    } while (!handle && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
    if (!handle) {
        saved_errno = errno;

        do {
            result = close(descriptor);
        } while (result == -1 && errno == EINTR);

        errno = saved_errno;
        return NULL;
    }

    /* Success! */
    errno = saved_errno;
    return handle;
}

Finally, there is a crash-proof way to update per-user files. It is very simple, too. When you wish to save a new file, you create a new file, for example with .new suffix. When done, you verify that there were no write errors, fdatasync() the file to make sure it hits the disk, and rename it over the old file. That way you will always have a complete file, never a partial one. Consider this helper function:
Code:

/* Flush the specified file to disk, then rename from saving to actual.
 * Returns 0 if successful, errno code otherwise.
 * The handle will be closed in all cases, and saving removed.
*/
int fsave(FILE *const handle, const char *const saving, const char *const actual)
{
    int result, saved_errno;

    if (!handle)
        return errno = EINVAL;

    if (!saving || !*saving || !actual || !*actual) {
        do {
            result = fclose(handle);
        } while (result == -1 && errno == EINTR);
        return errno = EINVAL;
    }

    saved_errno = errno;

    if (ferror(handle)) {
        saved_errno = EIO;
        goto fail_close;
    }

    /* Flush unwritten data to disk. */
    do {
        result = fflush(handle);
    } while (result == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
    if (result == -1) {
        saved_errno = EIO;
        goto fail_close;
    }

    /* Sync data; make sure it hits the storage device. */
    do {
        result = fdatasync(fileno(handle));
    }*while (result == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
    if (result == -1 && errno == EIO) {
        saved_errno = EIO;
        goto fail_close;
    }

    /* Close file. Check for delayed write errors. */
    do {
        result = fclose(handle);
    } while (result == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
    if (result == -1) {
        saved_errno = errno;
        goto fail_unlink;
    }

    /* File is successfully flushed to disk and closed. */

    /* Replace existing file with the new one. */
    do {
        result = rename(saving, actual);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        saved_errno = errno;
        goto fail_unlink;
    }

    /* Success! */
    errno = saved_errno;
    return 0;

fail_close:
    /* Close file. */
    do {
        result = fclose(handle);
    } while (result == -1 && errno == EINTR);

fail_unlink:
    /* Remove saving file. */
    do {
        result = unlink(saving);
    } while (result == -1 && errno == EINTR);

    /* Return failure. */
    return errno = saved_errno;
}

To use the above function -- which by the way is one of the rare examples where goto is not harmful; here it simplifies the error handling -- use something like
Code:

    FILE  *output;

    output = fopen("highscores.new", "wb");
    if (output) {
        /* We can save highscores. */

        /* Write the highscore content to output */
        fprintf(output, "Highscores:\n");
        /* ... */

        /* Try saving the highscore file. */
        if (fsave(output, "highscores.new", "highscores"))
            fprintf(stderr, "Warning: Cannot save highscores: %s.\n", strerror(errno));

    } else {
        /* We cannot save highscores. Do we need to exit?
          The user might be running this from a CD-ROM or something.
        */
    }

Note that users will prefer graceful handling of errors, so something like not being able to save the highscores should definitely not abort the program. (Some of us do use read-only media..)

If you had multiple simultaneous copies running, then you would have to use an unique suffix, preferably .shorthostname.pid (which is unique in a local network at any point in time; use gethostname() and getpid() to obtain the string and the PID number). The file replacement will be atomic, but in tight races some processes might see the previous one instead of the very latest one. All will see a complete file, though, never a mix.

Taiki 01-18-2012 11:09 AM

For the first part, you misunderstood me: I don't want to restart the soft but launch a new instance. Maybe use fork() + execlp() but it seems heavy and I don't know if they are REALLY independant.

For the second part, the file don't contain anything he's just there when the soft run a special part of the code. But the probleme is that I can't leave it and only check if it exists (in case of crash, the file won't be delete).
But the idea of "block" the file seems very interesting, I'll will use it and told you if it works :)

Nominal Animal 01-18-2012 11:42 AM

Quote:

Originally Posted by Taiki (Post 4577957)
For the first part, you misunderstood me: I don't want to restart the soft but launch a new instance.

We POSIX folks call that forking a child process, I think:

Code:

    pid_t  child, pid;

    child = fork();
    if (child == (pid_t)-1) {
        fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
        exit(1);
    }

    if (child) {
        /* Only the parent process will run this code */


        /* Reap the child before exiting */
        do {
            pid = waitpid(child, NULL, 0);
        } while (pid != child);

        exit(0);
       
    } else {
        /* Only the child process will run this code */

        exit(0);
    }

Normally, the parent should at some point reap the child process using one of the wait() functions. Otherwise, if the child process exits, it will stay in the process list listed as a zombie.

As you might guess, zombies are just children that haven't been reaped yet. It is mostly just a cosmetic issue, since the kernel will release all resources used by the process except for the PID and the exit status code.

Note that the child process will inherit all variables (and open file handles) from the parent process, but otherwise it will be a different process. The two will not see any changes they make to variables after the fork.

The situation may be easier to understand if you think of the child as having been magically started at the same time as the parent, has received all the same inputs and seen all the same files and times and everything as the parent process .. but that they diverge for the first time at the fork() call. Afterwards, they really are separate "programs".

You can obviously fork multiple times, but make sure you fork only from the parent process. Otherwise, you get an exponential number of child processes (since each child process will fork further children); this is called a fork bomb. When testing code that does forks, run ulimit -n numberofprocesses to limit the number of processes that session (shell or terminal) can create in parallel. A smallish number like 20 is usually enough for testing; I like to do my testing using another terminal or shell. (That way, even if you happen to trigger a fork bomb, it will be limited to 20 processes, and you can kill it from another shell, for example using killall -KILL myprogname.)

Any parent process can check on its children without waiting for the results, effectively just checking if anything notable has happened. Here is a full example:
Code:

    pid_t  pid;
    int    exitstatus;

    /* See if any child process has exited. */
    pid = waitpid((pid_t)-1, &exitstatus, WNOHANG);
    if (pid != (pid_t)0 && pid != (pid_t)-1) {
        /* Child process 'pid' has exited (or stopped or continued). */
        if (WIFEXITED(exitstatus)) {
            /* It exited with status WEXITSTATUS(exitstatus) */
        } elif (WIFSIGNALED(exitstatus)) {
            /* It died from signal WTERMSIG(exitstatus) */
        }
    }

The above snippet will not wait for any child to exit. If you don't care about the exit status stuff, you can just call waitpid((pid_t)-1,NULL,WNOHANG); in your code now and then, to reap any child processes that have exited. But, like I said, it is okay to leave zombies say during the game, only reaping them from the main menu, for example.

Oh, and if you leave child processes around when the program exits, that too is okay: init (process 1, the grandparent of all processes) will adopt the children, and reap them when they exit. So any zombies too will be eventually reaped.

Taiki 01-18-2012 12:40 PM

I knew fork() but thanks for precision ;)
It's not exactly a child process because both process will work on there own side w/o interact with the other.
If fork() allow each process to work without the other (one can be close without any impact on the other)
I'll check that, i'll be back with feedback soon :)

Nominal Animal 01-18-2012 01:32 PM

Quote:

Originally Posted by Taiki (Post 4578037)
If fork() allow each process to work without the other (one can be close without any impact on the other)

Forking two children and letting the parent process exit might be a better approach for you:
Code:

    pid_t pid, child1, child2;

    child1 = fork();
    if (child1 == (pid_t)-1) {
        fprintf(stderr, "Cannot fork first child process: %s.\n", strerror(errno));
        exit(1);
    }

    if (!child1) {

        /*
        * First child process.
        */

        exit(0);
    }

    child2 = fork();
    if (child2 == (pid_t)-1) {
        fprintf(stderr, "Cannot fork second child process: %s.\n", strerror(errno));
        exit(1);
    }

    if (!child2) {

        /*
        * Second child process.
        */

        exit(0);
    }

    /* This is the original parent process.
    * Since it exits now, the children will be reparented,
    * and reaped automatically when they exit.
    */
    exit(0);

The two are obviously siblings, only sharing open files and initial state.

The tricky thing about this is to note that the parent process will exit immediately. It might be a good idea to first put up a small splash window, then read the configuration and fork children, and then exit. That way the user will get visual feedback immediately, and is not left waiting for some input. Because the parent process will exit, even the task manager may not realize the children are still running, and you may not get the process icon on the task bar..

So, please test if this works for you. I haven't tested this using graphical user interfaces. Specifically, I don't know how the task bar behaves when there is a delay between the original parent process starting, and the child processes starting a new window. I suspect the task manager does not show the process is still starting up. (You can fix that, of course, by waiting for the children to fully start up before exit() in the parent process. You can do that easily by using two pipes, and closing the pipe when the child has started up. A blocking read on both by the parent will wait until the child closes the pipe.)

jthill 01-18-2012 03:26 PM

The only problem I can see that might trip up someone coming from Windows is leaving (and accumulating) open files. On linux anyway that's easy(-ish) to fix, a readdir loop on /proc/self/fd/, here's a really brutal demonstrator:
Code:

~/sandbox/49381$ cat opendir.cc
#include <cstdio>
#include <sys/types.h>
#include <dirent.h>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
using namespace std;
int main (int c, char **argv)
{
  puts(argv[0]);
  auto d = opendir ("/proc/self/fd/");
  vector<int> v;
  while (auto e = readdir (d))
    if (e->d_name[0] != '.' && atoi (e->d_name) > 2)
      v.push_back (atoi (e->d_name));
  for (auto fd:v)
    printf ("close(%d)==%d\n", fd, close (fd));
  if (argv[1])
    exit (0);
  puts ("restarting");
  char me[255];
  me[readlink("/proc/self/exe",me,255)]=0;
  execl("/proc/self/exe", me, "--restarting",0);
  perror("execl");
  return 0;
}
~/sandbox/49381$ make CXXFLAGS=--std=c++0x opendir
g++ --std=c++0x    opendir.cc  -o opendir
~/sandbox/49381$ ./opendir 4<opendir.cc
./opendir
close(3)==0
close(4)==0
restarting
/home/jthill/sandbox/49381/opendir
close(3)==0
~/sandbox/49381$


Nominal Animal 01-18-2012 06:44 PM

In C, one could use (untested)
Code:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>

static int close_all(void)
{
    size_t              fd_size = 0;
    size_t              fd_used = 0;
    int                *fd_list = NULL, *fd_temp;

    const struct dirent *ent;
    DIR                *dir;
    const char          *name;
    int                  saved_errno, fd, result, err;

    saved_errno = errno;

    do {
        dir = opendir("/proc/self/fd/");
    } while (!dir && errno != EINTR);
    if (!dir)
        return errno;

    while (1) {
        errno = 0;
        ent = readdir(dir);
        if (!ent)
            break;

        if (!(ent->d_name[0] >= '0' && ent->d_name[0] <= '9'))
            continue;

        fd = ent->d_name[0] - '0';
        name = (const char *)&(ent->d_name[1]);

        while (*name >= '0' && *name <= '9')
            fd = 10*fd + *(name++) - '0';

        if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO)
            continue;

        if (fd_used >= fd_size) {
            fd_size = (fd_used | (size_t)255) + (size_t)257;
            fd_temp = realloc(fd_list, sizeof(int) * fd_size);
            if (!fd_temp) {
                if (fd_list)
                    free(fd_list);
                do {
                    result = closedir(dir);
                } while (result == -1 && result == EINTR);
                return errno = ENOMEM;
            }
            fd_list = fd_temp;
        }

        fd_list[fd_used++] = fd;
    }

    if (errno) {
        err = errno;
        if (fd_list)
            free(fd_list);
        do {
            result = closedir(dir);
        } while (result == -1 && errno == EINTR);
        return errno = err;
    }

    fd = dirfd(dir);

    do {
        result = closedir(dir);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        err = errno;
        if (fd_list)
            free(fd_list);
        return errno = err;
    }

    err = 0;
    while (fd_used-->0)
        if (fd_list[fd_used] != fd) {
            do {
                result = close(fd_list[fd_used]);
            } while (result == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
            if (result == -1)
                err = errno;
        }

    if (fd_list)
        free(fd_list);

    if (err)
        return errno = err;

    errno = saved_errno;
    return 0;
}

This function has a bit different approach to closing the file descriptors. I think there is a possibility one might skip a descriptor when scanning /proc/self/fd/ and closing descriptors at the same time, although buffering may hide that unless you have a lot of open descriptors. It does happen when scanning directories and renaming files at the same time.

The function generates a dynamically allocated array of the open descriptors first, then tries to close them. It will keep the standard input, standard output, and standard error descriptors open. If successful -- all descriptors except the standard ones closed successfully -- it will return zero and keep errno unchanged, otherwise it will return errno (set to describe the error).

Taiki 01-27-2012 03:05 PM

Thanks for these answers :)
The file management seems to work fine.
For fork(), i made a big mistake and I can't check a new build after patched it so I'll be back later with news.

Thanks so much for your answers

Taiki


All times are GMT -5. The time now is 01:49 PM.