1 /*
2  *  main.cpp -- Simple daemon to source an icecast server with direct
3  *              mp3/ogg streaming, no resampling.
4  *
5  *  Copyright (C) 2004 Tony Sin(x) '76 <administrator@tortugalabs.it>
6  *  All rights reserved.
7  *
8  */
9 
10 /*
11  *			 GNU GENERAL PUBLIC LICENSE
12  *			    Version 2, June 1991
13  *
14  *  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
15  *                           675 Mass Ave, Cambridge, MA 02139, USA
16  *  Everyone is permitted to copy and distribute verbatim copies
17  *  of this license document, but changing it is not allowed.
18  *
19  *  This program is free software; you can redistribute it and/or modify
20  *  it under the terms of the GNU General Public License as published by
21  *  the Free Software Foundation; either version 2 of the License, or
22  *  (at your option) any later version.
23 
24  *  This program is distributed in the hope that it will be useful,
25  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
26  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27  *  GNU General Public License for more details.
28 
29  *  You should have received a copy of the GNU General Public License
30  *  along with this program; if not, write to the Free Software
31  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
32  *
33  */
34 
35 /* Version history:
36    0.1 - First release
37    0.3 - Libshout 2.0 support
38          Strong configuration parses
39          Support for Icecast2.0/Shoutcast
40          Bug fixes
41    0.5 - Libshout 1.3.x no longer supported
42          Full POSIX implementation
43          Multiple playlist directories
44          Support for OGG Vorbis
45          Command line interface
46          Multiple log configuration
47          Threads (no more detached processes)
48          All "external errors" are catched
49          Clean exit, all system resources are released
50          FreeBSD/NetBSD/Linux 2.x/MacOS platform supported
51  0.5.1 - Code cleaner
52          Added SOURCE option
53          Added LOOP option
54          Added SHUFFLE option
55          Added METAUPDATE option
56          FIXED: Serious bug in SERVER option
57          FIXED: try/catch mechanism didn't work properly
58          FIXED: possible error in parsing configuration
59          FIXED: wrong path bug
60          FIXED: opendir possibly crash (thanks to DIeMONd)
61          FIXED: multiple directories existing check
62          Songs are sorted by filename on SHUFFLE = 0
63          Now IceG is a GNU standard package, hope that multiple platforms work now
64          Added MP3/OGG recursive directory search option (thx to DIeMONd again for suggesting me)
65          MP3/OGG file extension search is now case insensitive
66          Update time for metadata information is configurable, even metadata information presence too
67  0.5.2 - Added telnet minimal interface
68          Added MySQL support
69          Code cleaner (again)
70          FIXED: heading '/' in log file path
71          FIXED: massive bugfix in configure script
72  0.5.3 - FIXED: Missing declaration on Mac OS X
73          FIXED: Alpha signal.h compilation error
74          FIXED: removed POSIX definition, cause BSD conflict
75  0.5.4 - FIXED: Incorrect socket definitions on MacOSX
76          FIXED: Segfault occurs if log object is NULL when error
77          FIXED: --with-shout parameter (-lshout -lvorbis missing)
78          Added PostGreSQL support
79          Added .M3U playlist support
80          Added .PLS playlist support
81          Removed basename function cause is not POSIX compliant
82          Log file is now path configurable
83          Added more info into README file
84  0.5.5 - FIXED: Serious bug in configuration file parser module
85          FIXED: Don't try to open non-existing files during stream
86          FIXED: Race condition on meta streamer <-> selector launch
87          FIXED: Memory leaks
88          Added ID3 support
89          Added ICEMETAL language support
90          Default icegenerator.conf path is now SYSCONFDIR-fixed
91 */
92 
93 #ifdef HAVE_CONFIG_H
94   #ifndef __main_config_h__
95     #define __main_config_h__
96     #include "../config.h"
97   #endif
98 
99   #ifndef __GNU_LIBRARY__
100     #define __GNU_LIBRARY__
101   #endif
102 
103   #ifdef HAVE_GETOPT_H
104     #include <getopt.h>
105   #endif
106 
107   #ifdef HAVE_STDIO_H
108     #include <stdio.h>
109   #endif
110 
111   #ifdef HAVE_STDLIB_H
112     #include <stdlib.h>
113   #endif
114 
115   #ifdef HAVE_PTHREAD_H
116     #include <pthread.h>
117   #endif
118 
119   #ifdef HAVE_SYS_TYPES_H
120     #include <sys/types.h>
121   #endif
122 
123   #ifdef HAVE_SYS_STAT_H
124     #include <sys/stat.h>
125   #endif
126 
127   #ifdef HAVE_FCNTL_H
128     #include <fcntl.h>
129   #endif
130 
131   #ifdef HAVE_CTYPE_H
132     #include <ctype.h>
133   #endif
134 
135   #ifdef HAVE_STRING_H
136     #include <string.h>
137   #endif
138 
139   #ifdef HAVE_UNISTD_H
140     #include <unistd.h>
141   #endif
142 
143   #define APPNAME PACKAGE_STRING
144 #else
145   #define APPNAME "IceGenerator"
146 #endif
147 
148 #include "log.h"
149 #include "config.h"
150 #include "sgnl_handler.h"
151 #include "streamer.h"
152 #include "circular.h"
153 #include "synch.h"
154 #include "main.h"
155 
156 #define HELP_MSG_LINES 8
157 
158 const char *help_msg[HELP_MSG_LINES] = { APPNAME,
159                                          NULL,
160                                          "Usage:",
161                                          "\ticegenerator [-h] [-d] [-f filename]",
162                                          NULL,
163                                          "\t-h\t\tDisplay this help message",
164                                          "\t-f filename\tSpecify configuration file",
165                                          "\t-d\t\tRun in foreground - no daemon" };
166 cLog *log_obj = NULL;
167 cSignal *sig_obj = NULL;
168 cConfig *conf_obj = NULL;
169 cStreamer *stream_obj = NULL;
170 cCircularList *ready_obj = NULL, *played_obj = NULL;
171 cSemaphore *sem_obj = NULL;
172 int track_load, end_track, data_mutex, start_meta;
173 volatile bool time_to_quit = false;
174 bool ok_to_end = false, changed = false;
175 char ErrStr[127] = "Unknown error";
176 
signal_termination_proc(const int sig)177 void signal_termination_proc(const int sig)
178 {
179   if (!time_to_quit)
180     log_obj->WriteLog("Wait for all child process to terminate...");
181   time_to_quit = true;
182 }
183 
clean_objs()184 void clean_objs()
185 {
186   if (sem_obj != NULL)
187     delete sem_obj;
188 
189   if (played_obj != NULL)
190     delete played_obj;
191 
192   if (ready_obj != NULL)
193     delete ready_obj;
194 
195   if (stream_obj != NULL)
196     delete stream_obj;
197 
198   if (conf_obj != NULL)
199     delete conf_obj;
200 
201   if (sig_obj != NULL)
202     delete sig_obj;
203 
204   if (log_obj != NULL)
205     delete log_obj;
206 }
207 
main(int argc,char ** argv)208 int main(int argc, char **argv)
209 {
210   int i, c;
211   bool is_daemon = true;
212   pthread_t player_tid, selector_tid, data_streamer_tid, data_server_tid;
213 
214   try
215   {
216     /* Parse command line parameters and reads configuration file */
217     if (argc > 1)
218       while ((c = getopt (argc, argv, "hdf:")) != -1)
219         switch (c)
220         {
221       	  case 'h': for (i = 0; i < HELP_MSG_LINES; i++)
222       	            {
223       	              if (help_msg[i] != NULL)
224       	                printf("%s",help_msg[i]);
225       	              printf("\n");
226       	            }
227       	            printf("\n");
228       	            exit(0);
229       	            break;
230       	  case 'f': conf_obj = new cConfig(optarg);
231       	                       break;
232       	  case 'd': is_daemon = false;
233       	            break;
234       	  case ':': printf("Invalid parameter\n");
235       	            exit(1);
236       	            break;
237         }
238 
239     if (conf_obj == NULL)
240       conf_obj = new cConfig(DEFAULT_CONF_FILE);
241 
242     /* Init log file */
243     log_obj = new cLog(conf_obj);
244 
245     /* Select streaming type and scans directories */
246     ready_obj = new cCircularList;
247     ready_obj->ParsePlayList(conf_obj);
248     if (ready_obj->Empty())
249     {
250       strcpy(ErrStr,"No files found");
251       throw 0;
252     }
253     else
254     {
255       played_obj = new cCircularList;
256       ready_obj->SetStart();
257     }
258 
259     /* Opens connection to stream server */
260     stream_obj = new cStreamer;
261     stream_obj->SetData(conf_obj);
262     stream_obj->Connect();
263 
264     log_obj->WriteLog("Connected to stream server");
265 
266     /* Init synchronization module */
267     sem_obj = new cSemaphore;
268     data_mutex = sem_obj->AddSem(1);
269     track_load = sem_obj->AddSem(0);
270     end_track = sem_obj->AddSem(0);
271     start_meta = sem_obj->AddSem(0);
272 
273     /* Launches as a daemon */
274     if (is_daemon)
275     {
276       fclose(stdin);
277       fclose(stdout);
278       fclose(stderr);
279       if (fork())
280         exit(0);
281       setsid();
282       if (fork())
283         exit(0);
284       chdir("/");
285       umask(0);
286       log_obj->WriteLog("Going to daemon land...");
287     }
288 
289     /* Install signal handler */
290     sig_obj = new cSignal();
291     sig_obj->AddHandler(SIGHUP,signal_termination_proc);
292     sig_obj->AddHandler(SIGTERM,signal_termination_proc);
293     sig_obj->AddHandler(SIGINT,signal_termination_proc);
294     sig_obj->AddHandler(SIGQUIT,signal_termination_proc);
295     sig_obj->AddHandler(SIGABRT,signal_termination_proc);
296 
297     /* Generates threads */
298     pthread_create(&player_tid,NULL,player,NULL);
299     pthread_create(&data_streamer_tid,NULL,data_streamer,NULL);
300     pthread_create(&selector_tid,NULL,selector,NULL);
301     if (conf_obj->GetValue(_dataport) != NULL)
302       pthread_create(&data_server_tid,NULL,data_server,NULL);
303 
304     /* Doesn't care about SIGS */
305     sig_obj->Block();
306 
307     /* Waits for childen to die */
308     pthread_join(player_tid,NULL);
309     pthread_join(data_streamer_tid,NULL);
310     pthread_join(selector_tid,NULL);
311 
312     if (conf_obj->GetValue(_dataport) != NULL)
313     {
314       close_data_server();
315       pthread_join(data_server_tid,NULL);
316     }
317 
318     log_obj->WriteLog("Have a nice day");
319 
320     /* Time to go out */
321     stream_obj->Disconnect();
322     clean_objs();
323   }
324   catch (...)
325   {
326     /* Something went wrong ... */
327     if (is_daemon && (log_obj != NULL))
328       log_obj->WriteLog(ErrStr);
329     else
330       printf("%s\n\n",ErrStr);
331 
332     clean_objs();
333   }
334 
335   return 0;
336 }
337