Linux server program standardization

Posted by jrtaylor on Thu, 23 Dec 2021 22:50:23 +0100

Linux Log System


The rsyslogd daemon can receive both user process output logs and kernel logs. The user process generates the system log by calling the syslog function. This function outputs the log to a UNIX local domain socket type (AF_UNIX) file / dev/log, and rsyslogd listens to the file to obtain the output of the user process. The kernel log is managed by another daemon rklogd on the old system, and rsyslogd uses additional modules to achieve the same function. The kernel log is printed to the ring buffer of the kernel by functions such as printk. The contents of the ring cache are mapped directly to the / proc/kmsg file. Rsyslogd obtains the kernel log by reading the file.

After receiving logs from user processes or kernel inputs, the rsyslogd daemon will output them to some specific log files. By default, debugging information is saved to / varlog/debug file, general information is saved to / var/log/messages file, and kernel messages are saved to / var / log / Kerm Log file. However, the specific distribution of log information can be set in the configuration file of rsyslogd. The main configuration file of rsyslogd is / etc / syslog Conf, the main items that can be set include: kernel log input path, whether to receive UDP log and its listening port (514 by default, see / etc/services file), whether to receive TCP log and its listening port, the permissions of log file, and which sub configuration files (such as / etc / rsyslog. D / *. CONF). The sub configuration file of rsyslogd specifies the target storage file of various logs.

The application uses the syslog function to communicate with the rsyslogd daemon. The syslog function is defined as follows:

#ifdef __va_arg_pack
__fortify_function void
syslog (int __pri, const char *__fmt, ...)
{
  __syslog_chk (__pri, __USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
}
#elif !defined __cplusplus
# define syslog(pri, ...) \
  __syslog_chk (pri, __USE_FORTIFY_LEVEL - 1, __VA_ARGS__)
#endif

The function uses variable parameters (the second parameter message and the third parameter) to structure the output. The priority parameter is the bitwise or of the so-called facility value and log level. The default value of facility value is LOG USER, and our discussion below is limited to this kind of facility value. Log levels are as follows:

#define	LOG_EMERG	0	/* system is unusable */
#define	LOG_ALERT	1	/* action must be taken immediately */
#define	LOG_CRIT	2	/* critical conditions */
#define	LOG_ERR		3	/* error conditions */
#define	LOG_WARNING	4	/* warning conditions */
#define	LOG_NOTICE	5	/* normal but significant condition */
#define	LOG_INFO	6	/* informational */
#define	LOG_DEBUG	7	/* debug-level messages */

The following function can change the default output mode of syslog to further structure the log content:

/* Open connection to system logger.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern void openlog (const char *__ident, int __option, int __facility);

The string specified by the ident parameter will be added after the date and time of the log message, which is usually set to the name of the program. The logopt parameter configures the behavior of subsequent syslog calls. It can take the bitwise OR bitwise of the following values:

/*
 * Option flags for openlog.
 *
 * LOG_ODELAY no longer does anything.
 * LOG_NDELAY is the inverse of what it used to be.
 */
#define	LOG_PID		0x01	/* log the pid with each message */
#define	LOG_CONS	0x02	/* log on the console if errors in sending */
#define	LOG_ODELAY	0x04	/* delay open until first syslog() (default) */
#define	LOG_NDELAY	0x08	/* don't delay open */
#define	LOG_NOWAIT	0x10	/* don't wait for console forks: DEPRECATED */
#define	LOG_PERROR	0x20	/* log to stderr as well */

The facility parameter can be used to modify the default facility value in the syslog function.

In addition, log filtering is also important. The program may need to output a lot of debugging information in the development stage, and we need to close these debugging information after release. The solution to this problem is not to delete the debugging code after the program is released (because it may be needed in the future), but to simply set the log mask so that the log information with a log level greater than the log mask is ignored by the system. The following function is used to set the log mask of syslog:

/* Set the log mask level.  */
extern int setlogmask (int __mask) __THROW;

The maskpri parameter specifies the log mask value. This function always succeeds and returns the previous log mask value of the calling process.
Finally, don't forget to turn off the logging function using the following function:

/* Close descriptor used to write to system logger.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern void closelog (void);

User information

User information is very important for the security of server programs. For example, most servers must be started as root, but cannot run as root. The following set of functions can obtain and set the real user ID(UID), valid user ID (EUID), real group ID (GID) and valid group ID (EGID) of the current process:

#include <sys/types.h>
#include <unistd.h>
uid_ t getuid();/* Get real user ID */ 
uid_ t geteuid() ;/*Get valid user ID */
gid_ t getgid() ;/*Get real group ID */
gid_ t getegid() ;/*Get valid group ID */
int setuid( uid_t uid ) ;/*Set real user ID */
int seteuid( uid_t uid ) ;/*Set valid user ID */
int setgid( gid_t gid );/*Set real group ID */
int setegid( gid_t gid );/*Set valid group ID */

It should be noted that a process has two user IDs: UID and EUID. The purpose of EUID Is to facilitate resource access: it enables the user running the program to have the permissions of the valid user of the program. For example, the su program can be used by any user to modify their account information, but when modifying the account, the su program has to access the / etc/passwd file, which requires root permission. So how can an su program started as an ordinary user access the / etc/passwd file? The trick Is in EUID. Using the Is command, you can see that the owner of the su program Is root, and it Is set with the set user ID flag. This flag indicates that when any ordinary user runs su program, its valid user Is the owner root of the program Then, according to the meaning of a valid user, any ordinary user running the su program can access the / epassd file. Processes with root as the valid user are called privileged processes. The meaning of EGID Is similar to that of EUID: provide valid group permissions to the group users running the target program. The difference between the UID and EUID of the process. The UID of the process Is the ID of the user who started the program, while the EUID Is the ID of the root account (file owner).

Inter process relationship

Process group

Under Linux, each process belongs to a process group, so they also have process group ID(PGID) in addition to PID information. We can use the following function to obtain the PGID of the specified process:

extern __pid_t getpgid (__pid_t __pid) __THROW;

When the function succeeds, it returns the PGID of the process group to which the process PID belongs. If it fails, it returns - 1 and sets that each process group has a leader process with the same PGID and PID. The process group will exist until all processes in it exit or join other process groups.

The following function is used to set PGID:

/* Set the process group ID of the process matching PID to PGID.
   If PID is zero, the current process's process group ID is set.
   If PGID is zero, the process ID of the process is used.  */
extern int setpgid (__pid_t __pid, __pid_t __pgid) __THROW;

This function sets the pgid of the process with PID as pgid. If the PID and pgid are the same, the process specified by the PID will be set as the process group leader; If the PID is 0, it means that the pgid of the current process is set to pgid; If pgid is 0, PID is used as the target pgid. The setpgid function returns 0 on success and - 1 on failure, and sets errno.

A process can only set the PGID of itself or its child processes. Moreover, when the child process calls the exec series of functions, we can no longer set PGID on it in the parent process.

conversation

Some associated process groups will form - a session. The following function is used to create - a session:

/* Create a new session with the calling process as its leader.
   The process group IDs of the session and the calling process
   are set to the process ID of the calling process, which is returned.  */
extern __pid_t setsid (void) __THROW;

This function cannot be called by the leader process of the process group, otherwise an error will be generated. For non group leader processes, calling this function not only creates a new session, but also has the following additional effects:

  • The calling process becomes the leader of the session, and the process is the only - member of the new session.
  • Create - a process group whose PGID is the PID of the calling process, and the calling process becomes the leader of the group.
  • The calling process will throw away the terminal (if any).

This function returns the PGID of the new process group when it succeeds. If it fails, it returns - 1 and sets error.

The Linux process does not provide the so-called session ID(SID), but the Linux system considers it equal to the PGID of the process group where the session leader is located, and provides the following numbers to read the SID:

/* Return the session ID of the given process.  */
extern __pid_t getsid (__pid_t __pid) __THROW;

View the relationship between processes with ps command

   PID    PPID    PGID     SID COMMAND
  53998   51650   53998   53998 bash
  54717   53998   54717   53998 ps
  54718   53998   54717   53998 less

We execute the ps and less commands under the bash shell, so the parent process of the ps and less commands is the bash command, which can be seen from the PPID (parent process PID) column. These three commands create one session (SID 53998) and two process groups (PGID 53998 and 53998 respectively). The bash command has the same PID, PGID, and Sid, and is obviously the leader of both the session and the group 53998. The ps command is the leader of group 54717 because its PID is also 54717. The figure describes the relationship between the three.

System resource limitation

Programs running on Linux will be affected by resource constraints, such as physical device restrictions (CPU quantity, memory quantity, etc.), system policy restrictions (CPU time, etc.), and specific implementation restrictions (such as the maximum length of file name). Linux system resource limits can be read and set through the following - pair of functions:

#include<sys/resource.h>

typedef int __rlimit_resource_t;

/* Put the soft and hard limits for RESOURCE in *RLIMITS.
   Returns 0 if successful, -1 if not (and sets errno).  */
extern int getrlimit (__rlimit_resource_t __resource, struct rlimit *__rlimits) __THROW;

__ The rlimits parameter is a pointer to the rlimit structure type. The definition of the rlimit structure is as follows:

struct rlimit
 {
   /* The current (soft) limit.  */
   rlim_t rlim_cur;
   /* The hard limit.  */
   rlim_t rlim_max;
 };

rlim_t is an integer type that describes the resource level. rlim_cur member specifies the soft limit of the resource, Rlim_ The max member specifies the hard limit of the resource. Soft limit is - a recommended limit that should not be exceeded. If exceeded, the system may send a signal to the process to terminate its operation. For example, when the CPU time of a process exceeds its soft limit, the system will send a SIGXCPU signal to the process; when the file size exceeds its soft limit, the system will send a SIGXFSZ signal to the process. The hard limit is generally the upper limit of the soft limit. Ordinary programs can reduce the hard limit, while only programs running as root can increase the hard limit. In addition, we can use the ulimit command to modify the resource limit (soft limit or / and hard limit) in the current shell environment. This modification will be effective for all subsequent programs started by the shell. We can also change the system soft limit and hard limit by modifying the configuration file, and this modification is permanent.

The resource parameter specifies the resource restriction type. Table 7-1 lists some important types of resource constraints.

setrlimit and getrlimit return 0 when they succeed, return - 1 if they fail, and set errno.

Change working directory and root directory

The number of methods to obtain the current working directory of the process and change the working directory of the process are:

/* Get the pathname of the current working directory,
   and put it in SIZE bytes of BUF.  Returns NULL if the
   directory couldn't be determined or SIZE was too small.
   If successful, returns BUF.  In GNU, if BUF is NULL,
   an array is allocated with `malloc'; the array is SIZE
   bytes long, unless SIZE == 0, in which case it is as
   big as necessary.  */
extern char *getcwd (char *__buf, size_t __size) __THROW __wur;

/* Change the process's working directory to PATH.  */
extern int chdir (const char *__path) __THROW __nonnull ((1)) __wur;

The memory pointed to by the buf parameter is used to store the absolute pathname of the current working directory of the process, and its size is specified by the size parameter. If the length of the absolute path of the current working directory (plus a NULL closing character "\ 0") exceeds size, getewd will return NULL and set erno to ERANGE If buf is NULL and the size is not 0, getcwd may dynamically allocate memory internally using malloc and store the current working directory of the process in it. If this is the case, we must free the memory created internally by getcwd ourselves. When the getcwd function succeeds, it returns -- a pointer to the target storage area (the cache area pointed to by buf or the cache area dynamically created by getcwd internally). If it fails, it returns NULL and sets errno.

The path parameter of the chdir function specifies the target directory to switch to. It returns 0 on success and - 1 on failure
Set errno.

The function to change the process root directory is chroot, which is defined as follows:

/* Make PATH be the root directory (the starting point for absolute paths).
   This call is restricted to the super-user.  */
extern int chroot (const char *__path) __THROW __nonnull ((1)) __wur;

The path parameter specifies the target root directory to switch to. It returns 0 on success and - 1 on failure and sets ermo. Chroot does not change the current working directory of the process, so after calling chroot, we still need to use chdir("/") to switch the working directory to the new root directory. After changing the root directory of the process, the program may not be able to access files (and directories) like / dev because these files (and directories) are not under the new root directory. However, fortunately, after calling chroot, the file descriptors originally opened by the process still take effect, so we can use these previously opened file descriptors to access files (and directories) that cannot be accessed directly after calling chroot, especially some log files. In addition, only privileged processes can change the root directory.

Post processing of service procedures

/* Put the program in the background, and dissociate from the controlling
   terminal.  If NOCHDIR is zero, do `chdir ("/")'.  If NOCLOSE is zero,
   redirects stdin, stdout, and stderr to /dev/null.  */
extern int daemon (int __nochdir, int __noclose) __THROW __wur;

The nochdir parameter is used to specify whether to change the working directory. If 0 is passed to it, the working directory will be set to "1" (root directory), otherwise the current working directory will continue to be used. When the noclose parameter is 0, the standard input, standard output and standard error output are redirected to the / dev/null file. Otherwise, the original device is still used. The function returns 0 on success and - 1 on failure and sets errno.

Topics: Linux socket server