Fancontrol: Unterschied zwischen den Versionen

Aus wiki.frank-wulf.de
Zur Navigation springen Zur Suche springen
 
(5 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 3: Zeile 3:
<syntaxhighlight lang="c" line="1">
<syntaxhighlight lang="c" line="1">
// Author:  Frank Wulf
// Author:  Frank Wulf
// Version: 2.3 (2020-12-23)
// Version: 2.3 (2020-12-30)
//
//
// This program monitors temperatures of both system and hard drives and
// This program monitors temperatures of both system and hard drives and
Zeile 38: Zeile 38:


enum {
enum {
        BUFSIZE = 256,
    SYS = 0,
        FAN_MODE = 1,
    HDD = 1,
        MAX_STEP = 10,
    BUFSIZE = 256,
        MAX_SENSOR_LENGTH = 50,
    FAN_MODE = 1,
        SMART_BUFFER_SIZE = 512,
    MAX_STEP = 10,
        SMART_SENSE_BUFFER_SIZE = 32,
    MAX_SENSOR_LENGTH = 50,
        SMART_CMD_LENGTH = 12,
    SMART_BUFFER_SIZE = 512,
        ATA_OP_CHECKPOWERMODE1 = 0xe5,
    SMART_SENSE_BUFFER_SIZE = 32,
        ATA_OP_CHECKPOWERMODE2 = 0x98,
    SMART_CMD_LENGTH = 12,
        ATA_USING_LBA = (1 << 6),
    ATA_OP_CHECKPOWERMODE1 = 0xe5,
        ATA_STAT_DRQ = (1 << 3),
    ATA_OP_CHECKPOWERMODE2 = 0x98,
        ATA_STAT_ERR = (1 << 0),
    ATA_USING_LBA = (1 << 6),
        SG_ATA_16 = 0x85,
    ATA_STAT_DRQ = (1 << 3),
        SG_ATA_16_LEN = 16,
    ATA_STAT_ERR = (1 << 0),
        SG_ATA_PROTO_NON_DATA = (3 << 1),
    SG_ATA_16 = 0x85,
        SG_CDB2_CHECK_COND = (1 << 5)
    SG_ATA_16_LEN = 16,
    SG_ATA_PROTO_NON_DATA = (3 << 1),
    SG_CDB2_CHECK_COND = (1 << 5)
};
};
enum {SYS, HDD};


struct s_fan {
struct s_fan {
        char name[20];
    char name[20];
        char control[2];
    char control[2];
        char pwm_enable[50];
    char pwm_enable[50];
        char pwm_write[50];
    char pwm_write[50];
        char interpolate[2];
    char interpolate[2];
        short hyst[2];
    short hyst[2];
        unsigned short interval[2];
    unsigned short interval[2];
        unsigned short decr_delay;
    unsigned short decr_delay;
        unsigned short stop_delay;
    unsigned short stop_delay;
        unsigned short count_scan[2];
    unsigned short count_scan[2];
        unsigned short count_step[2];
    unsigned short count_step[2];
        unsigned char idle_pwm;
    unsigned char idle_pwm;
        unsigned char error_pwm[2];
    unsigned char error_pwm[2];
        char loglevel;
    char loglevel;
        short temp[2];
    short temp[2];
        unsigned char pwm[2];
    unsigned char pwm[2];
        unsigned char actual_pwm;
    unsigned char actual_pwm;
        time_t next_check[2];
    time_t next_check[2];
        time_t min_decr_time;
    time_t min_decr_time;
        time_t min_stop_time;
    time_t min_stop_time;
} *fan;
} *fan;


struct s_temp_pwm {
struct s_temp_pwm {
        short temp;
    short temp;
        unsigned char pwm;
    unsigned char pwm;
} ***data_pwm;
} ***data_pwm;


struct s_buf {
struct s_buf {
        char name[MAX_SENSOR_LENGTH];
    char name[MAX_SENSOR_LENGTH];
        short temp;
    short temp;
} *buf;
} *buf;


Zeile 98: Zeile 98:


static inline int write_fan(char *name, unsigned char value) {
static inline int write_fan(char *name, unsigned char value) {
        FILE *fp;
    FILE *fp;


        if ((fp = fopen(name, "w")) == NULL) {
    if ((fp = fopen(name, "w")) == NULL) {
                syslog(LOG_ERR, "Error opening file %s!\n", name);
        syslog(LOG_ERR, "Error opening file %s!\n", name);
                return -1;
        return -1;
        }
    }
        fprintf(fp, "%d", value);
    fprintf(fp, "%d", value);
        fclose(fp);
    fclose(fp);


        return 0;
    return 0;
}
}


int read_fan_conf(short count_only) {
int read_fan_conf(short count_only) {
        FILE *fp;
    FILE *fp;
        size_t len;
    size_t len;
        char *name, *value, *value_w_comma, *total[2] = { NULL }, buf[BUFSIZE];
    char *name, *value, *value_w_comma, *total[2] = { NULL }, buf[BUFSIZE];
        short i = -1, j, k[2], t, per_fan[2];
    short i = -1, j, k[2], t, per_fan[2];


        now = time(NULL);
    now = time(NULL);
        if ((fp = fopen("/etc/fwcontrol.conf", "r")) == NULL) {
    if ((fp = fopen("/etc/fwcontrol.conf", "r")) == NULL) {
                syslog(LOG_ERR, "Error opening configuration file\n");
        syslog(LOG_ERR, "Error opening configuration file\n");
                return -1;
        return -1;
        }
    }


        while (fgets(buf, BUFSIZE, fp) != NULL) {
    while (fgets(buf, BUFSIZE, fp) != NULL) {
                if (buf[0] == '[') {
        if (buf[0] == '[') {
                        if (count_only) {
            if (count_only) {
                                ++count_fans;
                ++count_fans;
                                per_fan[SYS] = per_fan[HDD] = 0;
                per_fan[SYS] = per_fan[HDD] = 0;
                        } else {
            } else {
                                strcpy(fan[++i].name, strtok(buf, "[]\n"));
                strcpy(fan[++i].name, strtok(buf, "[]\n"));
                                k[SYS] = k[HDD] = 0;
                k[SYS] = k[HDD] = 0;
                        }
            }
                } else if (i >= 0 || count_only) {
        } else if (i >= 0 || count_only) {
                        name = strtok(buf, "=");
            name = strtok(buf, "=");
                        value = strtok(NULL, "=");
            value = strtok(NULL, "=");
                        value = strtok(value, "\n");
            value = strtok(value, "\n");
                        t = strstr(name, "sys") ? SYS : HDD;
            t = strstr(name, "sys") ? SYS : HDD;
                        if (!count_only) {
            if (!count_only) {
                                if (strncmp(name, "control_by_", 11) == 0) {
                if (strncmp(name, "control_by_", 11) == 0) {
                                        fan[i].control[t] = atoi(value);
                    fan[i].control[t] = atoi(value);
                                        fan[i].next_check[t] = now;
                    fan[i].next_check[t] = now;
                                        fan[i].error_pwm[t] = 255;
                    fan[i].error_pwm[t] = 255;
                                } else if (strcmp(name, "pwm_enable") == 0) {
                } else if (strcmp(name, "pwm_enable") == 0) {
                                        strcpy(fan[i].pwm_enable, value);
                    strcpy(fan[i].pwm_enable, value);
                                        write_fan(fan[i].pwm_enable, FAN_MODE);
                    write_fan(fan[i].pwm_enable, FAN_MODE);
                                } else if (strcmp(name, "pwm_write") == 0) {
                } else if (strcmp(name, "pwm_write") == 0) {
                                        strcpy(fan[i].pwm_write, value);
                    strcpy(fan[i].pwm_write, value);
                                } else if (strcmp(name, "stop_delay") == 0) {
                } else if (strcmp(name, "stop_delay") == 0) {
                                        fan[i].stop_delay = atoi(value);
                    fan[i].stop_delay = atoi(value);
                                } else if (strcmp(name, "decrease_delay") == 0) {
                } else if (strcmp(name, "decrease_delay") == 0) {
                                        fan[i].decr_delay = atoi(value);
                    fan[i].decr_delay = atoi(value);
                                } else if (strcmp(name, "loglevel") == 0) {
                } else if (strcmp(name, "loglevel") == 0) {
                                        fan[i].loglevel = atoi(value);
                    fan[i].loglevel = atoi(value);
                                } else if (strncmp(name, "interval_", 9) == 0) {
                } else if (strncmp(name, "interval_", 9) == 0) {
                                        fan[i].interval[t] = atoi(value);
                    fan[i].interval[t] = atoi(value);
                                } else if (strncmp(name, "interpolate_", 12) == 0) {
                } else if (strncmp(name, "interpolate_", 12) == 0) {
                                        fan[i].interpolate[t] = atoi(value);
                    fan[i].interpolate[t] = atoi(value);
                                } else if (strncmp(name, "hyst_", 5) == 0) {
                } else if (strncmp(name, "hyst_", 5) == 0) {
                                        fan[i].hyst[t] = atoi(value);
                    fan[i].hyst[t] = atoi(value);
                                } else if (strcmp(name, "idle_pwm") == 0) {
                } else if (strcmp(name, "idle_pwm") == 0) {
                                        fan[i].idle_pwm = atoi(value);
                    fan[i].idle_pwm = atoi(value);
                                } else if (strncmp(name, "error_pwm_", 10) == 0) {
                } else if (strncmp(name, "error_pwm_", 10) == 0) {
                                        fan[i].error_pwm[t] = atoi(value);
                    fan[i].error_pwm[t] = atoi(value);
                                } else if (strcmp(name, "sys_input") == 0 ||
                } else if (strncmp(name, "input_", 6) == 0) {
                                                strcmp(name, "scan_hdd") == 0 ) {
                    value = strtok(value, ",");
                                        value = strtok(value, ",");
                    while (value != NULL) {
                                        while (value != NULL) {
                        strcpy(scan[i][t][k[t]++], value);
                                                strcpy(scan[i][t][k[t]++], value);
                        value = strtok(NULL, ",");
                                                value = strtok(NULL, ",");
                    }
                                        }
                    fan[i].count_scan[t] = k[t];
                                        fan[i].count_scan[t] = k[t];
                } else if (strncmp(name, "temp_pwm_", 9) == 0) {
                                } else if (strncmp(name, "temp_pwm_", 9) == 0) {
                    j = 0;
                                        j = 0;
                    value = strtok(value, ",");
                                        value = strtok(value, ",");
                    while (value != NULL && ++j <= MAX_STEP) {
                                        while (value != NULL && ++j <= MAX_STEP) {
                        data_pwm[t][i][j-1].temp = atoi(value);
                                                data_pwm[t][i][j-1].temp = atoi(value);
                        value = strtok(NULL, ",");
                                                value = strtok(NULL, ",");
                        data_pwm[t][i][j-1].pwm = atoi(value);
                                                data_pwm[t][i][j-1].pwm = atoi(value);
                        value = strtok(NULL, ",");
                                                value = strtok(NULL, ",");
                    }
                                        }
                    fan[i].count_step[t] = j;
                                        fan[i].count_step[t] = j;
                }
                                }
            } else if (strncmp(name, "input_", 6) == 0) {
                        } else if (strcmp(name, "sys_input") == 0 ||
                // Adjust total number of sensors per type and
                                        strcmp(name, "scan_hdd") == 0) {
                // maximum number of sensors assigned to one fan
                                // Adjust total number of sensors per type and
                value = strtok(value, ",");
                                // maximum number of sensors assigned to one fan
                while (value != NULL) {
                                value = strtok(value, ",");
                    value_w_comma = malloc(strlen(value) + 2);
                                while (value != NULL) {
                    strcpy(value_w_comma, value);
                                        value_w_comma = malloc(strlen(value) + 2);
                    strcat(value_w_comma, ",");
                                        strcpy(value_w_comma, value);
                    if (count_total[t] == 0) {
                                        strcat(value_w_comma, ",");
                        total[t] = calloc(1, strlen(value_w_comma) + 1);
                                        if (count_total[t] == 0) {
                        strcat(total[t], value_w_comma);
                                                total[t] = calloc(1, strlen(value_w_comma) + 1);
                        ++count_total[t];
                                                strcat(total[t], value_w_comma);
                    } else if (strstr(total[t], value_w_comma) == NULL) {
                                                ++count_total[t];
                        len = strlen(total[t]) + strlen(value_w_comma) + 1;
                                        } else if (strstr(total[t], value_w_comma) == NULL) {
                        total[t] = realloc(total[t], len);
                                                len = strlen(total[t]) + strlen(value_w_comma) + 1;
                        strcat(total[t], value_w_comma);
                                                total[t] = realloc(total[t], len);
                        ++count_total[t];
                                                strcat(total[t], value_w_comma);
                    }
                                                ++count_total[t];
                    free(value_w_comma);
                                        }
                    if (++per_fan[t] > max_per_fan)
                                        free(value_w_comma);
                        ++max_per_fan;
                                        if (++per_fan[t] > max_per_fan)
                    value = strtok(NULL, ",");
                                                ++max_per_fan;
                                        value = strtok(NULL, ",");
                                }
                        }
                 }
                 }
            }
         }
         }
        fclose(fp);
    }
    fclose(fp);


        if (count_only) {
    if (count_only) {
                free(total[SYS]);
        free(total[SYS]);
                free(total[HDD]);
        free(total[HDD]);


                syslog(LOG_NOTICE, "Controlling %d %s by monitoring %d system %s and %d %s",
        syslog(LOG_NOTICE, "Controlling %d %s by monitoring %d system %s and %d %s",
                  count_fans, (count_fans == 1) ? "fan" : "fans", count_total[SYS],
          count_fans, (count_fans == 1) ? "fan" : "fans", count_total[SYS],
                  (count_total[SYS] == 1) ? "sensor" : "sensors", count_total[HDD],
          (count_total[SYS] == 1) ? "sensor" : "sensors", count_total[HDD],
                  (count_total[HDD] == 1) ? "hard drive" : "hard drives");
          (count_total[HDD] == 1) ? "hard drive" : "hard drives");
        }
    }


        return 0;
    return 0;
}
}


static inline int sgio_send(int fd, unsigned char cmd, unsigned char *rv) {
static inline int sgio_send(int fd, unsigned char cmd, unsigned char *rv) {
        unsigned char cdb[SG_ATA_16_LEN], sb[32], *desc, status, error;
    unsigned char cdb[SG_ATA_16_LEN], sb[32], *desc, status, error;
        sg_io_hdr_t io_hdr;
    sg_io_hdr_t io_hdr;


        memset(&cdb, 0, sizeof(cdb));
    memset(&cdb, 0, sizeof(cdb));
        memset(&sb, 0, sizeof(sb));
    memset(&sb, 0, sizeof(sb));
        memset(&io_hdr, 0, sizeof(io_hdr));
    memset(&io_hdr, 0, sizeof(io_hdr));


        cdb[0] = SG_ATA_16;
    cdb[0] = SG_ATA_16;
        cdb[1] = SG_ATA_PROTO_NON_DATA;
    cdb[1] = SG_ATA_PROTO_NON_DATA;
        cdb[2] = SG_CDB2_CHECK_COND;
    cdb[2] = SG_CDB2_CHECK_COND;
        cdb[13] = ATA_USING_LBA;
    cdb[13] = ATA_USING_LBA;
        cdb[14] = cmd;
    cdb[14] = cmd;


        io_hdr.cmd_len = SG_ATA_16_LEN;
    io_hdr.cmd_len = SG_ATA_16_LEN;
        io_hdr.interface_id = 'S';
    io_hdr.interface_id = 'S';
        io_hdr.mx_sb_len = sizeof(sb);
    io_hdr.mx_sb_len = sizeof(sb);
        io_hdr.dxfer_direction = SG_DXFER_NONE;
    io_hdr.dxfer_direction = SG_DXFER_NONE;
        io_hdr.dxfer_len = 0;
    io_hdr.dxfer_len = 0;
        io_hdr.dxferp = NULL;
    io_hdr.dxferp = NULL;
        io_hdr.cmdp = cdb;
    io_hdr.cmdp = cdb;
        io_hdr.sbp = sb;
    io_hdr.sbp = sb;
        io_hdr.pack_id = 0;
    io_hdr.pack_id = 0;
        io_hdr.timeout = 500; // Milliseconds
    io_hdr.timeout = 500; // Milliseconds


        if (ioctl(fd, SG_IO, &io_hdr) == -1) {
    if (ioctl(fd, SG_IO, &io_hdr) == -1) {
                syslog(LOG_ERR, "ioctl() failed (cmd %u, %s)\n", cmd, strerror(errno));
        syslog(LOG_ERR, "ioctl() failed (cmd %u, %s)\n", cmd, strerror(errno));
                return -1;
        return -1;
        }
    }


        desc = sb + 8;
    desc = sb + 8;
        status = desc[13];
    status = desc[13];
        error = desc[3];
    error = desc[3];
        if (rv)
    if (rv)
                *rv = desc[5];
        *rv = desc[5];


        if (status & (ATA_STAT_ERR | ATA_STAT_DRQ)) {
    if (status & (ATA_STAT_ERR | ATA_STAT_DRQ)) {
                syslog(LOG_ERR, "SG_IO command %u failed (status %u, error %u)\n", cmd, status, error);
        syslog(LOG_ERR, "SG_IO command %u failed (status %u, error %u)\n", cmd, status, error);
                return -1;
        return -1;
        }
    }


        return 0;
    return 0;
}
}


static inline char get_hdd_status(char *name) {
static inline char get_hdd_status(char *name) {
        int fd, ret;
    int fd, ret;
        unsigned char state;
    unsigned char state;


        if ((fd = open(name, O_RDONLY)) < 0) {
    if ((fd = open(name, O_RDONLY)) < 0) {
                syslog(LOG_ERR, "Error opening file %s!\n", name);
        syslog(LOG_ERR, "Error opening file %s!\n", name);
                return -1;
        return -1;
        }
    }


        ret = 1;
    ret = 1;
        if (sgio_send(fd, ATA_OP_CHECKPOWERMODE1, &state) &&
    if (sgio_send(fd, ATA_OP_CHECKPOWERMODE1, &state) &&
                        sgio_send(fd, ATA_OP_CHECKPOWERMODE2, &state))
            sgio_send(fd, ATA_OP_CHECKPOWERMODE2, &state))
                ret = 0;
        ret = 0;
        close(fd);
    close(fd);


        return (state == 0) ? 0 : 1; // 0 = Sleeping, 1 = Running
    return (state == 0) ? 0 : 1; // 0 = Sleeping, 1 = Running
}
}


static inline short get_hdd_temp(char *name) {
static inline short get_hdd_temp(char *name) {
        short hdd_temp;
    short hdd_temp;
        int fd;
    int fd;
        unsigned char buffer[SMART_BUFFER_SIZE] = "";
    unsigned char buffer[SMART_BUFFER_SIZE] = "";
        unsigned char sense_buffer[SMART_SENSE_BUFFER_SIZE];
    unsigned char sense_buffer[SMART_SENSE_BUFFER_SIZE];
        unsigned char smart_read_cdb[SMART_CMD_LENGTH] =
    unsigned char smart_read_cdb[SMART_CMD_LENGTH] =
                        {0xa1, 0x0c, 0x0e, 0xd0, 1, 0, 0x4f, 0xc2, 0, 0xb0, 0, 0};
            {0xa1, 0x0c, 0x0e, 0xd0, 1, 0, 0x4f, 0xc2, 0, 0xb0, 0, 0};


        sg_io_hdr_t io_hdr;
    sg_io_hdr_t io_hdr;


        if ((fd = open(name, O_RDONLY)) < 0) {
    if ((fd = open(name, O_RDONLY)) < 0) {
                syslog(LOG_ERR, "Error opening file %s!\n", name);
        syslog(LOG_ERR, "Error opening file %s!\n", name);
                return -1;
        return -1;
        }
    }


        memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
        io_hdr.interface_id = 'S';
    io_hdr.interface_id = 'S';
        io_hdr.cmd_len = SMART_CMD_LENGTH;
    io_hdr.cmd_len = SMART_CMD_LENGTH;
        io_hdr.mx_sb_len = SMART_SENSE_BUFFER_SIZE;
    io_hdr.mx_sb_len = SMART_SENSE_BUFFER_SIZE;
        io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
        io_hdr.dxfer_len = SMART_BUFFER_SIZE;
    io_hdr.dxfer_len = SMART_BUFFER_SIZE;
        io_hdr.dxferp = buffer;
    io_hdr.dxferp = buffer;
        io_hdr.cmdp = smart_read_cdb;
    io_hdr.cmdp = smart_read_cdb;
        io_hdr.sbp = sense_buffer;
    io_hdr.sbp = sense_buffer;
        io_hdr.timeout = 500; // Milliseconds
    io_hdr.timeout = 500; // Milliseconds


        if (ioctl(fd, SG_IO, &io_hdr) < 0) {
    if (ioctl(fd, SG_IO, &io_hdr) < 0) {
                syslog(LOG_ERR, "ioctl() call for reading temperature failed\n");
        syslog(LOG_ERR, "ioctl() call for reading temperature failed\n");
                close(fd);
        close(fd);
                return -1;
        return -1;
    }
 
    for (register int i = 2; i < 361; i += 12)
        if ((int)buffer[i] == 194) {
            hdd_temp = ((long long int)((buffer[i+5])|
                    (buffer[i+6]<<8)|
                    (buffer[i+7]<<16)|
                    (buffer[i+8]<<24)|
                    ((long long int)buffer[i+9]<<32)|
                    ((long long int)buffer[i+10]<<40))) & 0xFF;
            break;
         }
         }
    close(fd);


        for (register int i = 2; i < 361; i += 12)
    return hdd_temp;
                if ((int)buffer[i] == 194) {
                        hdd_temp = ((long long int)((buffer[i+5])|
                                        (buffer[i+6]<<8)|
                                        (buffer[i+7]<<16)|
                                        (buffer[i+8]<<24)|
                                        ((long long int)buffer[i+9]<<32)|
                                        ((long long int)buffer[i+10]<<40))) & 0xFF;
                        break;
                }
        close(fd);
 
        return hdd_temp;
}
}


static inline short get_sys_temp(char *name) {
static inline short get_sys_temp(char *name) {
        FILE *fp;
    FILE *fp;
        int sys_temp;
    int sys_temp;


        if ((fp = fopen(name, "r")) == NULL) {
    if ((fp = fopen(name, "r")) == NULL) {
                syslog(LOG_ERR, "Error opening file %s!\n", name);
        syslog(LOG_ERR, "Error opening file %s!\n", name);
                return -1;
        return -1;
        }
    }
        fscanf(fp, "%d", &sys_temp);
    fscanf(fp, "%d", &sys_temp);
        fclose(fp);
    fclose(fp);


        return sys_temp / 1000;
    return sys_temp / 1000;
}
}


static inline short get_temp(short i, short type) {
static inline short get_temp(short i, short type) {
        struct stat device;
    struct stat device;
        short j, k, sensor_temp, max_temp = 0;
    short j, k, sensor_temp, max_temp = 0;
        short (*get_sensor_temp[]) (char *) = {get_sys_temp, get_hdd_temp};
    short (*get_sensor_temp[]) (char *) = {get_sys_temp, get_hdd_temp};


        for (j = 0; j < fan[i].count_scan[type]; j++) {
    for (j = 0; j < fan[i].count_scan[type]; j++) {
                // Check if temperature is stored in buffer
        // Check if temperature is stored in buffer
                for (k = 0; k < count_buffer; k++)
        for (k = 0; k < count_buffer; k++)
                        if (strcmp(buf[k].name, scan[i][type][j]) == 0) {
            if (strcmp(buf[k].name, scan[i][type][j]) == 0) {
                                sensor_temp = buf[k].temp;
                sensor_temp = buf[k].temp;
                                break;
                break;
                        }
            }


                if (k == count_buffer) {
        if (k == count_buffer) {
                        if (type == SYS || (type == HDD &&
            if (type == SYS || (type == HDD &&
                                        !stat(scan[i][type][j], &device) &&
                    !stat(scan[i][type][j], &device) &&
                                        (device.st_mode & S_IFMT) == S_IFBLK &&
                    (device.st_mode & S_IFMT) == S_IFBLK &&
                                        get_hdd_status(scan[i][type][j])))
                    get_hdd_status(scan[i][type][j])))
                                // Get temperature from system sensor or hard drive
                // Get temperature from system sensor or hard drive
                                sensor_temp = get_sensor_temp[type](scan[i][type][j]);
                sensor_temp = get_sensor_temp[type](scan[i][type][j]);
                        else
            else
                                sensor_temp = 0;
                sensor_temp = 0;


                        // Write temperature to buffer
            // Write temperature to buffer
                        strcpy(buf[count_buffer].name, scan[i][type][j]);
            strcpy(buf[count_buffer].name, scan[i][type][j]);
                        buf[count_buffer++].temp = sensor_temp;
            buf[count_buffer++].temp = sensor_temp;
                }
        }


                if (sensor_temp > max_temp)
        if (sensor_temp > max_temp)
                        max_temp = sensor_temp;
            max_temp = sensor_temp;
                else if (sensor_temp < 0)
        else if (sensor_temp < 0)
                        return -1;
            return -1;
    }


        }
    return max_temp;
 
        return max_temp;
}
}


static inline unsigned char calc_pwm(char t, int i, short temp) {
static inline unsigned char calc_pwm(char t, int i, short temp) {
        unsigned char pwm = 0;
    unsigned char pwm = 0;
        short j = 0;
    short j = 0;


        while (temp >= data_pwm[t][i][j].temp) {
    while (temp >= data_pwm[t][i][j].temp) {
                pwm = data_pwm[t][i][j].pwm;
        pwm = data_pwm[t][i][j].pwm;
                if (++j >= fan[i].count_step[t])
        if (++j >= fan[i].count_step[t])
                        break;
            break;
        }
    }


        if (fan[i].interpolate[t] && j > 0 && j < fan[i].count_step[t])
    if (fan[i].interpolate[t] && j > 0 && j < fan[i].count_step[t])
                pwm = ((float)data_pwm[t][i][j].pwm - data_pwm[t][i][j-1].pwm) /
        pwm = ((float)data_pwm[t][i][j].pwm - data_pwm[t][i][j-1].pwm) /
                        (data_pwm[t][i][j].temp - data_pwm[t][i][j-1].temp) *
            (data_pwm[t][i][j].temp - data_pwm[t][i][j-1].temp) *
                        (temp - data_pwm[t][i][j-1].temp) + data_pwm[t][i][j-1].pwm + 0.5;
            (temp - data_pwm[t][i][j-1].temp) + data_pwm[t][i][j-1].pwm + 0.5;


        return pwm;
    return pwm;
}
}


static inline void calc_fan_data(short i, short type) {
static inline int control_fan_speed(void) {
        short temp;
    unsigned char new_pwm;
    char sw_checked;
    short i, type, temp;


         // Get relevant temperature for specific fan and sensor type
    next_check = 0;
        temp = get_temp(i, type);
    count_buffer = 0;
    for (i = 0; i < count_fans; i++) {
        sw_checked = 0;
         for (type = 0; type < 2; type++) {
            if (fan[i].control[type] && fan[i].next_check[type] <= now) {


        if (temp < 0 && calc_pwm(type, i, temp) < fan[i].error_pwm[type])
                 // Get relevant temperature for specific fan and sensor type
                 // An error occured, set fan speed value accordingly
                 temp = get_temp(i, type);
                 fan[i].pwm[type] = fan[i].error_pwm[type];
        else if (!(temp < fan[i].temp[type] &&
                        (fan[i].hyst[type] > fan[i].temp[type] - temp ||
                        now < fan[i].min_decr_time)) &&
                        (fan[i].temp[type] != temp || first_check)) {
                fan[i].pwm[type] = calc_pwm(type, i, temp);
                fan[i].temp[type] = temp;
        }


        // Set next check time for this fan
                if (temp < 0 && calc_pwm(type, i, temp) < fan[i].error_pwm[type])
        while (fan[i].next_check[type] <= now)
                    // An error occured, set fan speed value accordingly
                fan[i].next_check[type] += fan[i].interval[type];
                    fan[i].pwm[type] = fan[i].error_pwm[type];
}
                else if ((temp > fan[i].temp[type] ||
                        (fan[i].hyst[type] <= fan[i].temp[type] - temp &&
                        fan[i].min_decr_time <= now)) &&
                        (temp != fan[i].temp[type] || first_check)) {
                    fan[i].pwm[type] = calc_pwm(type, i, temp);
                    fan[i].temp[type] = temp;
                }


static inline int control_fan_speed(void) {
                // Set next check time for this fan and type
        unsigned char new_pwm;
                while (fan[i].next_check[type] <= now)
        char sw_checked;
                    fan[i].next_check[type] += fan[i].interval[type];
        short i, type;


        next_check = 0;
                 sw_checked = 1;
        count_buffer = 0;
            }
        for (i = 0; i < count_fans; i++) {
                 sw_checked = 0;
                for (type = 0; type < 2; type++) {
                        if (fan[i].control[type] && fan[i].next_check[type] <= now) {


                                // Calculate data for specific fan
            // Set time this function needs to be called again
                                calc_fan_data(i, type);
            if (fan[i].control[type] && (next_check == 0 ||
                                sw_checked = 1;
                    next_check > fan[i].next_check[type]))
                        }
                next_check = fan[i].next_check[type];
        }


                        // Set next check time
        if (!sw_checked)
                        if (fan[i].control[type] && (next_check == 0 ||
            continue;
                                        next_check > fan[i].next_check[type]))
                                next_check = fan[i].next_check[type];
                }


                if (!sw_checked)
        if (fan[i].pwm[SYS] > fan[i].pwm[HDD])
                        continue;
            new_pwm = fan[i].pwm[SYS];
        else
            new_pwm = fan[i].pwm[HDD];


                if (fan[i].pwm[SYS] > fan[i].pwm[HDD])
        if (new_pwm == fan[i].actual_pwm && !first_check)
                        new_pwm = fan[i].pwm[SYS];
            continue;
                else
        else if (new_pwm < fan[i].actual_pwm)
                        new_pwm = fan[i].pwm[HDD];
            // Check if decrease delay time has passed
 
            if (now < fan[i].min_decr_time)
                if (new_pwm == fan[i].actual_pwm && !first_check)
                continue;
            else if (new_pwm == 0)
                // Check if stop delay time has passed
                if (now < fan[i].min_stop_time)
                    if (fan[i].idle_pwm != 0 &&
                            fan[i].idle_pwm != fan[i].actual_pwm)
                        // Set fan speed to idle
                        new_pwm = fan[i].idle_pwm;
                    else
                         continue;
                         continue;
                else if (new_pwm < fan[i].actual_pwm)
                        // Check if decrease delay time has passed
                        if (now < fan[i].min_decr_time)
                                continue;
                        else if (new_pwm == 0)
                                // Check if stop delay time has passed
                                if (now < fan[i].min_stop_time)
                                        if (fan[i].idle_pwm != 0 &&
                                                        fan[i].idle_pwm != fan[i].actual_pwm)
                                                // Set fan speed to idle
                                                new_pwm = fan[i].idle_pwm;
                                        else
                                                continue;


                // Set new fan speed
        // Set new fan speed
                if (write_fan(fan[i].pwm_write, new_pwm) != 0)
        if (write_fan(fan[i].pwm_write, new_pwm) != 0)
                        continue;
            continue;
                if (fan[i].loglevel >= 1 && first_check)
        if (fan[i].loglevel >= 1 && first_check)
                        syslog(LOG_NOTICE, "%s: PWM set to %d (SYS: %d°%s, HDD: %d°%s)\n",
            syslog(LOG_NOTICE, "%s: PWM set to %d (SYS: %d°%s, HDD: %d°%s)\n",
                          fan[i].name, new_pwm, fan[i].temp[SYS],
              fan[i].name, new_pwm, fan[i].temp[SYS],
                          (fan[i].pwm[SYS] < new_pwm) ? "C" : "C*", fan[i].temp[HDD],
              (fan[i].pwm[SYS] < new_pwm) ? "C" : "C*", fan[i].temp[HDD],
                          (fan[i].pwm[HDD] < new_pwm) ? "C" : "C*");
              (fan[i].pwm[HDD] < new_pwm) ? "C" : "C*");
                else if (fan[i].loglevel >= 1)
        else if (fan[i].loglevel >= 1)
                        syslog(LOG_NOTICE, "%s: PWM changed from %d to %d (SYS: %d°%s, HDD: %d°%s)\n",
            syslog(LOG_NOTICE, "%s: PWM changed from %d to %d (SYS: %d°%s, HDD: %d°%s)\n",
                          fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].temp[SYS],
              fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].temp[SYS],
                          (fan[i].pwm[SYS] < new_pwm) ? "C" : "C*", fan[i].temp[HDD],
              (fan[i].pwm[SYS] < new_pwm) ? "C" : "C*", fan[i].temp[HDD],
                          (fan[i].pwm[HDD] < new_pwm) ? "C" : "C*");
              (fan[i].pwm[HDD] < new_pwm) ? "C" : "C*");
                if (fan[i].actual_pwm < new_pwm)
        if (fan[i].actual_pwm < new_pwm)
                        fan[i].min_decr_time = now + fan[i].decr_delay;
            fan[i].min_decr_time = now + fan[i].decr_delay;
                if (fan[i].actual_pwm == 0 && new_pwm != 0)
        if (fan[i].actual_pwm == 0 && new_pwm != 0)
                        fan[i].min_stop_time = now + fan[i].stop_delay;
            fan[i].min_stop_time = now + fan[i].stop_delay;
                fan[i].actual_pwm = new_pwm;
        fan[i].actual_pwm = new_pwm;
        }
    }
        if (first_check)
    if (first_check)
                first_check = 0;
        first_check = 0;


        return 0;
    return 0;
}
}


int main (int argc, char **argv) {
int main (int argc, char **argv) {
        openlog("fwcontrol", LOG_PID | LOG_CONS | LOG_NDELAY, LOG_LOCAL0);
    openlog("fwcontrol", LOG_PID | LOG_CONS | LOG_NDELAY, LOG_LOCAL0);
        syslog(LOG_NOTICE, "fwcontrol started ...\n");
    syslog(LOG_NOTICE, "fwcontrol started ...\n");


        // Determine number of configured fans, system sensors and hard drives and
    // Determine number of configured fans, system sensors and hard drives and
        // allocate memory accordingly
    // allocate memory accordingly
        read_fan_conf(1);
    read_fan_conf(1);


        fan = calloc(count_fans, sizeof(struct s_fan));
    fan = calloc(count_fans, sizeof(struct s_fan));
        buf = calloc(count_total[SYS] + count_total[HDD], sizeof(struct s_buf));
    buf = calloc(count_total[SYS] + count_total[HDD], sizeof(struct s_buf));
        data_pwm = calloc(2, sizeof(struct s_temp_pwm));
    data_pwm = calloc(2, sizeof(struct s_temp_pwm));
        for (int h = 0; h < 2; h++) {
    for (int h = 0; h < 2; h++) {
                data_pwm[h] = calloc(count_fans, sizeof(data_pwm[0]));
        data_pwm[h] = calloc(count_fans, sizeof(data_pwm[0]));
                for (int r = 0; r < count_fans; r++)
        for (int r = 0; r < count_fans; r++)
                        data_pwm[h][r] = calloc(MAX_STEP, sizeof(data_pwm[0][0]));
            data_pwm[h][r] = calloc(MAX_STEP, sizeof(data_pwm[0][0]));
        }
    }
        scan = calloc(count_fans, sizeof(char***));
    scan = calloc(count_fans, sizeof(char***));
        for (int h = 0; h < count_fans; h++) {
    for (int h = 0; h < count_fans; h++) {
                scan[h] = calloc(2, sizeof(char**));
        scan[h] = calloc(2, sizeof(char**));
                for (int r = 0; r < 2; r++) {
        for (int r = 0; r < 2; r++) {
                        scan[h][r] = calloc(max_per_fan, sizeof(char*));
            scan[h][r] = calloc(max_per_fan, sizeof(char*));
                        for (int s = 0; s < max_per_fan; s++)
            for (int s = 0; s < max_per_fan; s++)
                                scan[h][r][s] = calloc(MAX_SENSOR_LENGTH + 1, sizeof(char));
                scan[h][r][s] = calloc(MAX_SENSOR_LENGTH + 1, sizeof(char));
                }
         }
         }
    }


        // Read fan configuration
    // Read fan configuration
        read_fan_conf(0);
    read_fan_conf(0);
        while (1) {
    while (1) {
                now = time(NULL);
        now = time(NULL);
                control_fan_speed();
        control_fan_speed();
                sleep(next_check - now);
        sleep(next_check - now);
        }
    }
        syslog(LOG_NOTICE, "fwcontrol terminated.\n");
    syslog(LOG_NOTICE, "fwcontrol terminated.\n");
        closelog();
    closelog();
        return EXIT_SUCCESS;
    return EXIT_SUCCESS;
}
}
</syntaxhighlight>
</syntaxhighlight>
Zeile 549: Zeile 540:
# pwm_enable      | filename    | Used to set the fan mode.
# pwm_enable      | filename    | Used to set the fan mode.
# pwm_write      | filename    | Used to change the fan speed.
# pwm_write      | filename    | Used to change the fan speed.
# sys_input       | filename(s) | Defines which system temperature sensors
# input_sys       | filename(s) | Defines which system temperature sensors
#                |            | should be checked. Values can be written comma
#                |            | should be checked. Values can be written comma
#                |            | separated and/or in multiple lines:
#                |            | separated and/or in multiple lines:
#                |            | sys_input=/path1/filename1,/path2/filename2...
#                |            | input_sys=/path1/filename1,/path2/filename2...
#                |            | sys_input=/path6/filename6,/path7/filename7...
#                |            | input_sys=/path6/filename6,/path7/filename7...
#                |            | ...
#                |            | ...
# temp_pwm_sys    | temp,pwm    | Defines the PWM value for a given system
# temp_pwm_sys    | temp,pwm    | Defines the PWM value for a given system
Zeile 563: Zeile 554:
#                |            | least this value until fan speed can be
#                |            | least this value until fan speed can be
#                |            | reduced.
#                |            | reduced.
# scan_hdd        | device(s)  | Defines which hard drives should be checked.
# input_hdd      | device(s)  | Defines which hard drives should be checked.
#                |            | Values can be written comma separated and/or
#                |            | Values can be written comma separated and/or
#                |            | in multiple lines:
#                |            | in multiple lines:
#                |            | scan_hdd=/dev/<hdd1>,/dev/<hdd2>...
#                |            | input_hdd=/dev/<hdd1>,/dev/<hdd2>...
#                |            | scan_hdd=/dev/<hhd6>,/dev/<hdd7>...
#                |            | input_hdd=/dev/<hhd6>,/dev/<hdd7>...
#                |            | ...
#                |            | ...
# temp_pwm_hdd    | temp,pwm    | Defines the PWM value for a given hard drive
# temp_pwm_hdd    | temp,pwm    | Defines the PWM value for a given hard drive
Zeile 602: Zeile 593:
pwm_enable=/sys/class/hwmon/hwmon2/pwm2_enable
pwm_enable=/sys/class/hwmon/hwmon2/pwm2_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm2
pwm_write=/sys/class/hwmon/hwmon2/pwm2
sys_input=/sys/class/hwmon/hwmon1/temp1_input
input_sys=/sys/class/hwmon/hwmon1/temp1_input
temp_pwm_sys=0,34,38,34,59,160,70,255
temp_pwm_sys=0,34,38,34,59,160,70,255
interpolate_sys=1
interpolate_sys=1
Zeile 610: Zeile 601:
idle_pwm=34
idle_pwm=34
interval_sys=3
interval_sys=3
loglevel=0
loglevel=1


[Front left]
[Front left]
Zeile 617: Zeile 608:
pwm_enable=/sys/class/hwmon/hwmon2/pwm3_enable
pwm_enable=/sys/class/hwmon/hwmon2/pwm3_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm3
pwm_write=/sys/class/hwmon/hwmon2/pwm3
sys_input=/sys/class/hwmon/hwmon1/temp1_input
input_sys=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
temp_pwm_sys=0,58,38,58,39,76,40,108,41,122,51,202,60,255
temp_pwm_sys=0,58,38,58,39,76,40,108,41,122,51,202,60,255
temp_pwm_hdd=35,108,36,144,37,180,38,224,39,255
temp_pwm_hdd=35,108,36,144,37,180,38,224,39,255
Zeile 630: Zeile 621:
interval_sys=15
interval_sys=15
interval_hdd=60
interval_hdd=60
loglevel=0
loglevel=1


[Front right]
[Front right]
Zeile 637: Zeile 628:
pwm_enable=/sys/class/hwmon/hwmon2/pwm4_enable
pwm_enable=/sys/class/hwmon/hwmon2/pwm4_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm4
pwm_write=/sys/class/hwmon/hwmon2/pwm4
sys_input=/sys/class/hwmon/hwmon1/temp1_input
input_sys=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
temp_pwm_sys=60,255
temp_pwm_sys=60,255
temp_pwm_hdd=34,56,35,64,36,88,37,138,38,178,39,255
temp_pwm_hdd=34,56,35,64,36,88,37,138,38,178,39,255
Zeile 653: Zeile 644:
interval_sys=15
interval_sys=15
interval_hdd=60
interval_hdd=60
loglevel=0
loglevel=1


[Rear left]
[Rear left]
Zeile 660: Zeile 651:
pwm_enable=/sys/class/hwmon/hwmon2/pwm1_enable
pwm_enable=/sys/class/hwmon/hwmon2/pwm1_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm1
pwm_write=/sys/class/hwmon/hwmon2/pwm1
sys_input=/sys/class/hwmon/hwmon1/temp1_input
input_sys=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
temp_pwm_sys=0,70,38,70,39,88,40,106,41,124,51,224,60,255
temp_pwm_sys=0,70,38,70,39,88,40,106,41,124,51,224,60,255
temp_pwm_hdd=35,88,36,106,38,146,39,192,40,255
temp_pwm_hdd=35,88,36,106,38,146,39,192,40,255
Zeile 677: Zeile 668:
interval_sys=15
interval_sys=15
interval_hdd=60
interval_hdd=60
loglevel=0
loglevel=1


[Rear right]
[Rear right]
Zeile 684: Zeile 675:
pwm_enable=/sys/class/hwmon/hwmon2/pwm5_enable
pwm_enable=/sys/class/hwmon/hwmon2/pwm5_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm5
pwm_write=/sys/class/hwmon/hwmon2/pwm5
sys_input=/sys/class/hwmon/hwmon1/temp1_input
input_sys=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
scan_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
temp_pwm_sys=50,72,58,152,60,255
temp_pwm_sys=50,72,58,152,60,255
temp_pwm_hdd=37,104,38,142,39,188,40,255
temp_pwm_hdd=37,104,38,142,39,188,40,255
Zeile 701: Zeile 692:
interval_sys=15
interval_sys=15
interval_hdd=60
interval_hdd=60
loglevel=0
loglevel=1
</syntaxhighlight>
</syntaxhighlight>



Aktuelle Version vom 31. Dezember 2020, 09:09 Uhr

This program monitors temperatures of both system and hard drives and changes fan speeds accordingly.

C program fwcontrol.c

// Author:  Frank Wulf
// Version: 2.3 (2020-12-30)
//
// This program monitors temperatures of both system and hard drives and
// changes fan speeds accordingly.
//
// Copyright (C) 2017 Frank Wulf
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <http://www.gnu.org/licenses/>.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <scsi/sg.h>

enum {
    SYS = 0,
    HDD = 1,
    BUFSIZE = 256,
    FAN_MODE = 1,
    MAX_STEP = 10,
    MAX_SENSOR_LENGTH = 50,
    SMART_BUFFER_SIZE = 512,
    SMART_SENSE_BUFFER_SIZE = 32,
    SMART_CMD_LENGTH = 12,
    ATA_OP_CHECKPOWERMODE1 = 0xe5,
    ATA_OP_CHECKPOWERMODE2 = 0x98,
    ATA_USING_LBA = (1 << 6),
    ATA_STAT_DRQ = (1 << 3),
    ATA_STAT_ERR = (1 << 0),
    SG_ATA_16 = 0x85,
    SG_ATA_16_LEN = 16,
    SG_ATA_PROTO_NON_DATA = (3 << 1),
    SG_CDB2_CHECK_COND = (1 << 5)
};

struct s_fan {
    char name[20];
    char control[2];
    char pwm_enable[50];
    char pwm_write[50];
    char interpolate[2];
    short hyst[2];
    unsigned short interval[2];
    unsigned short decr_delay;
    unsigned short stop_delay;
    unsigned short count_scan[2];
    unsigned short count_step[2];
    unsigned char idle_pwm;
    unsigned char error_pwm[2];
    char loglevel;
    short temp[2];
    unsigned char pwm[2];
    unsigned char actual_pwm;
    time_t next_check[2];
    time_t min_decr_time;
    time_t min_stop_time;
} *fan;

struct s_temp_pwm {
    short temp;
    unsigned char pwm;
} ***data_pwm;

struct s_buf {
    char name[MAX_SENSOR_LENGTH];
    short temp;
} *buf;

char ****scan;
char first_check = 1;

unsigned short count_buffer, count_fans, count_total[2], max_per_fan;
time_t now, next_check;

static inline int write_fan(char *name, unsigned char value) {
    FILE *fp;

    if ((fp = fopen(name, "w")) == NULL) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }
    fprintf(fp, "%d", value);
    fclose(fp);

    return 0;
}

int read_fan_conf(short count_only) {
    FILE *fp;
    size_t len;
    char *name, *value, *value_w_comma, *total[2] = { NULL }, buf[BUFSIZE];
    short i = -1, j, k[2], t, per_fan[2];

    now = time(NULL);
    if ((fp = fopen("/etc/fwcontrol.conf", "r")) == NULL) {
        syslog(LOG_ERR, "Error opening configuration file\n");
        return -1;
    }

    while (fgets(buf, BUFSIZE, fp) != NULL) {
        if (buf[0] == '[') {
            if (count_only) {
                ++count_fans;
                per_fan[SYS] = per_fan[HDD] = 0;
            } else {
                strcpy(fan[++i].name, strtok(buf, "[]\n"));
                k[SYS] = k[HDD] = 0;
            }
        } else if (i >= 0 || count_only) {
            name = strtok(buf, "=");
            value = strtok(NULL, "=");
            value = strtok(value, "\n");
            t = strstr(name, "sys") ? SYS : HDD;
            if (!count_only) {
                if (strncmp(name, "control_by_", 11) == 0) {
                    fan[i].control[t] = atoi(value);
                    fan[i].next_check[t] = now;
                    fan[i].error_pwm[t] = 255;
                } else if (strcmp(name, "pwm_enable") == 0) {
                    strcpy(fan[i].pwm_enable, value);
                    write_fan(fan[i].pwm_enable, FAN_MODE);
                } else if (strcmp(name, "pwm_write") == 0) {
                    strcpy(fan[i].pwm_write, value);
                } else if (strcmp(name, "stop_delay") == 0) {
                    fan[i].stop_delay = atoi(value);
                } else if (strcmp(name, "decrease_delay") == 0) {
                    fan[i].decr_delay = atoi(value);
                } else if (strcmp(name, "loglevel") == 0) {
                    fan[i].loglevel = atoi(value);
                } else if (strncmp(name, "interval_", 9) == 0) {
                    fan[i].interval[t] = atoi(value);
                } else if (strncmp(name, "interpolate_", 12) == 0) {
                    fan[i].interpolate[t] = atoi(value);
                } else if (strncmp(name, "hyst_", 5) == 0) {
                    fan[i].hyst[t] = atoi(value);
                } else if (strcmp(name, "idle_pwm") == 0) {
                    fan[i].idle_pwm = atoi(value);
                } else if (strncmp(name, "error_pwm_", 10) == 0) {
                    fan[i].error_pwm[t] = atoi(value);
                } else if (strncmp(name, "input_", 6) == 0) {
                    value = strtok(value, ",");
                    while (value != NULL) {
                        strcpy(scan[i][t][k[t]++], value);
                        value = strtok(NULL, ",");
                    }
                    fan[i].count_scan[t] = k[t];
                } else if (strncmp(name, "temp_pwm_", 9) == 0) {
                    j = 0;
                    value = strtok(value, ",");
                    while (value != NULL && ++j <= MAX_STEP) {
                        data_pwm[t][i][j-1].temp = atoi(value);
                        value = strtok(NULL, ",");
                        data_pwm[t][i][j-1].pwm = atoi(value);
                        value = strtok(NULL, ",");
                    }
                    fan[i].count_step[t] = j;
                }
            } else if (strncmp(name, "input_", 6) == 0) {
                // Adjust total number of sensors per type and
                // maximum number of sensors assigned to one fan
                value = strtok(value, ",");
                while (value != NULL) {
                    value_w_comma = malloc(strlen(value) + 2);
                    strcpy(value_w_comma, value);
                    strcat(value_w_comma, ",");
                    if (count_total[t] == 0) {
                        total[t] = calloc(1, strlen(value_w_comma) + 1);
                        strcat(total[t], value_w_comma);
                        ++count_total[t];
                    } else if (strstr(total[t], value_w_comma) == NULL) {
                        len = strlen(total[t]) + strlen(value_w_comma) + 1;
                        total[t] = realloc(total[t], len);
                        strcat(total[t], value_w_comma);
                        ++count_total[t];
                    }
                    free(value_w_comma);
                    if (++per_fan[t] > max_per_fan)
                        ++max_per_fan;
                    value = strtok(NULL, ",");
                }
            }
        }
    }
    fclose(fp);

    if (count_only) {
        free(total[SYS]);
        free(total[HDD]);

        syslog(LOG_NOTICE, "Controlling %d %s by monitoring %d system %s and %d %s",
          count_fans, (count_fans == 1) ? "fan" : "fans", count_total[SYS],
          (count_total[SYS] == 1) ? "sensor" : "sensors", count_total[HDD],
          (count_total[HDD] == 1) ? "hard drive" : "hard drives");
    }

    return 0;
}

static inline int sgio_send(int fd, unsigned char cmd, unsigned char *rv) {
    unsigned char cdb[SG_ATA_16_LEN], sb[32], *desc, status, error;
    sg_io_hdr_t io_hdr;

    memset(&cdb, 0, sizeof(cdb));
    memset(&sb, 0, sizeof(sb));
    memset(&io_hdr, 0, sizeof(io_hdr));

    cdb[0] = SG_ATA_16;
    cdb[1] = SG_ATA_PROTO_NON_DATA;
    cdb[2] = SG_CDB2_CHECK_COND;
    cdb[13] = ATA_USING_LBA;
    cdb[14] = cmd;

    io_hdr.cmd_len = SG_ATA_16_LEN;
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sb);
    io_hdr.dxfer_direction = SG_DXFER_NONE;
    io_hdr.dxfer_len = 0;
    io_hdr.dxferp = NULL;
    io_hdr.cmdp = cdb;
    io_hdr.sbp = sb;
    io_hdr.pack_id = 0;
    io_hdr.timeout = 500; // Milliseconds

    if (ioctl(fd, SG_IO, &io_hdr) == -1) {
        syslog(LOG_ERR, "ioctl() failed (cmd %u, %s)\n", cmd, strerror(errno));
        return -1;
    }

    desc = sb + 8;
    status = desc[13];
    error = desc[3];
    if (rv)
        *rv = desc[5];

    if (status & (ATA_STAT_ERR | ATA_STAT_DRQ)) {
        syslog(LOG_ERR, "SG_IO command %u failed (status %u, error %u)\n", cmd, status, error);
        return -1;
    }

    return 0;
}

static inline char get_hdd_status(char *name) {
    int fd, ret;
    unsigned char state;

    if ((fd = open(name, O_RDONLY)) < 0) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }

    ret = 1;
    if (sgio_send(fd, ATA_OP_CHECKPOWERMODE1, &state) &&
            sgio_send(fd, ATA_OP_CHECKPOWERMODE2, &state))
        ret = 0;
    close(fd);

    return (state == 0) ? 0 : 1; // 0 = Sleeping, 1 = Running
}

static inline short get_hdd_temp(char *name) {
    short hdd_temp;
    int fd;
    unsigned char buffer[SMART_BUFFER_SIZE] = "";
    unsigned char sense_buffer[SMART_SENSE_BUFFER_SIZE];
    unsigned char smart_read_cdb[SMART_CMD_LENGTH] =
            {0xa1, 0x0c, 0x0e, 0xd0, 1, 0, 0x4f, 0xc2, 0, 0xb0, 0, 0};

    sg_io_hdr_t io_hdr;

    if ((fd = open(name, O_RDONLY)) < 0) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }

    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.cmd_len = SMART_CMD_LENGTH;
    io_hdr.mx_sb_len = SMART_SENSE_BUFFER_SIZE;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    io_hdr.dxfer_len = SMART_BUFFER_SIZE;
    io_hdr.dxferp = buffer;
    io_hdr.cmdp = smart_read_cdb;
    io_hdr.sbp = sense_buffer;
    io_hdr.timeout = 500; // Milliseconds

    if (ioctl(fd, SG_IO, &io_hdr) < 0) {
        syslog(LOG_ERR, "ioctl() call for reading temperature failed\n");
        close(fd);
        return -1;
    }

    for (register int i = 2; i < 361; i += 12)
        if ((int)buffer[i] == 194) {
            hdd_temp = ((long long int)((buffer[i+5])|
                    (buffer[i+6]<<8)|
                    (buffer[i+7]<<16)|
                    (buffer[i+8]<<24)|
                    ((long long int)buffer[i+9]<<32)|
                    ((long long int)buffer[i+10]<<40))) & 0xFF;
            break;
        }
    close(fd);

    return hdd_temp;
}

static inline short get_sys_temp(char *name) {
    FILE *fp;
    int sys_temp;

    if ((fp = fopen(name, "r")) == NULL) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }
    fscanf(fp, "%d", &sys_temp);
    fclose(fp);

    return sys_temp / 1000;
}

static inline short get_temp(short i, short type) {
    struct stat device;
    short j, k, sensor_temp, max_temp = 0;
    short (*get_sensor_temp[]) (char *) = {get_sys_temp, get_hdd_temp};

    for (j = 0; j < fan[i].count_scan[type]; j++) {
        // Check if temperature is stored in buffer
        for (k = 0; k < count_buffer; k++)
            if (strcmp(buf[k].name, scan[i][type][j]) == 0) {
                sensor_temp = buf[k].temp;
                break;
            }

        if (k == count_buffer) {
            if (type == SYS || (type == HDD &&
                    !stat(scan[i][type][j], &device) &&
                    (device.st_mode & S_IFMT) == S_IFBLK &&
                    get_hdd_status(scan[i][type][j])))
                // Get temperature from system sensor or hard drive
                sensor_temp = get_sensor_temp[type](scan[i][type][j]);
            else
                sensor_temp = 0;

            // Write temperature to buffer
            strcpy(buf[count_buffer].name, scan[i][type][j]);
            buf[count_buffer++].temp = sensor_temp;
        }

        if (sensor_temp > max_temp)
            max_temp = sensor_temp;
        else if (sensor_temp < 0)
            return -1;
    }

    return max_temp;
}

static inline unsigned char calc_pwm(char t, int i, short temp) {
    unsigned char pwm = 0;
    short j = 0;

    while (temp >= data_pwm[t][i][j].temp) {
        pwm = data_pwm[t][i][j].pwm;
        if (++j >= fan[i].count_step[t])
            break;
    }

    if (fan[i].interpolate[t] && j > 0 && j < fan[i].count_step[t])
        pwm = ((float)data_pwm[t][i][j].pwm - data_pwm[t][i][j-1].pwm) /
            (data_pwm[t][i][j].temp - data_pwm[t][i][j-1].temp) *
            (temp - data_pwm[t][i][j-1].temp) + data_pwm[t][i][j-1].pwm + 0.5;

    return pwm;
}

static inline int control_fan_speed(void) {
    unsigned char new_pwm;
    char sw_checked;
    short i, type, temp;

    next_check = 0;
    count_buffer = 0;
    for (i = 0; i < count_fans; i++) {
        sw_checked = 0;
        for (type = 0; type < 2; type++) {
            if (fan[i].control[type] && fan[i].next_check[type] <= now) {

                // Get relevant temperature for specific fan and sensor type
                temp = get_temp(i, type);

                if (temp < 0 && calc_pwm(type, i, temp) < fan[i].error_pwm[type])
                    // An error occured, set fan speed value accordingly
                    fan[i].pwm[type] = fan[i].error_pwm[type];
                else if ((temp > fan[i].temp[type] ||
                        (fan[i].hyst[type] <= fan[i].temp[type] - temp &&
                        fan[i].min_decr_time <= now)) &&
                        (temp != fan[i].temp[type] || first_check)) {
                    fan[i].pwm[type] = calc_pwm(type, i, temp);
                    fan[i].temp[type] = temp;
                }

                // Set next check time for this fan and type
                while (fan[i].next_check[type] <= now)
                    fan[i].next_check[type] += fan[i].interval[type];

                sw_checked = 1;
            }

            // Set time this function needs to be called again
            if (fan[i].control[type] && (next_check == 0 ||
                    next_check > fan[i].next_check[type]))
                next_check = fan[i].next_check[type];
        }

        if (!sw_checked)
            continue;

        if (fan[i].pwm[SYS] > fan[i].pwm[HDD])
            new_pwm = fan[i].pwm[SYS];
        else
            new_pwm = fan[i].pwm[HDD];

        if (new_pwm == fan[i].actual_pwm && !first_check)
            continue;
        else if (new_pwm < fan[i].actual_pwm)
            // Check if decrease delay time has passed
            if (now < fan[i].min_decr_time)
                continue;
            else if (new_pwm == 0)
                // Check if stop delay time has passed
                if (now < fan[i].min_stop_time)
                    if (fan[i].idle_pwm != 0 &&
                            fan[i].idle_pwm != fan[i].actual_pwm)
                        // Set fan speed to idle
                        new_pwm = fan[i].idle_pwm;
                    else
                        continue;

        // Set new fan speed
        if (write_fan(fan[i].pwm_write, new_pwm) != 0)
            continue;
        if (fan[i].loglevel >= 1 && first_check)
            syslog(LOG_NOTICE, "%s: PWM set to %d (SYS: %d°%s, HDD: %d°%s)\n",
              fan[i].name, new_pwm, fan[i].temp[SYS],
              (fan[i].pwm[SYS] < new_pwm) ? "C" : "C*", fan[i].temp[HDD],
              (fan[i].pwm[HDD] < new_pwm) ? "C" : "C*");
        else if (fan[i].loglevel >= 1)
            syslog(LOG_NOTICE, "%s: PWM changed from %d to %d (SYS: %d°%s, HDD: %d°%s)\n",
              fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].temp[SYS],
              (fan[i].pwm[SYS] < new_pwm) ? "C" : "C*", fan[i].temp[HDD],
              (fan[i].pwm[HDD] < new_pwm) ? "C" : "C*");
        if (fan[i].actual_pwm < new_pwm)
            fan[i].min_decr_time = now + fan[i].decr_delay;
        if (fan[i].actual_pwm == 0 && new_pwm != 0)
            fan[i].min_stop_time = now + fan[i].stop_delay;
        fan[i].actual_pwm = new_pwm;
    }
    if (first_check)
        first_check = 0;

    return 0;
}

int main (int argc, char **argv) {
    openlog("fwcontrol", LOG_PID | LOG_CONS | LOG_NDELAY, LOG_LOCAL0);
    syslog(LOG_NOTICE, "fwcontrol started ...\n");

    // Determine number of configured fans, system sensors and hard drives and
    // allocate memory accordingly
    read_fan_conf(1);

    fan = calloc(count_fans, sizeof(struct s_fan));
    buf = calloc(count_total[SYS] + count_total[HDD], sizeof(struct s_buf));
    data_pwm = calloc(2, sizeof(struct s_temp_pwm));
    for (int h = 0; h < 2; h++) {
        data_pwm[h] = calloc(count_fans, sizeof(data_pwm[0]));
        for (int r = 0; r < count_fans; r++)
            data_pwm[h][r] = calloc(MAX_STEP, sizeof(data_pwm[0][0]));
    }
    scan = calloc(count_fans, sizeof(char***));
    for (int h = 0; h < count_fans; h++) {
        scan[h] = calloc(2, sizeof(char**));
        for (int r = 0; r < 2; r++) {
            scan[h][r] = calloc(max_per_fan, sizeof(char*));
            for (int s = 0; s < max_per_fan; s++)
                scan[h][r][s] = calloc(MAX_SENSOR_LENGTH + 1, sizeof(char));
        }
    }

    // Read fan configuration
    read_fan_conf(0);
    while (1) {
        now = time(NULL);
        control_fan_speed();
        sleep(next_check - now);
    }
    syslog(LOG_NOTICE, "fwcontrol terminated.\n");
    closelog();
    return EXIT_SUCCESS;
}

Configuration file /etc/fwcontrol.conf

# Configuration file for fwcontrol
#
# Parameter         Value         Description
# ================+=============+===============================================
# control_by_sys  | 0 or 1      | If set to 1 the fan is controlled by system
#                 |             | temperatures like CPU/GPU or other internal
#                 |             | sensors.
# control_by_hdd  | 0 or 1      | If set to 1 the fan is controlled by hard
#                 |             | drive temperatures.
# pwm_enable      | filename    | Used to set the fan mode.
# pwm_write       | filename    | Used to change the fan speed.
# input_sys       | filename(s) | Defines which system temperature sensors
#                 |             | should be checked. Values can be written comma
#                 |             | separated and/or in multiple lines:
#                 |             | input_sys=/path1/filename1,/path2/filename2...
#                 |             | input_sys=/path6/filename6,/path7/filename7...
#                 |             | ...
# temp_pwm_sys    | temp,pwm    | Defines the PWM value for a given system
#                 |             | temperature: temp1,pwm1,temp2,pwm2,...
# interpolate_sys | 0 or 1      | If set to 1 the program calculates
#                 |             | intermediate values between the data points
#                 |             | defined in "temp_pwm_sys".
# hyst_sys        | temp        | System temperature must be decreased by at
#                 |             | least this value until fan speed can be
#                 |             | reduced.
# input_hdd       | device(s)   | Defines which hard drives should be checked.
#                 |             | Values can be written comma separated and/or
#                 |             | in multiple lines:
#                 |             | input_hdd=/dev/<hdd1>,/dev/<hdd2>...
#                 |             | input_hdd=/dev/<hhd6>,/dev/<hdd7>...
#                 |             | ...
# temp_pwm_hdd    | temp,pwm    | Defines the PWM value for a given hard drive
#                 |             | temperature: temp1,pwm1,temp2,pwm2,...
# interpolate_hdd | 0 or 1      | If set to 1 the program calculates
#                 |             | intermediate values between the data points
#                 |             | defined in "temp_pwm_hdd".
# hyst_hdd        | temp        | Hard drive temperature must be decreased by at
#                 |             | least this value until fan speed can be
#                 |             | reduced.
# decrease_delay  | seconds     | Defines how many seconds must have passed
#                 |             | since last speed increase until the speed can
#                 |             | be decreased.
# stop_delay      | seconds     | Defines how many seconds must have passed
#                 |             | since start of the fan until the fan can be
#                 |             | stopped.
# idle_pwm        | 0-255       | If the fan should be stopped but stop delay
#                 |             | time has not yet passed, then the fan speed
#                 |             | is set to this value.
# error_pwm_sys   | 0-255       | If something goes wrong when reading the
#                 |             | system temperatures then the fan speed is set
#                 |             | to this value (default is 255).
# error_pwm_hdd   | 0-255       | If something goes wrong when reading the hard
#                 |             | drive temperatures then the fan speed is set
#                 |             | to this value (default is 255).
# interval_sys    | seconds     | Check system temperatures every n seconds.
# interval_hdd    | seconds     | Check hard drive temperatures every n seconds.
# loglevel        | 0 or 1      | If set to 1 then every speed change will be
#                 |             | written to the logfile.
# ================+=============+===============================================
[CPU Cooler]
control_by_sys=1
control_by_hdd=0
pwm_enable=/sys/class/hwmon/hwmon2/pwm2_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm2
input_sys=/sys/class/hwmon/hwmon1/temp1_input
temp_pwm_sys=0,34,38,34,59,160,70,255
interpolate_sys=1
hyst_sys=5
decrease_delay=30
stop_delay=120
idle_pwm=34
interval_sys=3
loglevel=1

[Front left]
control_by_sys=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm3_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm3
input_sys=/sys/class/hwmon/hwmon1/temp1_input
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
temp_pwm_sys=0,58,38,58,39,76,40,108,41,122,51,202,60,255
temp_pwm_hdd=35,108,36,144,37,180,38,224,39,255
interpolate_sys=1
interpolate_hdd=0
hyst_sys=5
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=58
interval_sys=15
interval_hdd=60
loglevel=1

[Front right]
control_by_sys=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm4_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm4
input_sys=/sys/class/hwmon/hwmon1/temp1_input
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
temp_pwm_sys=60,255
temp_pwm_hdd=34,56,35,64,36,88,37,138,38,178,39,255
interpolate_sys=0
interpolate_hdd=0
hyst_sys=5
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=56
interval_sys=15
interval_hdd=60
loglevel=1

[Rear left]
control_by_sys=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm1_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm1
input_sys=/sys/class/hwmon/hwmon1/temp1_input
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
temp_pwm_sys=0,70,38,70,39,88,40,106,41,124,51,224,60,255
temp_pwm_hdd=35,88,36,106,38,146,39,192,40,255
interpolate_sys=1
interpolate_hdd=0
hyst_sys=5
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=70
interval_sys=15
interval_hdd=60
loglevel=1

[Rear right]
control_by_sys=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm5_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm5
input_sys=/sys/class/hwmon/hwmon1/temp1_input
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-2
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-3
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-4
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-5
input_hdd=/dev/disk/by-path/pci-0000:00:1f.2-ata-6
temp_pwm_sys=50,72,58,152,60,255
temp_pwm_hdd=37,104,38,142,39,188,40,255
interpolate_sys=1
interpolate_hdd=0
hyst_sys=5
hyst_hdd=2
decrease_delay=30
stop_delay=120
idle_pwm=72
interval_sys=15
interval_hdd=60
loglevel=1

Start fwcontrol during boot

Option 1: Using init script /etc/init.d/fwcontrol

#! /bin/sh

### BEGIN INIT INFO
# Provides:             fwcontrol
# Required-Start:       $syslog $remote_fs
# Required-Stop:        $syslog $remote_fs
# Default-Start:        2 3 4 5
# Default-Stop:
# Short-Description:    fwcontrol
# Description:          Control of fan speeds
### END INIT INFO

NAME="fwcontrol"
DESC="Control of fan speeds"
DAEMON=/usr/sbin/${NAME}
FWUSER=root

PATH=/sbin:/bin:/usr/sbin:/usr/bin

test -f $DAEMON || exit 0

mkdir -p /var/run
PIDFILE=/var/run/${NAME}.pid

RETVAL=0
case "$1" in
   start)
      echo -n "Starting ${DESC}: "
      start-stop-daemon --start --oknodo --quiet --background --make-pidfile --pidfile ${PIDFILE} --chuid ${FWUSER} --exec ${DAEMON}
      RETVAL=$?
      echo "${NAME}"
      ;;
   stop)
      echo -n "Stopping ${DESC}: "
      start-stop-daemon --stop --oknodo --quiet --pidfile ${PIDFILE} --remove-pidfile --chuid ${FWUSER} --exec ${DAEMON}
      RETVAL=$?
      echo "${NAME}"
      ;;
   restart|force-reload)
      $0 stop
      sleep 5
      $0 start
      RETVAL=$?
      ;;
   *)
      echo "Usage: /etc/init.d/${NAME} {start|stop|restart|force-reload}" >&2
      exit 1
      ;;
esac
exit $RETVAL

Option 2: Using systemd service /etc/systemd/system/fwcontrol.service

[Unit]
Description=fwcontrol daemon

[Service]
Type=simple
Restart=on-failure
ExecStartPre=-/bin/bash -c '/usr/bin/killall -q fwcontrol; exit 0'
ExecStart=/usr/sbin/fwcontrol
ExecStop=/usr/bin/killall fwcontrol

[Install]
WantedBy=multi-user.target

Logfile Configuration

File /etc/rsyslog.d/10-fwcontrol.conf:

local0.*                        /var/log/fwcontrol.log
& stop

Logfile rotation configured in file /etc/logrotate.d/fwcontrol:

/var/log/fwcontrol.log {
        weekly
        rotate 4
        compress
        delaycompress
        notifempty
        missingok
}

Fancontrol Documentation