static void cleanup(void) { rmsckif(); if(haskey(& rc, "unix") && getpid() == ppid) unlink(value(& rc, "unix")); empty(& data); empty(& rc); empty(& track); freelist(& playlist); if(current_station) { free(current_station); current_station = NULL; } if(subfork) waitpid(subfork, NULL, 0); dump_queue(); /* Clean cache. */ if(!access(rcpath("cache"), R_OK | W_OK | X_OK)) { const char * cache = rcpath("cache"); DIR * directory = opendir(cache); if(directory != NULL) { time_t expiry = 24 * 60 * 60; /* Expiration after 24h. */ struct dirent * entry; struct stat status; char path[PATH_MAX]; if(haskey(& rc, "expiry")) expiry = atoi(value(& rc, "expiry")); while((entry = readdir(directory)) != NULL) { snprintf(path, sizeof(path), "%s/%s", cache, entry->d_name); if(!stat(path, & status)) { if(status.st_mtime < (time(NULL) - expiry)) { unlink(path); } } } closedir(directory); } } if(playfork) kill(playfork, SIGUSR1); }
/* Write the tracks from the scrobble queue to a file. */ void dumpqueue(int overwrite) { const char * path = rcpath("scrobble-cache"); if(path != NULL) { FILE * fd = fopen(path, overwrite ? "w" : "a+"); if(fd != NULL) { unsigned n, x; for(n = 0; n < qlength; ++n) { const char keys [] = "atirolbnm"; for(x = 0; x < sizeof(keys) - 1; ++x) { char key[2] = { keys[x], 0 }, * encoded = NULL; encode(value(& queue[n], key), & encoded); fprintf(fd, "%c=%s;", keys[x], encoded); free(encoded); } fputc(10, fd); } fclose(fd); } else { fprintf(stderr, "Couldn't open scrobble cache. %s.\n", ERROR); } } else { fprintf(stderr, "Can't find suitable scrobble queue path.\n"); } sliceq(qlength); }
/* Check if a given artist is banned completely (it appears in "~/.shell-fm/autoban"). */ int banned(const char * artist) { FILE * fd; signed match = 0; char * line = NULL; unsigned int size = 0; if(!artist) return 0; if(!(fd = fopen(rcpath("autoban"), "r"))) return 0; while(!feof(fd) && !match) { char * ptr; if(!getln(& line, & size, fd)) continue; if(strlen(line) > 1) { if((ptr = strrchr(line, 10)) != NULL) * ptr = (char) 0; match = (strlen(line) == strlen(artist)) && !strncasecmp(line, artist, strlen(line)); } } if(line) free(line); fclose(fd); return match; }
void handle_keyboard_input() { int key, result; char customkey[8] = { 0 }, * marked = NULL; fflush(stderr); if((key = fgetc(stdin)) == -1) return; if(key == 27) { int ch; while((ch = fgetc(stdin)) != -1 && !strchr("ABCDEFGHMPQRSZojmk~", ch)); return; } snprintf(customkey, sizeof(customkey), "key0x%02X", key & 0xFF); if(haskey(& rc, customkey)) run(meta(value(& rc, customkey), M_SHELLESC, & track)); switch(key) { case 'l': puts(rate("L") ? "Loved." : "Sorry, failed."); break; case 'U': puts(rate("U") ? "Unloved." : "Sorry, failed."); break; case 'B': puts(rate("B") ? "Banned." : "Sorry, failed."); fflush(stdout); enable(INTERRUPTED); kill(playfork, SIGUSR1); break; case 'n': rate("S"); break; case 'q': if(haskey(& rc, "delay-change")) { delayquit = !delayquit; if(delayquit) fputs("Going to quit soon.\n", stderr); else fputs("Delayed quit cancelled.\n", stderr); } break; case 'Q': quit(); case 'i': if(playfork) { const char * path = rcpath("i-template"); if(path && !access(path, R_OK)) { char ** template = slurp(path); if(template != NULL) {
void interface(int interactive) { if(interactive) { int key, result; char customkey[8] = { 0 }, * marked = NULL; canon(0); fflush(stderr); if((key = fetchkey(1000000)) == -1) return; if(key == 27) { int ch; while((ch = fetchkey(100000)) != -1 && !strchr("ABCDEFGHMPQRSZojmk~", ch)); return; } switch(key) { case 'l': puts(rate("L") ? "Loved." : "Sorry, failed."); break; case 'U': puts(rate("U") ? "Unloved." : "Sorry, failed."); break; case 'B': puts(rate("B") ? "Banned." : "Sorry, failed."); kill(playfork, SIGUSR1); break; case 'n': rate("S"); break; case 'Q': unlink(rcpath("session")); exit(EXIT_SUCCESS); case 'i': if(playfork) { const char * path = rcpath("i-template"); if(path && !access(path, R_OK)) { char ** template = slurp(path); if(template != NULL) {
/* Ban an artist by adding it to the autoban file. */ int autoban(const char * artist) { FILE * fd; const char * file = rcpath("autoban"); if(!artist) return 0; if(!(fd = fopen(file, "a"))) { printf("Sorry, %s could not be written.\n", file); return 0; } fprintf(fd, "%s\n", artist); fclose(fd); return !0; }
void loadqueue(int overwrite) { const char * path = rcpath("scrobble-cache"); if(overwrite) sliceq(qlength); if(path != NULL) { FILE * fd = fopen(path, "r"); if(fd != NULL) { while(!feof(fd)) { char * line = NULL; unsigned size = 0; if(getln(& line, & size, fd) >= 2) { unsigned n = 0; char ** splt = split(line, ";\n", & n); struct hash track; memset(& track, 0, sizeof(struct hash)); while(n--) { char key[2] = { 0 }, * value = NULL; sscanf(splt[n], "%c=", & key[0]); if(strchr("atirolbnm", key[0])) { decode(splt[n] + 2, & value); set(& track, key, value); free(value); } free(splt[n]); } free(splt); queue = realloc(queue, sizeof(struct hash) * (++qlength)); assert(queue != NULL); memcpy(& queue[qlength - 1], & track, sizeof(struct hash)); } if(line) free(line); } fclose(fd); } } }
int main(int argc, char ** argv) { int option, nerror = 0, background = 0, have_socket = 0; time_t pauselength = 0; char * proxy; opterr = 0; /* Create directories. */ makercd(); /* Load settings from ~/.shell-fm/shell-fm.rc. */ settings(rcpath("shell-fm.rc"), !0); /* Enable discovery by default if it is set in configuration. */ if(haskey(& rc, "discovery")) enable(DISCOVERY); /* Disable RTP if option "no-rtp" is set to something. */ if(haskey(& rc, "no-rtp")) disable(RTP); /* If "daemon" is set in the configuration, enable daemon mode by default. */ if(haskey(& rc, "daemon")) background = !0; /* Get proxy environment variable. */ if((proxy = getenv("http_proxy")) != NULL) set(& rc, "proxy", proxy); /* Parse through command line options. */ while(-1 != (option = getopt(argc, argv, ":dbhi:p:D:y:"))) switch(option) { case 'd': /* Daemonize. */ background = !background; break; case 'i': /* IP to bind network interface to. */ set(& rc, "bind", optarg); break; case 'p': /* Port to listen on. */ if(atoi(optarg)) set(& rc, "port", optarg); else { fputs("Invalid port.\n", stderr); ++nerror; } break; case 'b': /* Batch mode */ batch = !0; break; case 'D': /* Path to audio device file. */ set(& rc, "device", optarg); break; case 'y': /* Proxy address. */ set(& rc, "proxy", optarg); break; case 'h': /* Print help text and exit. */ help(argv[0], 0); break; case ':': fprintf(stderr, "Missing argument for option -%c.\n\n", optopt); ++nerror; break; case '?': default: fprintf(stderr, "Unknown option -%c.\n", optopt); ++nerror; break; } /* The next argument, if present, is the lastfm:// URL we want to play. */ if(optind > 0 && optind < argc && argv[optind]) { const char * station = argv[optind]; set(& rc, "default-radio", station); } if(nerror) help(argv[0], EXIT_FAILURE); #ifdef EXTERN_ONLY /* Abort if EXTERN_ONLY is defined and no extern command is present */ if(!haskey(& rc, "extern")) { fputs("Can't continue without extern command.\n", stderr); exit(EXIT_FAILURE); } #else #ifndef LIBAO if(!haskey(& rc, "device")) set(& rc, "device", "/dev/audio"); #endif #endif if(!background) { puts("Shell.FM v" PACKAGE_VERSION ", (C) 2006-2010 by Jonas Kramer"); puts("Published under the terms of the GNU General Public License (GPL)."); #ifndef TUXBOX puts("\nPress ? for help.\n"); #else puts("Compiled for the use with Shell.FM Wrapper.\n"); #endif fflush(stdout); } /* Open a port so Shell.FM can be controlled over network. */ if(haskey(& rc, "bind")) { int port = 54311; if(haskey(& rc, "port")) port = atoi(value(& rc, "port")); if(tcpsock(value(& rc, "bind"), (unsigned short) port)) have_socket = !0; } /* Open a UNIX socket for local "remote" control. */ if(haskey(& rc, "unix") && unixsock(value(& rc, "unix"))) have_socket = !0; /* We can't daemonize if there's no possibility left to control Shell.FM. */ if(background && !have_socket) { fputs("Can't daemonize without control socket.\n", stderr); exit(EXIT_FAILURE); } /* Ask for username/password if they weren't specified in the .rc file. */ if(!haskey(& rc, "password")) { char * password; if(!haskey(& rc, "username")) { char username[256] = { 0 }; struct prompt prompt = { .prompt = "Login: "******"USER"), .history = NULL, .callback = NULL, }; strncpy(username, readline(& prompt), 255); set(& rc, "username", username); } if(!(password = getpass("Password: "******"password", password); }
/* Prompt for a Last.FM radio station URI, providing kind of smart tab completion and a history. No return value, playback of the URI is started directly from here. */ void radioprompt(const char * prompt) { char * url, * decoded = NULL; struct prompt setup = { .prompt = prompt, .line = NULL, .history = uniq(slurp(rcpath("radio-history"))), .callback = radiocomplete, }; /* Get overall top tags. */ overall = overalltags(); /* Get user, friends and neighbors. */ users = neighbors(value(& rc, "username")); users = merge(users, friends(value(& rc, "username")), 0); users = append(users, value(& rc, "username")); /* Get top artists. */ artists = topartists(value(& rc, "username")); /* Read the line. */ url = readline(& setup); /* Free everything. */ purge(users); purge(artists); purge(overall); overall = users = artists = NULL; if(setup.history) purge(setup.history); decode(url, & decoded); station(decoded); free(decoded); } /* Callback for the radio prompt for smart completion of radio URIs. */ int radiocomplete(char * line, const unsigned max, int changed) { unsigned length = strlen(line), nsplt = 0, slash = 0, nres = 0; const char * match; char ** splt, * types [] = { "user", "usertags", "artist", "globaltags", "play", NULL }; /* Remove leading "lastfm://", if found. */ if(!strncasecmp(line, "lastfm://", 9)) { memmove(line, line + 9, 9); memset(line + 9, 0, max - (length -= 9)); } if(length > 0 && line[length - 1] == '/') { slash = !0; changed = !0; } splt = split(line, "/", & nsplt); if(!nsplt) { free(splt); return 0; } switch(nsplt + (slash ? 1 : 0)) { /* First level completions (user, usertags, artists, ...) */ case 1: /* Get next match from first level chunks and fill it in. */ if((match = nextmatch(types, changed ? splt[0] : NULL, & nres)) != NULL) { snprintf(line, max, "%s%s", match, nres == 1 ? "/" : ""); } break; /* Second level completions (user/$USER, globaltags/$TAG, ...) */ case 2: /* For URIs like "{user,usertags}/...". */ if(!strcmp(splt[0], "user") || !strcmp(splt[0], "usertags")) { /* Get next match for 2nd level user chunk (user) and fill it in. */ match = nextmatch(users, changed ? (slash ? "" : splt[1]) : NULL, & nres); if(match) snprintf(line, max, "%s/%s%s", splt[0], match, nres == 1 ? "/" : ""); } /* For URIs like "artist/...". */ else if(!strcmp(splt[0], "artist")) { /* Get next artist match for 2nd level. */ match = nextmatch(artists, changed ? (slash ? "" : splt[1]) : NULL, & nres); if(match) snprintf(line, max, "%s/%s%s", splt[0], match, nres == 1 ? "/" : ""); } /* For URIs like "globaltags/...". Simply tag completion applied here. */ else if(!strcmp(splt[0], "globaltags")) { char * lastchunk = strrchr(line, '/') + 1; popular = overalltags(); tagcomplete(lastchunk, max - (lastchunk - line), changed); purge(popular); } break; /* Third level completions (artist/$ARTIST/fans, ...) */ case 3: /* "user/$USER/{personal,neighbors,loved,recommended,playlist}" */ if(!strcmp(splt[0], "user")) { char * radios [] = { "personal", "neighbours", "loved", "recommended", "playlist", NULL }; /* Get next match for 3rd level chunk and fill it in. */ match = nextmatch(radios, changed ? (slash ? "" : splt[2]) : NULL, NULL); snprintf(line, max, "%s/%s/%s", splt[0], splt[1], match ? match : splt[2]); } /* "artist/$ARTIST/{fans,similarartists}" */ else if(!strcmp(splt[0], "artist")) { char * radios [] = { "fans", "similarartists", NULL }; /* Get next match for 3rd level chunk. */ match = nextmatch(radios, changed ? (slash ? "" : splt[2]) : NULL, NULL); snprintf(line, max, "%s/%s/%s", splt[0], splt[1], match ? match : splt[2]); } /* Simple tag completion for "usertags" stations. */ else if(!strcmp(splt[0], "usertags")) { char * lastchunk = strrchr(line, '/') + 1; popular = overalltags(); tagcomplete(lastchunk, max - (lastchunk - line), changed); purge(popular); } break; } while(nsplt--) free(splt[nsplt]); free(splt); return !0; }
int main(int argc, char ** argv) { int option, nerror = 0, background = 0, quiet = 0, have_socket = 0; time_t pauselength = 0; char * proxy; opterr = 0; /* Create directories. */ makercd(); /* Load settings from ~/.shell-fm/shell-fm.rc. */ settings(rcpath("shell-fm.rc"), !0); /* Enable discovery by default if it is set in configuration. */ if(haskey(& rc, "discovery")) enable(DISCOVERY); /* Disable RTP if option "no-rtp" is set to something. */ if(haskey(& rc, "no-rtp")) disable(RTP); /* If "daemon" is set in the configuration, enable daemon mode by default. */ if(haskey(& rc, "daemon")) background = !0; /* If "quiet" is set in the configuration, enable quiet mode by default. */ if(haskey(& rc, "quiet")) quiet = !0; /* Get proxy environment variable. */ if((proxy = getenv("http_proxy")) != NULL) set(& rc, "proxy", proxy); /* Parse through command line options. */ while(-1 != (option = getopt(argc, argv, ":dbhqi:p:D:y:Y:"))) switch(option) { case 'd': /* Daemonize. */ background = !background; break; case 'i': /* IP to bind network interface to. */ set(& rc, "bind", optarg); break; case 'p': /* Port to listen on. */ if(atoi(optarg)) set(& rc, "port", optarg); else { fputs("Invalid port.\n", stderr); ++nerror; } break; case 'b': /* Batch mode */ batch = !0; break; case 'D': /* Path to audio device file. */ set(& rc, "device", optarg); break; case 'y': /* Proxy address. */ set(& rc, "proxy", optarg); break; case 'Y': /* SOCKS proxy address. */ set(& rc, "socks-proxy", optarg); break; case 'q': /* Quiet mode. */ quiet = !quiet; break; case 'h': /* Print help text and exit. */ help(argv[0], 0); break; case ':': fprintf(stderr, "Missing argument for option -%c.\n\n", optopt); ++nerror; break; case '?': default: fprintf(stderr, "Unknown option -%c.\n", optopt); ++nerror; break; } /* The next argument, if present, is the lastfm:// URL we want to play. */ if(optind > 0 && optind < argc && argv[optind]) { const char * station = argv[optind]; set(& rc, "default-radio", station); } if(nerror) help(argv[0], EXIT_FAILURE); #ifdef EXTERN_ONLY /* Abort if EXTERN_ONLY is defined and no extern command is present */ if(!haskey(& rc, "extern")) { fputs("Can't continue without extern command.\n", stderr); exit(EXIT_FAILURE); } #else #ifndef LIBAO if(!haskey(& rc, "device")) set(& rc, "device", "/dev/audio"); #endif #endif if(!background && !quiet) { puts("Shell.FM v" PACKAGE_VERSION ", (C) 2006-2012 by Jonas Kramer"); puts("Published under the terms of the GNU General Public License (GPL)."); #ifndef TUXBOX puts("\nPress ? for help.\n"); #else puts("Compiled for the use with Shell.FM Wrapper.\n"); #endif fflush(stdout); } /* Open a port so Shell.FM can be controlled over network. */ if(haskey(& rc, "bind")) { int port = 54311; if(haskey(& rc, "port")) port = atoi(value(& rc, "port")); if(tcpsock(value(& rc, "bind"), (unsigned short) port)) have_socket = !0; } /* Open a UNIX socket for local "remote" control. */ if(haskey(& rc, "unix") && unixsock(value(& rc, "unix"))) have_socket = !0; /* We can't daemonize if there's no possibility left to control Shell.FM. */ if(background && !have_socket) { fputs("Can't daemonize without control socket.\n", stderr); exit(EXIT_FAILURE); } memset(& data, 0, sizeof(struct hash)); memset(& track, 0, sizeof(struct hash)); memset(& playlist, 0, sizeof(struct playlist)); /* Fork to background. */ if(background) { int null; pid_t pid = fork(); if(pid == -1) { fputs("Failed to daemonize.\n", stderr); exit(EXIT_FAILURE); } else if(pid) { exit(EXIT_SUCCESS); } enable(QUIET); /* Detach from TTY */ setsid(); pid = fork(); if(pid > 0) exit(EXIT_SUCCESS); /* Close stdin out and err */ close(0); close(1); close(2); /* Redirect stdin and out to /dev/null */ null = open("/dev/null", O_RDWR); dup(null); dup(null); } ppid = getpid(); atexit(cleanup); load_queue(); /* Set up signal handlers for communication with the playback process. */ signal(SIGINT, forcequit); /* SIGUSR2 from playfork means it detected an error. */ signal(SIGUSR2, playsig); /* Catch SIGTSTP to set pausetime when user suspends us with ^Z. */ signal(SIGTSTP, stopsig); /* Authenticate to the Last.FM server. */ create_session(); if(!background) { struct input keyboard = { 0, KEYBOARD }; register_handle(keyboard); canon(0); atexit(cleanup_term); } /* Play default radio, if specified. */ if(haskey(& rc, "default-radio")) { if(!strcmp(value(& rc, "default-radio"), "last")) { char ** history = load_history(), * last = NULL, ** p; for(p = history; * p != NULL; ++p) { last = * p; } set(& rc, "default-radio", last); purge(history); } station(value(& rc, "default-radio")); } else if(!background) radioprompt("radio url> "); /* The main loop. */ while(!0) { pid_t child; int status, playnext = 0; /* Check if anything died (submissions fork or playback fork). */ while((child = waitpid(-1, & status, WNOHANG | WUNTRACED | WCONTINUED)) > 0) { if(child == subfork) subdead(WEXITSTATUS(status)); else if(child == playfork) { if(WIFSTOPPED(status)) { /* time(& pausetime); */ } else { if(WIFCONTINUED(status)) { signal(SIGTSTP, stopsig); if(pausetime != 0) { pauselength += time(NULL) - pausetime; } } else { playnext = !0; unlinknp(); if(delayquit) { quit(); } } pausetime = 0; } } } /* Check if the playback process died. If so, submit the data of the last played track. Then check if there are tracks left in the playlist; if not, try to refresh the list and stop the stream if there are no new tracks to fetch. Also handle user stopping the stream here. We need to check for playnext != 0 before handling enabled(STOPPED) anyway, otherwise playfork would still be running. */ if(playnext) { playfork = 0; if(enabled(RTP)) { unsigned duration, played, minimum; duration = atoi(value(& track, "duration")); played = time(NULL) - change_time - pauselength; /* Allow user to specify minimum playback length (min. 50%). */ if(haskey(& rc, "minimum")) { unsigned percent = atoi(value(& rc, "minimum")); if(percent < 50) percent = 50; minimum = duration * percent / 100; } else { minimum = duration / 2; } if(duration >= 30 && (played >= 240 || played > minimum)) { debug("adding track to scrobble queue\n"); enqueue(& track); } else { debug("track too short (duration %d, played %d, minimum %d) - not scrobbling\n", duration, played, minimum); } } submit(); /* Check if the user stopped the stream. */ if(enabled(STOPPED) || error) { freelist(& playlist); empty(& track); if(error) { fputs("Playback stopped with an error.\n", stderr); error = 0; } disable(STOPPED); disable(CHANGED); continue; } shift(& playlist); } if(playnext || enabled(CHANGED)) { if(nextstation != NULL) { playnext = 0; disable(CHANGED); if(!station(nextstation)) enable(STOPPED); free(nextstation); nextstation = NULL; } if(!enabled(STOPPED) && !playlist.left) { expand(& playlist); if(!playlist.left) { puts("No tracks left."); playnext = 0; disable(CHANGED); continue; } } if(!playfork) { /* If there was a track played before, and there is a gap configured, wait that many seconds before playing the next track. */ if(playnext && !enabled(INTERRUPTED) && haskey(& rc, "gap")) { int gap = atoi(value(& rc, "gap")); if(gap > 0) sleep(gap); } disable(INTERRUPTED); if(play(& playlist)) { time(& change_time); pauselength = 0; set(& track, "stationURL", current_station); /* Print what's currently played. (Ondrej Novy) */ if(!background) { if(enabled(CHANGED) && playlist.left > 0) { puts(meta("Receiving %s.", M_COLORED, & track)); disable(CHANGED); } if(haskey(& rc, "title-format")) printf("%s\n", meta(value(& rc, "title-format"), M_COLORED, & track)); else printf("%s\n", meta("Now playing \"%t\" by %a.", M_COLORED, & track)); } if(enabled(RTP)) { notify_now_playing(& track); } /* Write track data into a file. */ if(haskey(& rc, "np-file") && haskey(& rc, "np-file-format")) { signed np; const char * file = value(& rc, "np-file"), * fmt = value(& rc, "np-file-format"); unlink(file); if(-1 != (np = open(file, O_WRONLY | O_CREAT, 0644))) { const char * output = meta(fmt, 0, & track); if(output) write(np, output, strlen(output)); close(np); } } if(haskey(& rc, "screen-format")) { const char * term = getenv("TERM"); if(term != NULL && !strncmp(term, "screen", 6)) { const char * output = meta(value(& rc, "screen-format"), 0, & track); printf("\x1Bk%s\x1B\\", output); } } if(haskey(& rc, "term-format")) { const char * output = meta(value(& rc, "term-format"), 0, & track); printf("\x1B]2;%s\a", output); } /* Run a command with our track data. */ if(haskey(& rc, "np-unescaped-cmd")) run(meta(value(& rc, "np-unescaped-cmd"), 0, & track)); if(haskey(& rc, "np-cmd")) run(meta(value(& rc, "np-cmd"), M_SHELLESC, & track)); } else change_time = 0; } if(banned(value(& track, "creator"))) { puts(meta("%a is banned.", M_COLORED, & track)); rate(RATING_BAN); fflush(stdout); } } playnext = 0; if(playfork && change_time && haskey(& track, "duration") && !pausetime) { unsigned duration; signed remain; char remstr[32]; duration = atoi(value(& track, "duration")); remain = (change_time + duration) - time(NULL) + pauselength; snprintf(remstr, sizeof(remstr), "%d", remain); set(& track, "remain", remstr); if(!background) { printf( "%s%c", meta("%r", M_COLORED, & track), // strdup(meta("%v", M_COLORED, & track)), batch ? '\n' : '\r' ); fflush(stdout); } } handle_input(1000000); } return 0; }
int loadrc ( struct hash *** const rc, const char *filename ) { FILE *fp; char fullname[FILENAME_MAX] = { 0 }; /** Failed to build rc path */ if ( NULL == rcpath () ) { exit ( EXIT_FAILURE ); } snprintf ( fullname, FILENAME_MAX, "%s/%s", rcpath (), filename ); fp = fopen ( fullname, "r" ); if ( NULL == fp ) { error ( "Failed to open file '%s': %s", fullname ); return -1; } /** Parse the configuration file */ size_t size = 0; char *line = NULL, *ptr, key[16] = { 0 }, value[256] = { 0 }; register unsigned nline = 0; while ( !feof ( fp ) ) { ++nline; /** Too short */ if ( getline ( &line, &size, fp ) < 4 ) { goto ignore; } /** For free */ ptr = line; /** Trim the blank */ while ( isspace ( *ptr++ ) ); /** Skip comment line */ if ( '#' == *--ptr ) { goto ignore; } /** Parse key value */ if ( 2 != sscanf ( ptr, "%15[^= \t] = %255[^\r\n]", key, value ) ) { warn ( "Skip invalid line: %d, %s", nline, line ); goto ignore; } /** Store the key value */ set ( rc, key, value ); ignore: if ( size ) { free ( line ); size = 0; line = NULL; } } info ( "Load rc(%s) success!", fullname ); return 0; }
int mkrc ( const struct hash ** rc, const char *filename ) { #ifdef __APPLE__ char *string = NULL; #define strdupa(s) ( string = alloca ( strlen ( (s) ) + 1 ), strcpy ( string, (s) ), string ) #endif char fullname[FILENAME_MAX] = { 0 }, *s, *p; snprintf ( fullname, FILENAME_MAX, "%s", rcpath () ); s = strdupa ( filename ); while ( p = strchr ( s, '/' ), p ) { /** Skip 1th '/' */ if ( p - s ) { /** Mark the child directory name */ *p = 0; /** Build the child directory */ snprintf ( fullname, FILENAME_MAX, "%s/%s", ( strdupa ( fullname ) ), s ); if ( mkdir ( fullname, RC_PATH_MODE ) < 0 && EEXIST != errno ) { error ( "Failed to create directory '%s': %s", fullname, strerror ( errno ) ); return -1; } } /** Do next */ s = ++p; } /** Generate the filename */ snprintf ( fullname, FILENAME_MAX, "%s/%s", ( strdupa ( fullname ) ), s ); if ( !ENDWITH ( filename, "/" ) ) { FILE *fp = fopen ( fullname, rc ? "w+" : "a" ); struct hash *item = NULL; struct stat st; if ( NULL == fp ) { error ( "Failed to open '%s': %s", fullname, strerror ( errno ) ); return -1; } fstat ( fileno ( fp ), &st ); if ( !S_ISREG ( st.st_mode ) ) { fclose ( fp ); error ( "File '%s' is not an regular file.", fullname ); return -1; } if ( rc ) { while ( item = ( struct hash * )*rc++, item ) { fprintf ( fp, "%s = %s", item->key, item->value ); fputs ( "\r\n\r\n", fp ); } } fclose ( fp ); } return 0; }