Search code examples
clinuxsystemdxlib

Program doesn't work when configured as a systemd service


I'm trying to write a program that can run as a service on Linux. It mainly sends and receives data with a server, receives a .so file sent by the server, and uses dlopen() to execute the functions inside.

It works when I manually enable it through the sudo command. I tried to write a systemd file to make it boot automatically, however that doesn't work. For example, using the screen capture function of Xlib gets an all black picture.

I've tried to redirect the debugging information to the file, but I can only get the debugging information of the main program; I can't get the operation information of the plug-in. I am sure that the function of the plug-in has been executed, but there are errors in the execution process.

The result of its program is that the screenshot content is completely black, and the connection is automatically disconnected after client sending the screenshot. It may be that a segment fault is encountered at the client, but this has not happened when I start the program manually

Any ideas?

This is my service file.

[Unit]
Description=just for test

[Service]
Type=forking
ExecStart=/mnt/main.debug
Environment=DBUS_SESSION_BUS_ADDRESS,DISPLAY,WAYLAND_DISPLAY(new added)

[Install]
WantedBy=multi-user.target

I tried to start service by systemctl start ps-hak.service,the error message is this:

a@ubuntu:~$ sudo systemctl daemon-reload
a@ubuntu:~$ sudo systemctl start ps-hak.service
^[OAJob for ps-hak.service failed because a fatal signal was delivered to the control process. See "systemctl status ps-hak.service" and "journalctl -xe" for details.
a@ubuntu:~$ sudo systemctl start ps-hak.service^C
a@ubuntu:~$ systemctl status ps-hak.service
● ps-hak.service - just for test
   Loaded: loaded (/etc/systemd/system/ps-hak.service; disabled; vendor preset: 
   Active: failed (Result: signal) since Tue 2021-08-03 19:20:18 PDT; 18s ago
  Process: 2662 ExecStart=/mnt/main.debug (code=killed, signal=SEGV)

Aug 03 19:20:08 ubuntu systemd[1]: Starting just for test...
Aug 03 19:20:08 ubuntu main.debug[2662]: 
Aug 03 19:20:08 ubuntu main.debug[2662]: [DEBUG|main.cpp:40 (main)]: WAYLAND_DIS
Aug 03 19:20:08 ubuntu main.debug[2662]: DISPLAY=(null)
Aug 03 19:20:08 ubuntu main.debug[2662]: 
Aug 03 19:20:18 ubuntu systemd[1]: ps-hak.service: Control process exited, code=
Aug 03 19:20:18 ubuntu systemd[1]: Failed to start just for test.
Aug 03 19:20:18 ubuntu systemd[1]: ps-hak.service: Unit entered failed state.
Aug 03 19:20:18 ubuntu systemd[1]: ps-hak.service: Failed with result 'signal'.

I can only sure that the environment of the program is wrong, i have tried to add Environment=DBUS_SESSION_BUS_ADDRESS,DISPLAY,WAYLAND_DISPLAY, but it doesn't make sense.

I made a small program for debugging SYSTEMd. The code is as follows. When I manually enable it, it can normally generate screenshots, but after I register as a service service, it cannot work normally. There is even no file generation in the specified directory, but there is no error message in systemctl status test. Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdio>
#include <sys/time.h>
#include <X11/Xlib.h>

#pragma pack (1)
typedef struct BitMAPFILEHEADER 
{
    short    bfType;
    int    bfSize;
    short    bfReserved1;
    short    bfReserved2;
    int   bfOffBits;
} BITMAPFILEHEADER;

typedef struct BitMAPINFOHEADER
{
    int  biSize;
    int   biWidth;
    int   biHeight;
    short   biPlanes;
    short   biBitCount;
    int  biCompression;
    int  biSizeImage;
    int   biXPelsPerMeter;
    int   biYPelsPerMeter;
    int  biClrUsed;
    int  biClrImportant;
} BITMAPINFOHEADER;

void saveXImageToBitmap(const char* filename,XImage *pImage)
{
    BITMAPFILEHEADER bmpFileHeader;
    BITMAPINFOHEADER bmpInfoHeader;
    FILE *fp;
    memset(&bmpFileHeader, 0, sizeof(BITMAPFILEHEADER));
    memset(&bmpInfoHeader, 0, sizeof(BITMAPINFOHEADER));
    bmpFileHeader.bfType = 0x4D42;
    bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bmpFileHeader.bfReserved1 = 0;
    bmpFileHeader.bfReserved2 = 0;
    int biBitCount =32;
    int dwBmpSize = ((pImage->width * biBitCount + 31) / 32) * 4 * pImage->height;

    // DEBUG("size of short:%d\r\n",(int)sizeof(short));
    // DEBUG("size of int:%d\r\n",(int)sizeof(int));
    // DEBUG("size of long:%d\r\n",(int)sizeof(long));
    // DEBUG("dwBmpSize:%d\r\n",(int)dwBmpSize);
    // DEBUG("BITMAPFILEHEADER:%d\r\n",(int)sizeof(BITMAPFILEHEADER));
    // DEBUG("BITMAPINFOHEADER:%d\r\n",(int)sizeof(BITMAPINFOHEADER));
    bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +  dwBmpSize;

    bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfoHeader.biWidth = pImage->width;
    bmpInfoHeader.biHeight = pImage->height;
    bmpInfoHeader.biHeight = - bmpInfoHeader.biHeight;  // important,otherwise the pic will be reversed
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biBitCount = biBitCount;
    bmpInfoHeader.biSizeImage = 0;
    bmpInfoHeader.biCompression = 0;
    bmpInfoHeader.biXPelsPerMeter = 0;
    bmpInfoHeader.biYPelsPerMeter = 0;
    bmpInfoHeader.biClrUsed = 0;
    bmpInfoHeader.biClrImportant = 0;

    fp = fopen(filename,"wb");

    if(fp == NULL)
        return;

    fwrite(&bmpFileHeader, sizeof(bmpFileHeader), 1, fp);
    fwrite(&bmpInfoHeader, sizeof(bmpInfoHeader), 1, fp);
    fwrite(pImage->data, dwBmpSize, 1, fp);
    fclose(fp);
}

int CaptureDesktop(const char* filename)
{
    Window desktop;
    Display* dsp;
    XImage* img;

    int screen_width;
    int screen_height;
    dsp = XOpenDisplay(NULL);/* Connect to a local display */
    if(NULL==dsp)
    {
        // DEBUG("%s,%s\n","CaptureDesktop","Cannot connect to local display");
        return 0;
    }
    desktop = RootWindow(dsp,0);/* Refer to the root window */
    if(0==desktop)
    {
        // DEBUG("%s,%s\n","CaptureDesktop","cannot get root window");
        return 0;
    }

    /* Retrive the width and the height of the screen */
    screen_width = DisplayWidth(dsp,0);
    screen_height = DisplayHeight(dsp,0);
    // DEBUG("%d %d\n",screen_width,screen_height);

    img = XGetImage(dsp,desktop,0,0,screen_width,screen_height,~0,ZPixmap);

    saveXImageToBitmap(filename,img);
   
    XCloseDisplay(dsp);
    return 1;
}

int main()
{
    CaptureDesktop("/home/a/out.bmp");
    return 0;
}

Operation process:

a@ubuntu:~$ sudo systemctl daemon-reload
a@ubuntu:~$ sudo systemctl enable test
a@ubuntu:~$ sudo systemctl start test
a@ubuntu:~$ sudo systemctl status test
● test.service - just for test
   Loaded: loaded (/etc/systemd/system/test.service; enabled; vendor preset: ena
   Active: inactive (dead) since Tue 2021-08-03 20:17:57 PDT; 5s ago
  Process: 2501 ExecStart=/mnt/test (code=exited, status=0/SUCCESS)

Aug 03 20:17:57 ubuntu systemd[1]: Starting just for test...
Aug 03 20:17:57 ubuntu systemd[1]: Started just for test.
lines 1-7/7 (END)

I can confirm that there is a problem with the environment variable of the service started by systemd, but the applet should work normally without environment variable, but as a service, it does not work normally. I think the main problem may not be the screenshot function, because other functions have similar results.


Solution

  • You can't call Xlib from that kind of service because it is not attached to a user session. The only kind of service that could reasonably use Xlib is a user-login service.

    If you check the envrionment variables, DISPLAY and XAUTHORITY will be blank. That's the immediate failure reason. Filling them both with the "right" values will allow it to work. If you manage to find them, setenv(3) will set them and Xlib will pick them up. You could play tricks as root and try to track down the stuff for the current X session, but that's a vector-scalar problem. I run multiple X sessions. Which one's the right one?