ubuntuusers.de

C-Programm auch bei Shutdown geordnet beenden

Status: Gelöst | Ubuntu-Version: Nicht spezifiziert
Antworten |

gypakk

Anmeldungsdatum:
7. März 2008

Beiträge: 68

Hallo!

Wie kann mein C-Programm die Information erhalten, dass der Anwender den PC runterfahren will? Ich würde gerne eine Prozedur schreiben, die diverse Abschlussarbeiten erledigt. Natürlich wär es dann auch praktisch, wenn das C-Programm das Runterfahren um ein paar Sekunden verzögern könnte...

Auf ein normales "kill" reagiere ich per signal() und kann dann alles Notwendige erledigen, bevor das Programm endet. Aber beim Shutdown scheint das nicht zu klappen - jedenfalls hab ich es nicht hinbekommen.

Weiß jemand Rat?

adun Team-Icon

Avatar von adun

Anmeldungsdatum:
29. März 2005

Beiträge: 8606

Was meinst du mit normalen "kill", du musst auf 'TERM' und 'QUIT' reagieren. Kill ist abschießen, da hast du schon keine "Zeit" mehr.

Kinch

Anmeldungsdatum:
6. Oktober 2007

Beiträge: 1261

gypakk schrieb:

Auf ein normales "kill" reagiere ich per signal() und kann dann alles Notwendige erledigen, bevor das Programm endet. Aber beim Shutdown scheint das nicht zu klappen - jedenfalls hab ich es nicht hinbekommen.

Ich glaube das ist so: Zuerst wird an alle Prozesse SIGTERM gesendet. Auf das musst du halt reagieren, und zwar in einem vorgebenem Zeitfenster. Ich glaube es waren so 5 Sekunden. Anschließend wird an alle Prozesse ein SIGKILL gesendet und das kann ein Prozess nicht abfangen (zum Glück).

Den Shutdown verzögern kannst du nicht so ohne Weiteres, du müsstest da schon etwas tricksen, denke ich. Was mir einfiele: Vor dem Shutdown wird noch /etc/shutdown aufgerufen. Da kannst du Shell-Code palzieren der auf das Ende deines Prozesses wartet.

gypakk

(Themenstarter)

Anmeldungsdatum:
7. März 2008

Beiträge: 68

Hallo und danke für eure Antworten!

Bisher reagierte ich nur auf TERM, ok, ab jetzt auch auf QUIT. Allerdings komm ich gar nicht mehr zum Reagieren, wenn ich bei Ubuntu auf "Ausschalten" klicke. Dann beginnt nämlich blitzartig der Shutdown; das Programm wird innerhalb von Millisekunden abgebrochen.

Der Trick mit /etc/shutdown würde wahrscheinlich funktionieren, aber mein Programm hat keine root-Rechte.

Habt ihr noch eine Idee?

adun: Ein "normales" kill schickt TERM, auf das kann ich gut reagieren und das Programmende um einige Sekunden verzögern.

Lysander

Avatar von Lysander

Anmeldungsdatum:
30. Juli 2008

Beiträge: 2669

Wohnort: Hamburg

Zeig doch mal Deinen Code! Du musst ja irgend wie eine Funktion haben die Du an atexit() übergibst. In dieser kannst Du dann ja z.B. mit switch dispatchen.

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Falls noch nicht geschehen, vielleicht einfach mal alle Signale mitloggen. So in der Art:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>

#define LOGPATH "signals.log"

char *timenow()
{
    time_t now;
    struct tm *t;
    static char timestr[9];

    time(&now);
    t = gmtime(&now);
    sprintf(timestr, "%02d:%02d:%02d",
           (t->tm_hour)%24, t->tm_min, t->tm_sec);
    return timestr;
}

void sigwrite(int signum)
{
    FILE *logfile;

    logfile = fopen(LOGPATH, "a");
    fprintf(logfile, "signal %d at %s\n", signum, timenow());
    fclose(logfile);
}

int isfile(const char *path)
{
    struct stat result;

    stat(path, &result);
    return S_ISREG(result.st_mode);
}

int main()
{
    struct sigaction sa;
    int i;

    if (isfile(LOGPATH))
        remove(LOGPATH);

    sa.sa_handler = sigwrite;

    for (i = 0; i < NSIG; i++)
        sigaction(i, &sa, NULL);

    sleep(1000);
    return 0;
}

Ergibt dann bei mir nach einem Klick auf "Neustart" in Ubuntu:

1
2
signal 1 at 08:19:00
signal 18 at 08:19:00

Und anschließend wird ja offensichtlich SIGKILL gesendet, welches du ohnehin nicht abfangen kannst.

Man könnte noch mittels Interval in etwa rauskriegen, wie lange Ubuntu das Programm noch bis zum SIGKILL leben lässt. Aber ich muss jetzt zur Arbeit... 😉

gypakk

(Themenstarter)

Anmeldungsdatum:
7. März 2008

Beiträge: 68

Lysander: Mit atexit() hab ich es schon versucht. Die so registrierte Exit-Prozedur wird beim Shutdown nicht aufgerufen. ☹

snafu1: Wenn ich das richtig sehe, kommen bei dir beide Signale (1 und 18) zur gleichen Zeit. Anscheinend besteht bei dir das gleiche Problem. Ich hab nun auch mal alles abgefangen und mitgeloggt:

In main():

1
2
3
4
5
6
    sa.sa_handler = NULL;
    memset(&sa.sa_mask,0,sizeof(sa.sa_mask));
    sa.sa_flags= SA_SIGINFO;
    sa.sa_sigaction = endall;
    for(i= 0;i<NSIG;i++)
        sigaction(i,&sa,NULL);

In endall():

1
2
3
4
5
6
7
void endall(int signo,siginfo_t *info,void *context) {
    char t[200];
    
    sprintf(t,"Received: signal %i, code %i from process %i.\n",
        info->si_signo,info->si_code,info->si_pid);
    (hier t[] in eine datei loggen - mit fflush() )
    }  // end   endall()

Dummerweise bekomme ich gar keine Zeile geloggt beim Runterfahren. Nur wenn ich die Anwendung per kill beende, entsteht folgende Zeile:

Received: signal 15, code 0 from process 2273.

evilhomer

Anmeldungsdatum:
22. Mai 2009

Beiträge: 41

Hi,

hier mal ein Beispiel Code von mir. So mache ich es, hoffe es hilft weiter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include < stdio.h >
#include < signal.h >
#include < unistd.h >
#include < stdlib.h >

bool b = true;

int iSignals[] = {
        SIGABRT,        // (Signal Abort) Abnormal termination, such as is initiated by the abort function.
        SIGFPE,         // (Signal Floating-Point Exception) Erroneous arithmetic operation, such as zero
                        // divide or an operation resulting in overflow (not necessarily with a floating-point operation).
        SIGILL,         // (Signal Illegal Instruction) Invalid function image, such as an illegal instruction.
                        // This is generally due to a corruption in the code or to an attempt to execute data.
        SIGINT,         // (Signal Interrupt) Interactive attention signal. Generally generated by the application user.
        SIGSEGV,        // (Signal Segmentation Violation) Invalid access to storage: When a program tries to read or
                        // write outside the memory it is allocated for it.
        SIGTERM,        // (Signal Terminate) Termination request sent to program.
        0
        };



void hSignal(int i){
        printf("Killed by signal %d\n",i);
        exit(0); // Ohne das Exit kann es sein das sicht das Programm nicht beendet
}

int main(int argc, char **argv){
        int i = 0;
        while(iSignals[i++] > 0){
                signal (iSignals[i],hSignal);
        }

        while(b) {
                printf(".");
                fflush(stdout);
                sleep(1);
        }
        printf("\n");
        return 0;
}

gypakk

(Themenstarter)

Anmeldungsdatum:
7. März 2008

Beiträge: 68

evilhomer schrieb:

hier mal ein Beispiel Code von mir. So mache ich es, hoffe es hilft weiter.

Hallo und danke für den Code! Das heißt, du reagierst auf bestimmte Signale und beendest dann ggf. das Programm in geordneter Weise. Soweit komm ich noch mit. Was aber passiert beim Shutdown? Kannst du den irgendwie erkennen und ggf. um ein oder zwei Sekunden verzögern, bis sich das Programm sauber beendet hat?

evilhomer

Anmeldungsdatum:
22. Mai 2009

Beiträge: 41

Hi,

also ich habe eben selbst erst einmal schauen müssen.

Also wenn das Programm OHNE root Rechte läuft, scheint es nicht mitzubekommen wenn es gekillt wird. Wenn das Programm aber mit root Rechten läuft, kannst du SIGTERM abgreifen.

Ich frag mal zu blöd - warum schreibst du nicht einfach ein Start/Stop Script das du in die init.d schmeisst und über das entsprechend rcX.d startest und stopst?

gypakk

(Themenstarter)

Anmeldungsdatum:
7. März 2008

Beiträge: 68

evilhomer schrieb:

Wenn das Programm aber mit root Rechten läuft, kannst du SIGTERM abgreifen.

Ah, danke. Kann das Programm dann den Shutdown um ein paar Sekunden verzögern?

Grundsätzlich bringt mir der Weg aber vermutlich auch nichts, weil es Ziel ist, das Programm "normal", also aus Sicherheitsgründen nicht mit root-Rechten laufen zu lassen. ☹

Ich frag mal zu blöd - warum schreibst du nicht einfach ein Start/Stop Script das du in die init.d schmeisst und über das entsprechend rcX.d startest und stopst?

Weil das Programm einfach so laufen soll, ohne dass es installiert wird. Ja, ich weiß, seltsame Anforderungen, aber in diesem Fall geht es nicht anders.

evilhomer

Anmeldungsdatum:
22. Mai 2009

Beiträge: 41

Ah ok - ich verstehe.

Hmmm kann das Programm denn als root generell gestartet werden?

Wenn ja könntest du mit setuid(int) das Programm als quasi anderer User laufen lassen.

1
2
3
4
5
6
#include <unistd.h>
#include <sys/types.h>

...
setuid(1000);
...

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Hm, ich weiß, dass ich eine Warnmeldung bekomme, wenn ich z.B. im Gedit noch eine veränderte ungepeicherte Datei offen habe. Der Prozess des Herunterfahrens/Neustartens wird dort also angehalten. Ich vermute aber, dass dies etwas GNOME-spezifisches ist und nicht direkt etwas mit /sbin/reboot zu tun hat. Letzteres könntest du meines Wissens nur über Eingriffe in die Runlevel steuern, was Root-Rechte braucht und was du ja offenbar eh nicht willst. Kannst du vielleicht den Hintergrund für deine Anforderung erklären?

adun Team-Icon

Avatar von adun

Anmeldungsdatum:
29. März 2005

Beiträge: 8606

Über DBUS wird der Shutdown angesagt und "jeder" kann erstmal Einspruch einlegen. Allerdings würde ich nicht extra DBUS einbinden für sowas.

Ich vermute das Problem liegt woanders, kann es sein, dass du dein Programm an ein Terminal bindest?

Edit: So habs mal ausprobiert und denke mal, dass es das sein wird.

ruby -e "trap('SIGTERM') {puts 'Ich bin unsterblich, muhahaa'}; sleep 100"
killall ruby
-> Ich bin unsterblich, muhahaa
killall -9 ruby
fish: Job 2, 'ruby' durch Signal SIGKILL (Erzwungene Beendigung) beendet
bash
ruby -e "trap('SIGTERM') {puts 'Ich bin unsterblich, muhahaa'}; sleep 100"
killall ruby
-> Ich bin unsterblich, muhahaa
killall bash
(passiert nix)
killall -9 bash
fish: Job 3, 'bash' durch Signal SIGKILL (Erzwungene Beendigung) beendet 

Kein Mux von Ruby im letzten Aufruf (bei SIGQUT würde er beim Beenden auch noch Quit ausgeben), also hat er nie SIGTERM oder SIGQUT bekommen.

gypakk

(Themenstarter)

Anmeldungsdatum:
7. März 2008

Beiträge: 68

evilhomer: Für setuid bräuchte ich dann auch wieder root-Rechte - soweit ich weiß.

adun: Danke! Ich versuche aber erstmal, mit Ubuntu-Bordmitteln auszukommen.

snafu1: Das Ganze ist für ein Internet-Einwahlprogramm gedacht (mobiles Internet). Damit es jeder User möglichst einfach verwenden kann, versuche ich, das Programm ohne besondere Anforderungen an die Benutzerrechte zu entwickeln. Gleichzeitig minimiert das natürlich auch verschiedene Sicherheitsrisiken.

Dein Hinweis auf gedit hat mich weitergebracht! Jedenfalls hatte ich dann den Mut, noch weiter in den Tiefen von Gnome zu graben, denn wenn gedit so etwas macht, muss es irgendwo dafür eine Funktion geben. Und siehe da - ich wurde fündig! ☺

Die Lösung liegt im Paket libgnomeui. Ich kopier euch einfach ein Stück Beispielcode:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <libgnomeui/libgnomeui.h>

static gboolean shutdownproc(GnomeClient* gc,gint arg1,
    GnomeSaveStyle arg2,gboolean shutd,GnomeInteractStyle ias,
    gboolean arg5,gpointer par) {
    if(!shutd || ias!=GNOME_INTERACT_ANY)
return TRUE;

    -- hier den Code einfuegen, der die Abschlussarbeiten durchfuehrt --

return TRUE;
    }  // end   shutdownproc()


-- irgendwo am Anfang von main() --

    // detect shutdown event
    GnomeClient* gc;

    gnome_program_init("my_program","my_version",LIBGNOMEUI_MODULE,argc,argv,NULL);
    gc= gnome_master_client();
    g_signal_connect(gc,"save-yourself",G_CALLBACK(shutdownproc),NULL);

Danke an euch alle!

Antworten |