1 /* By Laurent Coustet (c) 2005 */
2 
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <errno.h>
6 
7 #ifndef WIN32
8   #include <arpa/inet.h>
9 #endif
10 
11 #include <string.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <dirent.h>
15 #include <ctype.h>
16 #include <sys/stat.h>
17 
18 #ifndef WIN32
19  #include <regex.h>
20  #include <locale.h>
21 #endif
22 
23 #include "cpige.h"
24 #include "icy.h"
25 #include "id3.h"
26 #include "pool.h"
27 #include "tool.h"
28 #include "mytime.h"
29 #include "debug.h"
30 
31 #ifndef NOCONFIG
32  #include "configlib.h"
33  char *configFile = NULL;
34 #endif
35 
36 /* Windows needs file to be opened in byte mode */
37 #if WIN32
38  #define WAITALL 0
39  #define WRITE "wb+"
40  #define sleep(msec) _sleep(msec)
41 #else
42  #define WAITALL 0x100
43  #define WRITE "w+"
44 #endif
45 
46 #define TENTATIVES -1
47 #define CONNECTION_TIMEOUT 5 /* The connection must be established within 5 seconds */
48 #define RECONNECT_TIME 5 /* We are waiting 5 seconds before trying to reconnect. */
49 
50 /* Struct storing command line arguments */
51 commandLine *cmdLine;
52 
53 /* Uptime */
54 time_t start_time;
55 time_t current_time;
56 /* for select() */
57 fd_set rfds;
58 /* socket timeout */
59 struct timeval timeout;
60 
61 /* Statistics */
62 stats *cPigeStats;
63 
64 /* Servers pool */
65 urlPool_t *serversPool = NULL;
66 int poolPosition = 0;
67 
68 int server_socket = -1;
69 
70 #ifdef SOLARIS
71 int daemon(int nochdir, int noclose);
72 #endif
73 
74 /* Main Program */
main(int argc,char ** argv)75 int main (int argc, char **argv)
76 {
77   char *buffer      = NULL;
78   char *titre       = NULL;
79   char *oldtitre    = NULL;
80   char *nexttitre   = NULL;
81   char *filename    = NULL;
82   char *extension   = "mp3";
83   char *meta;
84   char *statusline;
85   char *dir;
86 
87   long long unsigned int uptime      = 0;
88   long long unsigned int metacounter = 0;
89 
90   /* When operates in quiet mode, display stats in logfile
91    * evry countdown seconds
92    */
93   time_t countdown = 3600; /* Evry 1 hour */
94   time_t temptime;
95 
96   /* Integers ... */
97   int size;
98   int retval;
99   int i;
100   int tempsize = 0;
101   int nextsize = 0;
102   int len1     = 0;
103   int len2     = 0;
104   int maxlen   = 0;
105   int songs    = 0;
106   int buflen   = 0;
107   int maxlinelength = 80;
108 
109   /* Directory & File manipulations */
110   DIR *pige_dir;
111   FILE *output_file = NULL;
112   FILE *logfile     = NULL;
113 
114   /* Custom typedef */
115   currentSong *curSong;
116   icyHeaders *icy_headers = NULL;
117   lastCut last_cut;
118 
119   /*
120    * last_cut.hour = getHour();
121    * last_cut.min  = getMinute();
122    */
123 
124   last_cut.hour = -1;
125   last_cut.min =  -1;
126   last_cut.sec =  -1;
127 
128   /* Global dynamic configuration */
129   cmdLine = parseCommandLine(argc, argv);
130   testCommandLine();
131 
132   /* Logfile */
133   if ((logfile = fopen(cmdLine->logFile, "a+")) == NULL)
134   {
135     _ERROR("Unable to openlogfile: %s Setting log to stdout\n", cmdLine->logFile);
136     logfile = stdout;
137   } else {
138     VERBOSE("Successfully opened %s\n", cmdLine->logFile);
139   }
140 
141   /* We setup debug to logfile instead of stderr */
142   SetDebugFile(logfile);
143 
144   if (cmdLine->background)
145   {
146 #ifndef WIN32
147     int pid;
148     int fd;
149     FILE *pidfile;
150 
151     if (daemon(1, 1) == -1)
152     {
153       _ERROR("Error daemonizing. %s\n", strerror(errno));
154       _exit(-1);
155     }
156 
157     pid = (int)getpid();
158 
159     if (cmdLine->pidFile != NULL)
160     {
161       pidfile = fopen(cmdLine->pidFile, "w");
162       if (pidfile == NULL)
163         _ERROR("Unable to open pidfile %s\n", cmdLine->pidFile);
164       else
165       {
166         fprintf(pidfile, "%d", pid);
167         fclose(pidfile);
168       }
169     }
170 
171     fprintf(stdout, "cPige launched in background. pid: %d\n", pid);
172     fd = open("/dev/null", O_RDWR);
173 
174     if (fd == -1)
175     {
176       _ERROR("Error opening /dev/null: %s\n", strerror(errno));
177       _exit(0);
178     }
179 
180     for (i = 0; i < 3; i++)
181       dup2(fd, i);
182 
183     close(fd);
184 #else
185    printf("Fork not available under WIN32.\n");
186 #endif
187   }
188 
189   cPigeStats = (stats *)memory_allocation(sizeof(stats));
190   cPigeStats->reconnections = 0;
191   cPigeStats->songs = 0;
192 
193   /* Create output dir if does not exists! */
194   if (( pige_dir = opendir(cmdLine->dir)) == NULL)
195   {
196     _ERROR("Unable to open %s for writing\n", cmdLine->dir);
197 #ifdef WIN32
198     if (mkdir(cmdLine->dir) != 0) {
199 #else
200     if (mkdir(cmdLine->dir, 0755) != 0) {
201 #endif
202       _ERROR("Failed trying to create %s. Verify rights.\n", cmdLine->dir);
203       _exit(-1);
204     } else {
205       VERBOSE("Created %s\n", cmdLine->dir);
206     }
207   } else {
208     VERBOSE("Sucessfully opened %s\n", cmdLine->dir);
209     closedir(pige_dir);
210   }
211 
212   /* If we are keeping One week of stream, check if directories
213    * exists, and check disk space
214    */
215 
216   checkWeekBackup();
217 
218   /* Start time, for uptime calculation */
219   start_time = time((time_t *)NULL);
220   temptime = time((time_t *)NULL) + countdown;
221 
222   /* We try connecting and receiving good headers infinitely */
223   do
224   {
225     server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 0, NULL);
226     if (icy_headers != NULL)
227     {
228       free_icy_headers(icy_headers);
229       free(icy_headers);
230       icy_headers = NULL;
231     }
232 
233     icy_headers = readicyheaders(getHeaders(server_socket));
234   } while (icy_headers == NULL);
235 
236   if (icy_headers->metaint != buflen)
237   {
238     buflen = icy_headers->metaint;
239     if (buffer != NULL)
240       free(buffer);
241     buffer = (char *)memory_allocation(buflen);
242   }
243 
244   if (strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0)
245     extension = "mp3";
246   else if (strncmp(icy_headers->content_type, "audio/aacp", 10) == 0)
247     extension = "aac";
248   else if (strncmp(icy_headers->content_type, "application/ogg", 15) == 0)
249     extension = "ogg";
250 
251   VERBOSE("Using extension: %s\n", extension);
252 
253   /* Only usefull for debug, not for production ! */
254   /* print_icyheaders(icy_headers); */
255 
256 #ifndef WIN32
257   if(cmdLine->useNumbers == 1)
258     songs = getSongs(cmdLine->dir);
259 #endif
260 
261   nextsize = 512;
262   oldtitre = strdup("please.delete");
263 
264   while (1)
265   {
266     /* For select() it's a global struct. */
267     timeout.tv_sec = SOCKET_TIMEOUT;
268     timeout.tv_usec = 0;
269     retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
270     if (retval <= 0)
271     {
272       _ERROR("Connection Error.\n");
273       server_close(server_socket);
274       server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 1, icy_headers);
275       if (icy_headers->metaint != buflen)
276       {
277         buflen = icy_headers->metaint;
278         free(buffer);
279         buffer = (char *)memory_allocation(buflen);
280       }
281 
282       tempsize = 0;
283       nextsize = 512;
284       continue;
285     }
286 
287     size = recv(server_socket, buffer, nextsize, 0);
288     if ((size == -1) || ((size == 0) && nextsize != 0))
289     {
290       _ERROR("Connection error in recv main() size: %d nextsize: %d\n", size, nextsize);
291       server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 1, icy_headers);
292       if (icy_headers->metaint != buflen)
293       {
294         buflen = icy_headers->metaint;
295         free(buffer);
296         buffer = (char *)memory_allocation(buflen);
297       }
298 
299       tempsize = 0;
300       nextsize = 512;
301       continue;
302     }
303 
304     if (output_file)
305     {
306       if (fwrite(buffer, sizeof(char), size, output_file) != size*sizeof(char))
307       {
308         _ERROR("Error writing output. Disk full ?\n");
309         break;
310       }
311       /* Many thanks to the #hurdfr team! */
312       fflush(output_file);
313     }
314 
315     if ( tempsize == icy_headers->metaint )
316     {
317       if (cmdLine->pige)
318       {
319         /* Pige Mode */
320 
321         if (mustCut(&last_cut) == 1)
322         {
323           if ((cmdLine->usePigeMeta == 1) && (output_file != NULL) && (strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0))
324           {
325             char buffer1[30];
326             char buffer2[30];
327             char *buffer3;
328 
329             switch (cmdLine->intervalType)
330             {
331               case IVAL_HOUR:
332                 snprintf(buffer1, 30, "pige %.2dh -> %.2dh", last_cut.hour, getCloserInterval(getHour(), cmdLine->interval));
333                 break;
334               case IVAL_MIN:
335                 snprintf(buffer1, 30, "pige %.2dh%.2d -> %.2dh%.2d", last_cut.hour, last_cut.min, getCloserInterval(getHour(), 1), getCloserInterval(getMinute(), cmdLine->interval));
336                 break;
337             }
338             strncpy(buffer2, icy_headers->name, 30);
339 
340             buffer3 = GetId3v1(buffer2, buffer1, icy_headers->name);
341             fwrite(buffer3, sizeof(char), 128, output_file);
342             (void)free(buffer3);
343           }
344 
345           if (output_file)
346             fclose(output_file);
347 
348           if (cmdLine->weekBackup)
349           {
350             char *dirname = getDayName(time((time_t *)NULL));
351             int dirlen = strlen(cmdLine->dir);
352             int totlen = dirlen+strlen(dirname)+1+1+1;
353 
354             dir = (char *)memory_allocation(totlen);
355             if (cmdLine->dir[dirlen-1] == '/')
356               snprintf(dir, totlen, "%s%s/", cmdLine->dir, dirname);
357             else
358               snprintf(dir, totlen, "%s/%s/", cmdLine->dir, dirname);
359 
360             (void)free(dirname);
361           } else {
362             dir = strdup(cmdLine->dir);
363           }
364 
365           switch (cmdLine->intervalType)
366           {
367             case IVAL_HOUR:
368               last_cut.hour = getCloserInterval(getHour(), cmdLine->interval);
369               last_cut.min  = getCloserInterval(getMinute(), 60);
370 
371               len1 = strlen(dir) + 1 + strlen(extension) + 2 + 1;
372               filename = (char *)memory_allocation(len1);
373               snprintf(filename, len1, "%s%.2d.%s", dir, last_cut.hour, extension);
374               break;
375 
376             case IVAL_MIN:
377               last_cut.min  = getCloserInterval(getMinute(), cmdLine->interval);
378               last_cut.hour = getCloserInterval(getHour(), 1);
379 
380               len1 = strlen(dir) + 1 + strlen(extension) + 2 + 2 + 1 + 1;
381               filename = memory_allocation(len1);
382               snprintf(filename, len1, "%s%.2d-%.2d.%s", dir, getHour(), last_cut.min, extension);
383               break;
384 
385             default:
386               fprintf(stderr, "Internal Error: unknown interval type.\n");
387               break;
388           }
389           last_cut.sec = time(NULL);
390 
391           (void)free(dir);
392 
393           if ((output_file = fopen(filename, "r")) != NULL)
394           {
395             VERBOSE("Erasing %s\n", filename);
396             unlink(filename);
397             fclose(output_file);
398           }
399           VERBOSE("Opening: %s\n", filename);
400           output_file = fopen(filename, WRITE);
401           (void)free(filename);
402 
403         }
404       } else if (cmdLine->live) {
405         printf("Not yet implemented.\n");
406         _exit(1);
407       }
408 
409       metacounter++;
410       current_time = time((time_t *)NULL);
411       uptime = (long long unsigned int) difftime(current_time, start_time);
412 
413       /* Classical stdout status line */
414       if (!cmdLine->quiet)
415       {
416         fflush(stdout);
417         fprintf(stdout, "\r");
418 
419         /* 100% pur porc */
420         for(i = 0; i < maxlinelength; i++)
421           fprintf(stdout, " ");
422 
423         if (strncmp(icy_headers->content_type, "application/ogg", 15) == 0)
424           statusline = statusLine(uptime, metacounter, icy_headers->metaint, NULL, NULL);
425         else
426           statusline = statusLine(uptime, metacounter, icy_headers->metaint, oldtitre, nexttitre);
427 
428         fprintf(stdout, "\r%s", statusline);
429 
430         if ( strlen(statusline) > maxlinelength)
431           maxlinelength = strlen(statusline);
432 
433         if (statusline != NULL)
434           (void)free(statusline);
435       }
436 
437       if ((cmdLine->useGUI) && (cmdLine->quiet))
438       {
439         char *mybuf;
440 
441         /* GUI Communication protocol */
442         /* Protocol:
443          * CPIGEGUI:title:nexttitle:timeleft\n
444          */
445         fprintf(stdout, "CPIGEGUI:");
446 
447         if (oldtitre)
448         {
449           mybuf = (char *)memory_allocation(strlen(oldtitre)+1);
450           stripcolon(oldtitre, mybuf);
451           fprintf(stdout, "%s", mybuf);
452           free(mybuf);
453         }
454 
455         fprintf(stdout, ":");
456 
457         if (nexttitre)
458         {
459           mybuf = (char *)memory_allocation(strlen(nexttitre)+1);
460 
461           if ((cmdLine->Next) && (strlen(nexttitre) > strlen(cmdLine->Next)))
462             stripcolon(nexttitre + strlen(cmdLine->Next), mybuf);
463           else
464             stripcolon(nexttitre, mybuf);
465 
466           fprintf(stdout, "%s", mybuf);
467           free(mybuf);
468         } else
469           fprintf(stdout, " ");
470 
471         fprintf(stdout, ":");
472 
473         if (cmdLine->pige == 1)
474         {
475           int timeleft, percentage;
476           int stop_s, nowTmp;
477           int tmp1;
478 
479           nowTmp = time((time_t *)NULL);
480 
481           /* How many time left ? */
482           switch (cmdLine->intervalType)
483           {
484             case IVAL_HOUR:
485               /* Il me faut l'heure d'arriv�e th�orique, en seconde, du stream */
486               tmp1 = (getCloserInterval(getHour(), cmdLine->interval) + cmdLine->interval);
487               stop_s = ((tmp1 * 3600) + (nowTmp - ((getHour()*3600) - getMinute() * 60) - getSec()));
488               percentage = ((float)(1.0 - (((float)stop_s - (float)nowTmp) / ((float)cmdLine->interval*3600.0))) * 100.0);
489               break;
490             case IVAL_MIN:
491               tmp1 = (getCloserInterval(getMinute(), cmdLine->interval) + cmdLine->interval);
492               stop_s = ((tmp1 * 60) + (nowTmp - (getMinute() * 60) - getSec()));
493               percentage = ((float)(1.0 - (((float)stop_s - (float)nowTmp) / ((float)cmdLine->interval*60.0))) * 100.0);
494               //percentage = ((float)(1.0 - (((float)tmp1 - (float)getMinute()) / (float)getMinute())) * 100.0);
495               break;
496           }
497 
498           timeleft = stop_s - nowTmp;
499 
500           fprintf(stdout, "%d:%d", timeleft, percentage);
501         }
502 
503         fprintf(stdout, "\n");
504         fflush(stdout);
505       }
506 
507       /* Stats evry countdown seconds */
508       if ((countdown != 0) && (temptime <= current_time))
509       {
510         statusline = getStats(uptime, metacounter, icy_headers->metaint);
511         if (statusline != NULL)
512         {
513           if (fwrite(statusline, sizeof(char), strlen(statusline), logfile) != strlen(statusline))
514           {
515             _ERROR("Fwrite error.\n");
516             break;
517           }
518           /* Many thanks to the #hurdfr@freenode team! */
519           fflush(logfile);
520           (void)free(statusline);
521         } else {
522           VERBOSE("getStats returned NULL values...\n");
523         }
524         temptime = current_time + countdown;
525       }
526 
527       if ((strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0) ||
528           (strncmp(icy_headers->content_type, "audio/aacp", 10) == 0))
529       {
530         meta = readMeta(server_socket);
531 
532         if ((meta == NULL) && (server_socket == 0))
533         {
534           server_socket = reconnect(RECONNECT_TIME, TENTATIVES, 0, icy_headers);
535           if (icy_headers->metaint != buflen)
536           {
537             buflen = icy_headers->metaint;
538             free(buffer);
539             buffer = (char *)memory_allocation(buflen);
540           }
541 
542           tempsize = 0;
543           nextsize = 512;
544           continue;
545         }
546 
547         if (titre != NULL)
548           free(titre);
549 
550         titre = getTitle(meta);
551 
552         if (meta != NULL)
553           (void)free(meta);
554       }
555 
556       if (titre != NULL)
557       {
558         if (strlen(titre) > 0)
559         {
560           if (strstr(titre, cmdLine->Next) == NULL) /* If title isn't ASUIVRE */
561           {
562             if (oldtitre != NULL)
563             {
564               len1 = strlen(oldtitre);
565               len2 = strlen(titre);
566 
567               /* We are determining the maximal comp len */
568               if (len1 >= len2); else maxlen = len1;
569 
570               if (strncmp(titre, oldtitre, maxlen) != 0)
571               {
572                 if (! cmdLine->pige)
573                 {
574                   if (cmdLine->skipSongs > 0)
575                   {
576                     cmdLine->skipSongs--;
577                     VERBOSE("Skipping song `%s' (left %d songs to skip)\n", titre, cmdLine->skipSongs);
578                   } else {
579                     cPigeStats->songs++;
580                     songs++;
581 
582                     if (output_file != NULL)
583                     {
584                       curSong = getCurrentSong(oldtitre);
585                       if (curSong != NULL)
586                       {
587                         if (strncmp(icy_headers->content_type, "audio/mpeg", 10) == 0)
588                         {
589                           char *buffer1;
590                           buffer1 = GetId3v1(curSong->title, curSong->artist, icy_headers->name);
591                           fwrite(buffer1, sizeof(char), 128, output_file);
592                           (void)free(buffer1);
593                         }
594                         if (curSong->artist != NULL)
595                           free(curSong->artist);
596                         if (curSong->title != NULL)
597                           free(curSong->title);
598                         free(curSong);
599                       }
600 
601                       fclose(output_file);
602                     }
603 
604                     if (cmdLine->useNumbers == 0)
605                     {
606                       len1 = strlen(titre) + strlen(cmdLine->dir)+ 1 + strlen(extension) + 1 + 1;
607                       filename = (char *)memory_allocation(len1);
608                       snprintf(filename, len1, "%s%s.%s", cmdLine->dir, titre, extension);
609                     } else {
610                       len1 = 5+strlen(titre) + strlen(cmdLine->dir)+ 1 + strlen(extension) + 1 + 1;
611                       filename = (char *)memory_allocation(len1);
612                       snprintf(filename, len1, "%s%.4d_%s.%s", cmdLine->dir, songs, titre, extension);
613                     }
614 
615                     if ((output_file = fopen(filename, "r")) == NULL)
616                     { /* Anti doublons */
617                       VERBOSE("New file opened: %s\n", filename);
618                       output_file = fopen(filename, WRITE);
619                     } else {
620                       VERBOSE("File already exists %s.\n", filename);
621                       fclose(output_file);
622                       output_file = NULL;
623                     }
624                     (void)free(filename);
625                   } /* Song skip */
626                 }
627               } /* Title are differents */
628             } /* Oldtitre != NULL */
629             if (oldtitre != NULL)
630               (void)free(oldtitre);
631 
632             oldtitre = strdup(titre);
633 
634           } else {
635             /* Title is "ASUIVRE" */
636             if (nexttitre != NULL)
637               (void)free(nexttitre);
638 
639             nexttitre = strdup(titre);
640           }
641           if (titre != NULL)
642           {
643             (void)free(titre);
644             titre = NULL;
645           }
646 
647         } /* Strlen(titre > 0) */
648       }
649       tempsize = 0; /* tempsize = 0: chunk received successfully. Awaiting next chunk */
650     } else if (tempsize > icy_headers->metaint) {
651       _ERROR("Error tempsize > metaint\n");
652       break;
653     } else
654       tempsize = tempsize + size;
655 
656     if ((tempsize + 512) >= icy_headers->metaint)
657       nextsize = icy_headers->metaint - tempsize;
658     else
659       nextsize = 512;
660 
661     /* Check timeToStop */
662     if ((cmdLine->timeToStop != -1) && (uptime >= cmdLine->timeToStop))
663     {
664       VERBOSE("TimeToStop reached. Stopping cPige.\n");
665       goto cleanup;
666     }
667   } /* infinite loop */
668 
669 cleanup:
670   printf("\n");
671   fflush(stdout);
672 
673   server_close(server_socket);
674 
675   /* cleanup */
676   if (icy_headers != NULL)
677     (void)free(icy_headers);
678   if (output_file != NULL)
679     fclose(output_file);
680   if (logfile != stdout)
681     fclose(logfile);
682 
683   (void)free(cmdLine);
684   (void)free(cPigeStats);
685 
686   return 0;
687 }
688 
689 cPigeUptime *getUptime(long long unsigned int uptime)
690 {
691   cPigeUptime *cu;
692 
693   cu = (cPigeUptime *)memory_allocation(sizeof(cPigeUptime));
694 
695   /* Calculs bourrins :) */
696   cu->day  = (unsigned int) (uptime / (60 * 60 * 24));
697   cu->hour = (unsigned int) (uptime / (60 * 60)) - (cu->day * 24);
698   cu->min  = (unsigned int) (uptime / (60)) - ((cu->hour * 60) + (cu->day * 24 * 60));
699   cu->sec  = (unsigned int) (uptime) - ((cu->min * 60) + (cu->hour * 60 * 60) + (cu->day * 24 * 60 * 60));
700 
701   return cu;
702 }
703 
704 char *getStats(long long unsigned int uptime, long long unsigned int metacounter, int metaint)
705 {
706   char *line;
707   cPigeUptime *cu;
708 
709   cu = getUptime(uptime);
710   line = (char *)memory_allocation(300); /* Exessif. */
711 
712   snprintf(line, 299, "Uptime: %d days, %d hours, %d min, %d seconds\nDownloaded: %lldKo\nSongs played: %ld\nReconnections: %ld\n", cu->day, cu->hour, cu->min, cu->sec, (long long unsigned int)((metacounter*metaint)/1024), cPigeStats->songs, cPigeStats->reconnections);
713 
714   free(cu);
715   return line;
716 }
717 
718 char *statusLine(long long unsigned int uptime, long long unsigned int metacounter, int metaint, char *titre, char *nexttitre)
719 {
720   char *line;
721   int len;
722   cPigeUptime *cu;
723 
724   cu = getUptime(uptime);
725 
726   /* Pas terrible... */
727   line = memory_allocation(300);
728 
729   len = snprintf(line, 299, "[%dj %dh:%dm:%ds][%lldKo] ", cu->day, cu->hour, cu->min, cu->sec, (long long unsigned int)((metacounter * metaint) / 1024));
730 
731   if (cmdLine->pige)
732     len += snprintf(line + len, 299-len, "%dh -> %dh ", getHour(), getHour()+1);
733 
734   if (titre != NULL)
735     len += snprintf(line+len, 299-len, "%s", titre);
736 
737   if (nexttitre != NULL)
738     if (strstr(nexttitre, titre) == NULL)
739       snprintf(line+len, 299-len, " -> %s", nexttitre);
740 
741   free(cu);
742   return line;
743 }
744 
745 
746 char *readMeta(int serversocket)
747 {
748   char *meta;
749   char c;
750   int retval;
751   int size = 0;
752   int readsize = 0;
753   char *buffer;
754 
755   /* For select() it's a global struct. */
756   timeout.tv_sec = SOCKET_TIMEOUT;
757   timeout.tv_usec = 0;
758 
759   retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
760   if (retval <= 0)
761   {
762     _ERROR("Erreur de connexion!\n");
763     server_close(server_socket);
764     return NULL;
765   } else if (recv(server_socket, &c, 1, 0) != 1) {
766     _ERROR("Error reading from server socket: \n", strerror(errno));
767     server_close(server_socket);
768     return NULL;
769   }
770 
771   if (c > 0)
772   {
773     meta   = (char *)memory_allocation((c*16)+2);
774     buffer = (char *)memory_allocation((c*16)+1);
775 
776     /* For select() it's a global struct. */
777     timeout.tv_sec = SOCKET_TIMEOUT;
778     timeout.tv_usec = 0;
779     retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
780     if (retval <= 0)
781     {
782       _ERROR("Connection error in select. (readmeta)\n");
783       (void)free(buffer);
784       (void)free(meta);
785       server_close(server_socket);
786       return NULL;
787 
788     } else {
789       readsize = 0;
790       while(readsize < c*16)
791       {
792         /* For select() it's a global struct. */
793         timeout.tv_sec = SOCKET_TIMEOUT;
794         timeout.tv_usec = 0;
795 
796         retval = select(server_socket+1, &rfds, NULL, NULL, &timeout);
797         if (retval <= 0)
798         {
799           _ERROR("Erreur de connexion!\n");
800           server_close(server_socket);
801           return NULL;
802         } else {
803           size = recv(server_socket, buffer, (c*16 - readsize), 0);
804           if (size <= 0)
805           {
806             VERBOSE("Megaproblem here.\n");
807             server_close(server_socket);
808           }
809           readsize += size;
810           strncat(meta, buffer, size);
811         }
812       }
813       (void)free(buffer);
814     }
815   } else {
816     /* Title was not sent. */
817     return NULL;
818   }
819   return meta;
820 }
821 
822 
823 /* Lorsque le serveur envoie: "StreamTitle='TITREZIK';"
824  * On ne r�cup�re avec cette fonction que TITREZIK.
825  */
826 
827 char *getTitle(char *titleString)
828 {
829   int i;
830   char *result;
831   int count = 0;
832   int j = 0;
833   int first = 0;
834 
835   if ((titleString == NULL) || (strlen(titleString) == 0))
836     return NULL;
837 
838   if (strstr(titleString, "StreamTitle") == NULL)
839   {
840     /* We have there an invalid strea title. Can't be
841      * parsed.
842      *
843      * Aborting, will try another server!
844      */
845     _ERROR("Received a stream with metadata enabled, but at metaint interval, we only received binary crap. Please fix your stream.");
846     return NULL;
847   }
848 
849   result = (char *)memory_allocation(strlen(titleString) + 1);
850 
851   for (i=0; i < strlen(titleString); i++)
852   {
853     if (count == 1)
854     {
855       if (titleString[(i+1)] != ';')
856       {
857         if (first == 0)
858         {
859           if ((isalnum((int) titleString[i])) && (titleString[i] != ' '))
860           {
861             result[j++] = titleString[i];
862             first++;
863           }
864         } else {
865           result[j++] = titleString[i];
866         }
867       }
868     }
869 
870     if (titleString[i] == '\'')
871     {
872       if (count == 0)
873         count++;
874       else if (titleString[(i+1)] == ';')
875         break;
876     }
877   } /* for */
878 
879   result[j] = '\0';
880 
881   stripBadChar(result, result);
882 
883   return result;
884 }
885 
886 currentSong *getCurrentSong(char *title)
887 {
888   currentSong *cursong;
889   char c;
890   int i;
891   int j = 0;
892   int flag = 0;
893 
894   if (title == NULL) return NULL;
895 
896   cursong = (currentSong *)malloc(sizeof(currentSong));
897 
898   cursong->title  = (char *)memory_allocation(30 + 1); /* id3v1 == 30 chars max */
899   cursong->artist = (char *)memory_allocation(30 + 1); /* id3v1 == 30 chars max */
900 
901   for (i = 0; i < strlen(title); i++)
902   {
903     c = title[i];
904     if (!flag) /* ! Flag */
905     {
906       if ((c == '-') && ((j-1) > 0))/* Avant le - => artiste */
907       {
908         if (cursong->artist[(j-1)] == ' ')
909           cursong->artist[(j-1)] = '\0';
910 
911         flag = 1;
912         j = 0; /* Pour l'espace */
913       } else {
914         if (j < 30) /* "l'espace!" */
915           cursong->artist[j++] = c;
916       }
917     } else { /* Flag */
918       if (j < 30)
919       {
920         if (j == 0 && c == ' ') continue;
921         cursong->title[j++] = c;
922       }
923     }
924   } /* boucle :) */
925 
926   if (strlen(cursong->title) == 0)
927   {
928     (void)free(cursong->title);
929     cursong->title = NULL;
930   }
931 
932   if (strlen(cursong->artist) == 0)
933   {
934     (void)free(cursong->artist);
935     cursong->artist = NULL;
936   }
937 
938   /* Last byte of artist & title in the structure
939    * are 0 as they have been initialized with calloc.
940    */
941   return cursong;
942 }
943 
944 void print_credits()
945 {
946   printf ("cPige %s by Laurent Coustet (c) 2005\n", VERSION);
947   return;
948 }
949 
950 void print_help()
951 {
952   fprintf (stderr,
953           "cPige help. cPige is a Laurent Coustet product.\n"
954           "For more informations about me and my software,\n"
955           "please visit http://ed.zehome.com/\n\n"
956 #ifdef NOCONFIG
957           "Usage: ./cpige -h http://stream-hautdebit.frequence3.net:8000/ -d /home/ed/Pige -l logfile.log\n\n"
958           "    -h host to connect to.\n"
959           "    -V show cpige Version.\n"
960           "    -d directory save stream to this directory.\n"
961           "    -P Pige mode (takes no argument), save stream by hour.\n"
962           "    -M Use pige Meta: will write id3v1 tag (only usefull with pige mode).\n"
963           "    -q Quiet mode, does not output stream status.\n"
964           "    -b Background mode (UNIX only) use cPige as a daemon.\n"
965           "    -l Path to logfile.\n"
966           "    -I [h|m] pige mode will cut on a hour by hour basis or min by min basis.\n"
967           "    -i nb how many \"nb\" hour(s) or minute(s) we should wait before cutting.\n"
968 #ifndef WIN32
969           "    -n cPige will append xxxx to file in 'non pige mode', where xxxx is a number.\n");
970 #else
971           );
972 #endif
973 #else
974           "Usage: ./cpige -c path/to/cpige.conf\n"
975           "    -c path/to/cpige.conf.\n");
976 #endif
977   fflush(stderr);
978 }
979 
980 commandLine *parseCommandLine(int argc, char **argv)
981 {
982   commandLine *cmdLine;
983   int i;
984   char *c;
985 
986 #ifndef NOCONFIG
987   char *buffer;
988   config_t *conf, *workconf;
989 #endif
990 
991   if (argc < 2)
992   {
993     print_help();
994     _exit(-1);
995   }
996 
997   cmdLine = (commandLine *)malloc(sizeof(commandLine));
998 
999   /* default is not pige mode */
1000   cmdLine->pige  = 0;
1001   cmdLine->quiet = 0;
1002   cmdLine->live  = 0;
1003   cmdLine->background   = 0;
1004   cmdLine->interval     = 1;
1005   cmdLine->intervalType = IVAL_HOUR;
1006   cmdLine->usePigeMeta  = 0;
1007   cmdLine->useNumbers   = 0;
1008   cmdLine->skipSongs    = 0;
1009   cmdLine->useGUI       = 0;
1010 
1011   cmdLine->logFile    = "cpige.log";
1012   cmdLine->dir        = NULL;
1013   cmdLine->Next       = "A suivre";
1014 
1015   for (i = 1; i < argc; i++)
1016   {
1017     c = strchr("c", argv[i][1]);
1018     if (c != NULL)
1019     {
1020       /* from streamripper */
1021       if ((i == (argc-1)) || (argv[i+1][0] == '-'))
1022       {
1023         (void)print_help();
1024         fprintf(stderr, "option %s requires an argument\n", argv[i]);
1025         _exit(1);
1026       }
1027     }
1028 
1029     switch (argv[i][1])
1030     {
1031       case 'V':
1032         printf("%s\n", USER_AGENT);
1033         _exit(0);
1034         break;
1035 
1036 #ifndef NOCONFIG
1037       case 'c':
1038         i++;
1039         configFile = strdup(argv[i]);
1040         break;
1041       case 'g':
1042         cmdLine->useGUI = 1;
1043         cmdLine->quiet  = 1;
1044         break;
1045 #else
1046       case 'h':
1047         i++;
1048 
1049         if (serversPool == NULL)
1050           serversPool = addPoolUrl(NULL, argv[i]);
1051         else
1052           addPoolUrl(serversPool, argv[i]);
1053 
1054         break;
1055 
1056       case 'd':
1057         i++;
1058         {
1059           int len = strlen(argv[i]);
1060           cmdLine->dir = (char *)memory_allocation(len+2);
1061           strncpy(cmdLine->dir, argv[i], len);
1062 
1063           /* TODO: Windows compatibility ? */
1064           if (cmdLine->dir[len-1] != '/')
1065           {
1066             cmdLine->dir[len] = '/';
1067             cmdLine->dir[len+1] = 0;
1068           }
1069         }
1070         break;
1071 #ifndef WIN32
1072       case 'n':
1073         cmdLine->useNumbers = 1;
1074         break;
1075 #endif
1076       case 'l':
1077         i++;
1078         cmdLine->logFile = strdup(argv[i]);
1079         break;
1080 
1081       case 'P':
1082         if (cmdLine->live == 1)
1083         {
1084           printf("You can't use Live Mode and Pige mode simultaneously.\n");
1085           _exit(-1);
1086         }
1087         if (cmdLine->pige == 1)
1088           break;
1089 
1090         cmdLine->pige = 1;
1091         printf("Pige Mode activated.\n");
1092         break;
1093 
1094       case 'q':
1095         cmdLine->quiet = 1;
1096         break;
1097 
1098       case 'b':
1099         cmdLine->quiet = 1;
1100         cmdLine->background = 1;
1101         break;
1102 
1103       case 'L':
1104         if (cmdLine->pige == 1)
1105         {
1106           printf("You can't use Live Mode and Pige mode simultaneously.\n");
1107           _exit(-1);
1108         }
1109         cmdLine->live = 1;
1110         printf("Live Mode activated.\n");
1111         break;
1112 
1113       case 'M':
1114         if (cmdLine->pige == 1)
1115           cmdLine->usePigeMeta = 1;
1116 
1117         break;
1118 
1119       case 'I':
1120         i++;
1121 
1122         if (cmdLine->pige != 1)
1123         {
1124           cmdLine->pige = 1;
1125           printf("Pige Mode activated.\n");
1126         }
1127 
1128         if ( *argv[i] == 'h' || *argv[i] == 'H' )
1129           cmdLine->intervalType = IVAL_HOUR;
1130         else if ( *argv[i] == 'm' || *argv[i] == 'M' )
1131         {
1132           cmdLine->intervalType = IVAL_MIN;
1133           if (cmdLine->interval == 1)
1134             cmdLine->interval = 30;
1135         }
1136         else
1137         {
1138           fprintf(stderr, "Unknown interval type.\n");
1139           _exit(1);
1140         }
1141         break;
1142 
1143       case 'i':
1144         i++;
1145 
1146         if (cmdLine->pige != 1)
1147         {
1148           cmdLine->pige = 1;
1149           printf("Pige Mode activated.\n");
1150         }
1151 
1152         cmdLine->interval = atoi(argv[i]);
1153         if (cmdLine->interval == 0)
1154         {
1155           fprintf(stderr, "Invalid interval 0.\n");
1156           _exit(1);
1157         }
1158 
1159         break;
1160 
1161 #endif /* NOCONFIG */
1162 
1163       default:
1164         fprintf(stderr, "Unknown switch: `%s'\n", argv[i]);
1165         break;
1166     } /* switch */
1167   } /* for */
1168 
1169 #ifndef NOCONFIG
1170   /* Use the config file parser :) */
1171 
1172   if (configFile == NULL)
1173     configFile = strdup("./cpige.conf");
1174 
1175   /* Configfile Readin */
1176   printf("Reading config file: %s\n", configFile);
1177   if ( (conf = parseConfig( configFile )) == NULL)
1178   {
1179     fprintf(stderr, "Could not read config from: %s.\n", configFile);
1180     _exit(0);
1181   }
1182 
1183   /* Setting up url pool */
1184   workconf = conf;
1185   while (workconf != NULL)
1186   {
1187     workconf = _conf_getValue(workconf, "url", &buffer);
1188     if (buffer == NULL)
1189       break;
1190     else
1191     {
1192       VERBOSE("Adding %s to the pool.", buffer);
1193       if (serversPool == NULL)
1194         serversPool = addPoolUrl(NULL, buffer);
1195       else
1196         addPoolUrl(serversPool, buffer);
1197       free(buffer);
1198     }
1199   }
1200 
1201   /* Setting up cpige common parameters */
1202   set_str_from_conf(conf, "savedirectory", &buffer, NULL, "savedirectory not found in config file.\n", 1);
1203 
1204   {
1205     int len = strlen(buffer);
1206     cmdLine->dir = (char *)memory_allocation(len+2);
1207     strncpy(cmdLine->dir, buffer, len);
1208     free(buffer);
1209 
1210     /* TODO: Windows compatibility ? */
1211     if (cmdLine->dir[len-1] != '/')
1212     {
1213       cmdLine->dir[len] = '/';
1214       cmdLine->dir[len+1] = 0;
1215     }
1216   }
1217 
1218   /* String values */
1219 #ifndef WIN32
1220   set_str_from_conf(conf,  "pidfile", &(cmdLine->pidFile), "/var/run/cpige.pid", "Warning: no pid file defined. Using /var/run/cpige.pid\n", 0);
1221 #endif
1222   set_str_from_conf(conf,  "logfile",   &(cmdLine->logFile), "./cpige.log", NULL, 0);
1223   set_str_from_conf(conf,  "nexttitle", &(cmdLine->Next),    "A suivre",    NULL, 0);
1224 
1225 #ifndef WIN32
1226   set_str_from_conf(conf,  "locale", &buffer, "C", NULL, 0);
1227 
1228   if (setlocale(LC_TIME, buffer) == NULL)
1229     _ERROR("Error setting up locale: `%s'.\n", buffer);
1230   free(buffer);
1231 #endif
1232 
1233   /* Int values */
1234   set_int_from_conf(conf, "cutdelay",  &(cmdLine->interval),   30, NULL, 0);
1235   set_int_from_conf(conf, "skipsongs", &(cmdLine->skipSongs),  0,  NULL, 0);
1236   set_str_from_conf(conf, "cuttype",   &buffer,               "h", NULL, 0);
1237   if ( (*buffer == 'h') || (*buffer == 'H') )
1238     cmdLine->intervalType = IVAL_HOUR;
1239   else if ( (*buffer == 'm') || (*buffer == 'M') )
1240   {
1241     cmdLine->intervalType = IVAL_MIN;
1242     if (cmdLine->interval == 1)
1243       cmdLine->interval = 30;
1244   } else {
1245     fprintf(stderr, "Unknown interval type: `%s'. Should be 'm' or 'h'\n", buffer);
1246     _exit(1);
1247   }
1248   free(buffer);
1249 
1250   set_str_from_conf(conf, "timetostop", &buffer, NULL, NULL, 0);
1251   if (buffer != NULL)
1252   {
1253     /* Feature activated */
1254     for (i = 0; i < strlen(buffer); i++)
1255     {
1256       if (! isdigit(buffer[i]))
1257       {
1258         /* We have a letter */
1259         char c = buffer[i];
1260         buffer[i] = 0;
1261 
1262         if (c == 'h')
1263         {
1264           cmdLine->timeToStop = atoi(buffer) * 3600;
1265         } else if (c == 'm') {
1266           cmdLine->timeToStop = atoi(buffer) * 60;
1267         } else if (c == 's') {
1268           cmdLine->timeToStop = atoi(buffer);
1269         } else {
1270           fprintf(stderr, "Unknown interval for timetostop (%c)\n", c);
1271         }
1272 
1273         fprintf(stdout, "Setting timetostop: %lld\n", cmdLine->timeToStop);
1274         break;
1275       }
1276     }
1277     free(buffer);
1278   } else {
1279     cmdLine->timeToStop = -1;
1280   }
1281 
1282   /* Boolean values */
1283   set_bool_from_conf(conf, "weekbackup", &(cmdLine->weekBackup),  0, NULL, 0);
1284   set_bool_from_conf(conf, "pigemode",   &(cmdLine->pige),        0, NULL, 0);
1285   set_bool_from_conf(conf, "pigemeta",   &(cmdLine->usePigeMeta), 1, NULL, 0);
1286   if (cmdLine->useGUI != 1)
1287   {
1288     set_bool_from_conf(conf, "quietmode",  &(cmdLine->quiet),       0, NULL, 0);
1289     set_bool_from_conf(conf, "background", &(cmdLine->background),  0, NULL, 0);
1290   }
1291   set_bool_from_conf(conf, "usenumbers", &(cmdLine->useNumbers),  0, NULL, 0);
1292 
1293   /* TODO: I know there's a little memleak there,
1294    * as we are not freeing the conf chained list,
1295    * and not freeing var(s) / val(s)
1296    */
1297 
1298   if (cmdLine->background)
1299     cmdLine->quiet = 1;
1300 
1301 #endif /* Using config file */
1302 
1303   if (cmdLine->dir == NULL)
1304     cmdLine->dir = strdup("./");
1305 
1306   _conf_freeConfig(conf);
1307   /* Don't forget to free it on exit ! */
1308   return cmdLine;
1309 }
1310 
1311 int getCloserInterval(int now, int interval)
1312 {
1313   int tmp;
1314   tmp = (now % interval);
1315   return (now - tmp);
1316 }
1317 
1318 int mustCut(lastCut *cut)
1319 {
1320   int ret = 0;
1321   int closer;
1322 
1323   if (cmdLine->intervalType == IVAL_HOUR)
1324   {
1325     closer = getCloserInterval(getHour(), cmdLine->interval);
1326     if (closer != cut->hour)
1327       ret = 1;
1328   } else {
1329     closer = getCloserInterval(getMinute(), cmdLine->interval);
1330     if (closer != cut->min)
1331       ret = 1;
1332   }
1333 
1334   /*
1335    * if (ret)
1336    * printf("must cut will return true: itype: %d ival: %d closer: %d hour: %d min: %d\n", cmdLine->intervalType, cmdLine->interval, closer, cut->hour, cut->min);
1337    */
1338   return ret;
1339 }
1340 
1341 /* Returns what song number we lastly saved
1342  * the stream to
1343  */
1344 
1345 #ifndef WIN32
1346 int getSongs(char *dir)
1347 {
1348   DIR *dirp;
1349   char *filename;
1350   static regex_t *match = NULL;
1351   int songs = 0;
1352   int current = 0;
1353   struct dirent *cur_dir;
1354 
1355   if (match == NULL)
1356   {
1357     match = (regex_t *)malloc(sizeof(regex_t));
1358     if (regcomp(match, "^[0-9]+.*\\.(mp3|aac)$", REG_EXTENDED) != 0)
1359     {
1360       _ERROR("Regex compilation error... Please contact developper!\n");
1361       return 0;
1362     }
1363   }
1364 
1365   if ( (dirp = opendir(dir)) == NULL)
1366   {
1367     _ERROR("Unable to open directory: %s\n", strerror(errno));
1368     return 0;
1369   }
1370 
1371   while ((cur_dir = readdir(dirp)) != NULL)
1372   {
1373     filename = cur_dir->d_name;
1374 
1375     if ((*filename == '.') || (strcmp(filename, "..") == 0))
1376       continue;
1377 
1378     if (regexec(match, filename, 0, 0, 0) == 0)
1379     {
1380       /* Match */
1381       current = atoi(filename); /* A bit uggly.. */
1382       if (current > songs)
1383         songs = current;
1384     }
1385   }
1386 
1387   closedir(dirp);
1388 
1389   return songs;
1390 }
1391 #endif
1392 
1393 int checkInterval()
1394 {
1395   int ret = 0;
1396   if (cmdLine->intervalType == IVAL_HOUR)
1397   {
1398     if ((cmdLine->interval <= 0) || (cmdLine->interval > 12))
1399       ret = 1;
1400 
1401   } else if (cmdLine->intervalType == IVAL_MIN) {
1402     if ((cmdLine->interval <= 0 || cmdLine->interval > 30))
1403       ret = 1;
1404   } else {
1405     ret = 1;
1406     fprintf(stderr, "Intenal Error: intervalType unknown!\n");
1407   }
1408 
1409   return ret;
1410 }
1411 
1412 void testCommandLine()
1413 {
1414   if (cmdLine == NULL)
1415   {
1416     (void) print_help();
1417     _exit(-1);
1418   }
1419 
1420   if (serversPool == NULL)
1421   {
1422     (void) print_help();
1423     _exit(-1);
1424   }
1425 
1426   if (checkInterval() != 0)
1427   {
1428     fprintf(stderr, "Incorrect interval specified. Exiting...\n");
1429     _exit(1);
1430   }
1431 }
1432 
1433 /* Tries to create directories for week backup */
1434 void checkWeekBackup()
1435 {
1436   int i;
1437   char *dayName;
1438   char *dirName;
1439   int dirNameLen;
1440   time_t when;
1441   DIR *weekdir;
1442 
1443   if ((! cmdLine->weekBackup) || (cmdLine->pige == 0))
1444     return;
1445 
1446   when = time((time_t *) NULL);
1447   /* One week => 7 days ! */
1448   for (i = 0; i < 7; i++)
1449   {
1450     dayName = getDayName((time_t)(when+(i*86400)));
1451 
1452     dirNameLen = strlen(cmdLine->dir) + strlen(dayName) + 2;
1453     dirName = (char *) memory_allocation(dirNameLen);
1454     snprintf(dirName, dirNameLen, "%s%s/", cmdLine->dir, dayName);
1455     (void)free(dayName);
1456 
1457     /* Create output dir if does not exists! */
1458     if (( weekdir = opendir(dirName)) == NULL)
1459     {
1460       _ERROR("Unable to open %s for writing\n", dirName);
1461   #ifdef WIN32
1462       if (mkdir(dirName) != 0) {
1463   #else
1464       if (mkdir(dirName, 0755) != 0) {
1465   #endif
1466         _ERROR("Failed trying to create %s. Verify rights.\n", dirName);
1467         fprintf(stderr, "Failed trying to create %s. Verify rights.\n", dirName);
1468         _exit(-1);
1469       } else {
1470         VERBOSE("Created %s\n", dirName);
1471         /* pige_dir = opendir(cmdLine->dir); */
1472       }
1473     } else {
1474       VERBOSE("Sucessfully opened %s\n", dirName);
1475       closedir(weekdir);
1476     }
1477 
1478     (void)free(dirName);
1479   }
1480 }
1481 
1482 #ifdef SOLARIS
1483 int daemon(int nochdir, int noclose)
1484 {
1485   /* Dummy function, do not use out of cPige ! */
1486   int pid;
1487 
1488   pid = fork();
1489   if (pid == 0)
1490   {
1491     _exit(0);
1492   }
1493   return 0;
1494 }
1495 #endif
1496