void json_print_status_line(struct status_line *status) { bool first = true; int i = 0; fprintf(stdout, ",["); for (i = 0; i < status->num; ++i) { struct block *block = status->updated_blocks + i; /* full_text is the only mandatory key, skip if empty */ if (!*block->full_text) { bdebug(block, "no text to display, skipping"); continue; } if (!first) fprintf(stdout, ","); else first = false; bdebug(block, "print json"); block_to_json(block); } fprintf(stdout, "]\n"); fflush(stdout); }
void block_reap(struct block *block) { int status, code; if (block->pid <= 0) { bdebug(block, "not spawned yet"); return; } if (waitpid(block->pid, &status, 0) == -1) { berrorx(block, "waitpid(%d)", block->pid); mark_as_failed(block, strerror(errno)); goto close; } code = WEXITSTATUS(status); bdebug(block, "process %d exited with %d", block->pid, code); /* Process successfully reaped, reset the block PID */ block->pid = 0; block_dump_stderr(block); if (code != 0 && code != EXIT_URGENT) { char reason[32]; if (code == EXIT_ERR_INTERNAL) sprintf(reason, "internal error"); else sprintf(reason, "bad exit code %d", code); berror(block, "%s", reason); mark_as_failed(block, reason); goto close; } /* Do not update unless it was meant to terminate */ if (block->interval == INTER_PERSIST) goto close; block_update(block); /* Exit code takes precedence over the output */ if (code == EXIT_URGENT) strcpy(block->updated_props.urgent, "true"); close: if (close(block->out) == -1) berrorx(block, "close stdout"); if (close(block->err) == -1) berrorx(block, "close stderr"); }
static int parse_status_line(FILE *fp, struct status_line *status) { struct block *block = NULL; struct block global; char line[2048]; memset(&global, 0, sizeof(struct block)); while (fgets(line, sizeof(line), fp) != NULL) { int len = strlen(line); if (line[len - 1] != '\n') { error("line \"%s\" is not terminated by a newline", line); return 1; } line[len - 1] = '\0'; switch (*line) { /* Comment or empty line? */ case '#': case '\0': break; /* Section? */ case '[': block = add_block(status); if (!block) return 1; /* Init the block with default settings (if any) */ memcpy(block, &global, sizeof(struct block)); if (parse_section(line, block->name, sizeof(block->name))) return 1; bdebug(block, "new block"); break; /* Property? */ case 'a' ... 'z': if (!block) { debug("no section yet, consider global properties"); block = &global; } if (parse_property(line, block)) return 1; break; /* Syntax error */ default: error("malformated line: %s", line); return 1; } } return duplicate_blocks(status); }
static inline bool need_update(struct block *block) { bool first_time, outdated, signaled, clicked; first_time = outdated = signaled = clicked = false; if (block->last_update == 0) first_time = true; if (block->interval) { const unsigned long now = time(NULL); const unsigned long next_update = block->last_update + block->interval; outdated = ((long) (next_update - now)) <= 0; } if (caughtsig) { signaled = caughtsig == block->signal; clicked = *block->click.button != '\0'; } bdebug(block, "CHECK first_time: %s, outdated: %s, signaled: %s, clicked: %s", first_time ? "YES" : "no", outdated ? "YES" : "no", signaled ? "YES" : "no", clicked ? "YES" : "no"); return first_time || outdated || signaled || clicked; }
static void update_status_line(struct status_line *status) { int i; for (i = 0; i < status->num; ++i) { const struct block *config_block = status->blocks + i; struct block *updated_block = status->updated_blocks + i; /* Skip static block */ if (!*config_block->command) { bdebug(config_block, "no command, skipping"); continue; } /* If a block needs an update, reset and execute it */ if (need_update(updated_block)) { struct click click; /* save click info and restore config values */ memcpy(&click, &updated_block->click, sizeof(struct click)); memcpy(updated_block, config_block, sizeof(struct block)); memcpy(&updated_block->click, &click, sizeof(struct click)); block_update(updated_block); /* clear click info */ memset(&updated_block->click, 0, sizeof(struct click)); } } if (caughtsig > 0) caughtsig = 0; }
static void handle_click(struct status_line *status) { char json[1024] = { 0 }; struct click click = { "" }; char *name, *instance; fread(json, 1, sizeof(json) - 1, stdin); parse_click(json, &name, &instance, &click); debug("got a click: name=%s instance=%s button=%s x=%s y=%s", name, instance, click.button, click.x, click.y); /* find the corresponding block */ if (*name || *instance) { int i; for (i = 0; i < status->num; ++i) { struct block *block = status->updated_blocks + i; if (strcmp(block->name, name) == 0 && strcmp(block->instance, instance) == 0) { memcpy(&block->click, &click, sizeof(struct click)); /* It shouldn't be likely to have several blocks with the same name/instance, so stop here */ bdebug(block, "clicked"); break; } } } }
void bwrite(buffer *buff) { debug(BUFFER_DL, "bwrite(buff:%p) {\n", buff); // mark to be written BS_SET(buff->status, BS_DELAYED); bdebug(1 + BUFFER_DL, buff); debug(BUFFER_DL, "}\n"); }
void block_update(struct block *block) { FILE *child_stdout; int child_status, code; char output[2048], *text = output; if (setup_env(block)) return mark_as_failed(block, "failed to setup env", -1); /* Pipe, fork and exec a shell for the block command line */ child_stdout = popen(block->command, "r"); if (!child_stdout) { berrorx(block, "popen(%s)", block->command); return mark_as_failed(block, "failed to fork", -1); } /* Do not distinguish EOF or error, just read child's output */ memset(output, 0, sizeof(output)); fread(output, 1, sizeof(output) - 1, child_stdout); /* Wait for the child process to terminate */ child_status = pclose(child_stdout); if (child_status == -1) { berrorx(block, "pclose"); return mark_as_failed(block, "failed to wait", -1); } if (!WIFEXITED(child_status)) { berror(block, "child did not exit correctly"); return mark_as_failed(block, "command did not exit", -1); } code = WEXITSTATUS(child_status); if (code != 0 && code != '!') { char reason[1024] = { 0 }; berror(block, "bad exit code %d", code); linecpy(&text, reason, sizeof(reason) - 1); return mark_as_failed(block, reason, code); } /* From here, the update went ok so merge the output */ strncpy(block->urgent, code == '!' ? "true" : "false", sizeof(block->urgent) - 1); linecpy(&text, block->full_text, sizeof(block->full_text) - 1); linecpy(&text, block->short_text, sizeof(block->short_text) - 1); linecpy(&text, block->color, sizeof(block->color) - 1); block->last_update = time(NULL); bdebug(block, "updated successfully"); }
static void block_dump_stderr(struct block *block) { char buf[2048] = { 0 }; /* Note read(2) returns 0 for end-of-pipe */ if (read(block->err, buf, sizeof(buf) - 1) == -1) { berrorx(block, "read stderr"); return; } if (*buf) bdebug(block, "stderr:\n{\n%s\n}", buf); }
static int parse_property(const char *line, struct block *block) { char *equal = strchr(line, '='); const char *property, *value; if (!equal) { berror(block, "malformated property, should be of the form 'key=value'"); return 1; } /* split property and value */ *equal = '\0'; property = line; value = equal + 1; #define PARSE(_name, _size, _type) \ if (strcmp(property, #_name) == 0) { \ strncpy(block->_name, value, _size - 1); \ goto parsed; \ } \ #define PARSE_NUM(_name) \ if (strcmp(property, #_name) == 0) { \ block->_name = atoi(value); \ goto parsed; \ } \ PROTOCOL_KEYS(PARSE); PARSE(command, sizeof(block->command), _); PARSE_NUM(interval); PARSE_NUM(signal); /* TODO some better check for numbers and boolean */ #undef PARSE_NUM #undef PARSE berror(block, "unknown property: \"%s\"", property); return 1; parsed: bdebug(block, "set property %s to \"%s\"", property, value); return 0; }
void block_update(struct block *block) { struct properties *props = &block->updated_props; char buf[2048] = { 0 }; int nr; /* Read a single line for persistent block, everything otherwise */ if (block->interval == INTER_PERSIST) { nr = io_readline(block->out, buf, sizeof(buf)); if (nr < 0) { berror(block, "failed to read a line"); return mark_as_failed(block, "failed to read"); } else if (nr == 0) { berror(block, "pipe closed"); return mark_as_failed(block, "pipe closed"); } } else { /* Note: read(2) returns 0 for end-of-pipe */ if (read(block->out, buf, sizeof(buf) - 1) == -1) { berrorx(block, "read stdout"); return mark_as_failed(block, strerror(errno)); } } /* Reset the defaults and merge the output */ memcpy(props, &block->default_props, sizeof(struct properties)); if (block->format == FORMAT_JSON) block_update_json(block, buf); else block_update_plain_text(block, buf); if (*FULL_TEXT(block) && *LABEL(block)) { static const size_t size = sizeof(props->full_text); char concat[size]; snprintf(concat, size, "%s %s", LABEL(block), FULL_TEXT(block)); strcpy(props->full_text, concat); } bdebug(block, "updated successfully"); }
void json_print_bar(struct bar *bar) { fprintf(stdout, ",[{\"full_text\":\"\"}"); for (int i = 0; i < bar->num; ++i) { struct block *block = bar->blocks + i; /* full_text is the only mandatory key, skip if empty */ if (!*FULL_TEXT(block)) { bdebug(block, "no text to display, skipping"); continue; } print_block(block); } fprintf(stdout, "]\n"); fflush(stdout); }
buffer *bread(int fs, ino_t inode, int block) { debug(BUFFER_DL, "bread(fs:%d, inode:%d, block:%d) {\n", fs, (int)inode, block); buffer *buff = getblk(fs, inode, block); if (!BS_CMP(buff->status, BS_VALID)) { int s = lseek(buff->fd, buff->block * BLKSIZE, SEEK_SET); buff->valid = read(buff->fd, buff->data, BLKSIZE); if (buff->valid == -1) perror("No se pudo leer el archivo"); BS_SET(buff->status, BS_VALID); } bdebug(1 + BUFFER_DL, buff); debug(BUFFER_DL, "} -> %p\n", buff); return buff; }
void block_spawn(struct block *block, struct click *click) { const unsigned long now = time(NULL); int out[2], err[2]; if (!*COMMAND(block)) { bdebug(block, "no command, skipping"); return; } if (block->pid > 0) { bdebug(block, "process already spawned"); return; } if (pipe(out) == -1 || pipe(err) == -1) { berrorx(block, "pipe"); return mark_as_failed(block, strerror(errno)); } if (block->interval == INTER_PERSIST) { if (io_signal(out[0], SIGRTMIN)) return mark_as_failed(block, "event I/O impossible"); } block->pid = fork(); if (block->pid == -1) { berrorx(block, "fork"); return mark_as_failed(block, strerror(errno)); } /* Child? */ if (block->pid == 0) { /* Error messages are merged into the parent's stderr... */ child_setup_env(block, click); child_reset_signals(block); child_redirect_write(block, out, STDOUT_FILENO); child_redirect_write(block, err, STDERR_FILENO); /* ... until here */ child_exec(block); } /* * Note: for non-persistent blocks, no need to set the pipe read end as * non-blocking, since it is meant to be read once the child has exited * (and thus the write end is closed and read is available). */ /* Parent */ if (close(out[1]) == -1) berrorx(block, "close stdout"); if (close(err[1]) == -1) berrorx(block, "close stderr"); block->out = out[0]; block->err = err[0]; if (!click) block->timestamp = now; bdebug(block, "forked child %d at %ld", block->pid, now); }
buffer *getblk(int fs, ino_t inode, int block) { debug(1 + BUFFER_DL, "getblk(fs:%d, inode:%d, block:%d) {\n", fs, (int)inode, block); // wait for semaphore to start while (!semWait(semid)) { // skip signal interruptions if (errno != EINTR) { perror("getblk, semWait"); debug(2 + BUFFER_DL, "ERROR en getblk, semWait."); exit(1); } } // search for specified buffer or return a new one buffer *buff = NULL; while (buff == NULL) { // search hash list for block int pos = bc->header.hash[block % MAXHASH]; buffer *hl_buff = (pos > -1) ? &(bc->buffers[pos]) : NULL; int times = 0; while(pos > -1 && (hl_buff->inode != inode || hl_buff->block != block)) { times ++; if (times == MAXBUFF) { printf("[%d] times: %d ... \n", getpid(), times); int end = pos; do { bdebug(0, &(bc->buffers[pos])); pos = bc->buffers[pos].hashnext; } while (end != pos); exit(0); } pos = hl_buff->hashnext; hl_buff = (pos > -1) ? &(bc->buffers[pos]) : NULL; } // return the buffer if already loaded if (hl_buff != NULL && hl_buff->inode == inode && hl_buff->block == block) { // sleep until the targeted buffer becomes free if (BS_CMP(hl_buff->status, BS_LOCKED)) { bwait(hl_buff); continue; } // take ownership off buffer hl_buff->fd = getfd(inode); hl_buff->pid = getpid(); // lock the found buffer BS_SET(hl_buff->status, BS_LOCKED); // will return found buffer buff = hl_buff; // allocate the block in a new buffer } else { buffer *new_buff = getfree(); // sleep until any buffer becomes free if (new_buff == NULL) { bwait(NULL); continue; } // asynchronous write buffer to disk while (BS_CMP(new_buff->status, BS_DELAYED)) { basyncwrite(new_buff); // wait for free buffer to be written usleep(1000); } // set new buffer properties and proper hash list drophash(new_buff); new_buff->valid = 0; new_buff->inode = inode; new_buff->block = block; new_buff->fd = getfd(inode); new_buff->pid = getpid(); rehash(new_buff); // lock the new buffer and mark it as not valid BS_SET(new_buff->status, BS_LOCKED); BS_CLR(new_buff->status, BS_VALID); // will return the new buffer buff = new_buff; } } // extract the buffer from free list setused(buff); // free the semaphore if (!semSignal(semid)) { perror("getblk, semSignal"); debug(2 + BUFFER_DL, "ERROR en getblk, semSignal."); exit(1); } bdebug(2 + BUFFER_DL, buff); debug(1 + BUFFER_DL, "} -> %p\n", buff); return buff; }