1 /*
2   This file is part of Deadbeef Player source code
3   http://deadbeef.sourceforge.net
4 
5   application launcher, compatible with GNU/Linux and most other POSIX systems
6 
7   Copyright (C) 2009-2016 Alexey Yakovenko
8 
9   This software is provided 'as-is', without any express or implied
10   warranty.  In no event will the authors be held liable for any damages
11   arising from the use of this software.
12 
13   Permission is granted to anyone to use this software for any purpose,
14   including commercial applications, and to alter it and redistribute it
15   freely, subject to the following restrictions:
16 
17   1. The origin of this software must not be misrepresented; you must not
18      claim that you wrote the original software. If you use this software
19      in a product, an acknowledgment in the product documentation would be
20      appreciated but is not required.
21   2. Altered source versions must be plainly marked as such, and must not be
22      misrepresented as being the original software.
23   3. This notice may not be removed or altered from any source distribution.
24 
25   Alexey Yakovenko waker@users.sourceforge.net
26 */
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <stddef.h>
35 #include <time.h>
36 #include <locale.h>
37 #ifdef __linux__
38 #include <sys/prctl.h>
39 #endif
40 #ifndef __linux__
41 #define _POSIX_C_SOURCE 1
42 #endif
43 #include <limits.h>
44 #include <errno.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <sys/select.h>
50 #include <sys/un.h>
51 #include <sys/fcntl.h>
52 #include <sys/errno.h>
53 #include <signal.h>
54 #ifdef __GLIBC__
55 #include <execinfo.h>
56 #endif
57 #include <unistd.h>
58 #include "gettext.h"
59 #include "playlist.h"
60 #include "threading.h"
61 #include "messagepump.h"
62 #include "streamer.h"
63 #include "conf.h"
64 #include "volume.h"
65 #include "plugins.h"
66 #include "common.h"
67 #include "junklib.h"
68 #ifdef HAVE_COCOAUI
69 #include "cocoautil.h"
70 #endif
71 #include "playqueue.h"
72 #include "tf.h"
73 
74 #ifndef PREFIX
75 #error PREFIX must be defined
76 #endif
77 
78 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
79 #define trace(fmt,...)
80 
81 #ifdef HAVE_COCOAUI
82 #define SYS_CONFIG_DIR "Library/Preferences"
83 #else
84 #define SYS_CONFIG_DIR ".config"
85 #endif
86 
87 // some common global variables
88 char sys_install_path[PATH_MAX]; // see deadbeef->get_prefix
89 char confdir[PATH_MAX]; // $HOME/.config
90 char dbconfdir[PATH_MAX]; // $HOME/.config/deadbeef
91 char dbinstalldir[PATH_MAX]; // see deadbeef->get_prefix
92 char dbdocdir[PATH_MAX]; // see deadbeef->get_doc_dir
93 char dbplugindir[PATH_MAX]; // see deadbeef->get_plugin_dir
94 char dbpixmapdir[PATH_MAX]; // see deadbeef->get_pixmap_dir
95 char dbcachedir[PATH_MAX];
96 
97 char use_gui_plugin[100];
98 
99 static void
print_help(void)100 print_help (void) {
101 #ifdef ENABLE_NLS
102 	bind_textdomain_codeset (PACKAGE, "");
103 #endif
104     fprintf (stdout, _("Usage: deadbeef [options] [--] [file(s)]\n"));
105     fprintf (stdout, _("Options:\n"));
106     fprintf (stdout, _("   --help  or  -h     Print help (this message) and exit\n"));
107     fprintf (stdout, _("   --quit             Quit player\n"));
108     fprintf (stdout, _("   --version          Print version info and exit\n"));
109     fprintf (stdout, _("   --play             Start playback\n"));
110     fprintf (stdout, _("   --stop             Stop playback\n"));
111     fprintf (stdout, _("   --pause            Pause playback\n"));
112     fprintf (stdout, _("   --toggle-pause     Toggle pause\n"));
113     fprintf (stdout, _("   --play-pause       Start playback if stopped, toggle pause otherwise\n"));
114     fprintf (stdout, _("   --next             Next song in playlist\n"));
115     fprintf (stdout, _("   --prev             Previous song in playlist\n"));
116     fprintf (stdout, _("   --random           Random song in playlist\n"));
117     fprintf (stdout, _("   --queue            Append file(s) to existing playlist\n"));
118     fprintf (stdout, _("   --gui PLUGIN       Tells which GUI plugin to use, default is \"GTK2\"\n"));
119     fprintf (stdout, _("   --nowplaying FMT   Print formatted track name to stdout\n"));
120     fprintf (stdout, _("                      FMT %%-syntax: [a]rtist, [t]itle, al[b]um,\n"
121                 "                      [l]ength, track[n]umber, [y]ear, [c]omment,\n"
122                 "                      copy[r]ight, [e]lapsed\n"));
123     fprintf (stdout, _("                      example: --nowplaying \"%%a - %%t\" should print \"artist - title\"\n"));
124     fprintf (stdout, _("                      for more info, see %s\n"), "http://github.com/Alexey-Yakovenko/deadbeef/wiki/Title-formatting");
125     fprintf (stdout, _("                      NOTE: --nowplaying is deprecated.\n"));
126     fprintf (stdout, _("   --nowplaying-tf FMT  Print formatted track name to stdout, using the new title formatting\n"));
127     fprintf (stdout, _("                      FMT syntax: http://github.com/Alexey-Yakovenko/deadbeef/wiki/Title-formatting-2.0\n"));
128     fprintf (stdout, _("                      example: --nowplaying-tf \"%%artist%% - %%title%%\" should print \"artist - title\"\n"));
129 #ifdef ENABLE_NLS
130 	bind_textdomain_codeset (PACKAGE, "UTF-8");
131 #endif
132 }
133 
134 // Parse command line an return a single buffer with all
135 // parameters concatenated (separated by \0).  File names
136 // are resolved.
137 char*
prepare_command_line(int argc,char * argv[],int * size)138 prepare_command_line (int argc, char *argv[], int *size) {
139     int seen_ddash = 0;
140 
141     // initial buffer limit, will expand if needed
142     int limit = 4096;
143     char *buf = (char*) malloc (limit);
144 
145     if (argc <= 1) {
146         buf[0] = 0;
147         *size = 1;
148         return buf;
149     }
150 
151     int p = 0;
152     for (int i = 1; i < argc; i++) {
153         // if argument is a filename, try to resolve it
154         char resolved[PATH_MAX];
155         char *arg;
156         if (!strncmp ("--", argv[i], 2) && !seen_ddash || !realpath (argv[i], resolved)) {
157             arg = argv[i];
158         }
159         else {
160             arg = resolved;
161         }
162 
163         // make sure that there is enough space in the buffer;
164         // re-allocate, if needed
165         int arglen = strlen(arg) + 1;
166         while (p + arglen >= limit) {
167             char *newbuf = (char*) malloc (limit * 2);
168             memcpy (newbuf, buf, p);
169             free (buf);
170             limit *= 2;
171             buf = newbuf;
172         }
173 
174         memcpy (buf + p, arg, arglen);
175         p += arglen;
176 
177         if (!strcmp("--", argv[i])) {
178             seen_ddash = 1;
179         }
180     }
181 
182     *size = p;
183     return buf;
184 }
185 
186 
187 // this function executes server-side commands only
188 // must be called only from within server
189 // -1 error, program must exit with error code -1
190 //  0 proceed normally as nothing happened
191 //  1 no error, but program must exit with error code 0
192 //  2 don't load playlist on startup
193 //  when executed in remote server -- error code will be ignored
194 int
server_exec_command_line(const char * cmdline,int len,char * sendback,int sbsize)195 server_exec_command_line (const char *cmdline, int len, char *sendback, int sbsize) {
196     if (sendback) {
197         sendback[0] = 0;
198     }
199     const uint8_t *parg = (const uint8_t *)cmdline;
200     const uint8_t *pend = cmdline + len;
201     int queue = 0;
202     while (parg < pend) {
203         const char *parg_c = parg;
204         if (strlen (parg) >= 2 && parg[0] == '-' && parg[1] != '-') {
205             parg += strlen (parg);
206             parg++;
207             return 0; // running under osx debugger?
208         }
209         else if (!strcmp (parg, "--nowplaying")) {
210             parg += strlen (parg);
211             parg++;
212             if (parg >= pend) {
213                 const char *errtext = "--nowplaying expects format argument";
214                 if (sendback) {
215                     snprintf (sendback, sbsize, "error %s\n", errtext);
216                     return 0;
217                 }
218                 else {
219                     fprintf (stderr, "%s\n", errtext);
220                     return -1;
221                 }
222             }
223             char out[2048];
224             playItem_t *curr = streamer_get_playing_track ();
225             if (curr) {
226                 pl_format_title (curr, -1, out, sizeof (out), -1, parg);
227                 pl_item_unref (curr);
228             }
229             else {
230                 strcpy (out, "nothing");
231             }
232             if (sendback) {
233                 snprintf (sendback, sbsize, "nowplaying %s", out);
234             }
235             else {
236                 fwrite (out, 1, strlen (out), stdout);
237                 return 1; // exit
238             }
239         }
240         else if (!strcmp (parg, "--nowplaying-tf")) {
241             parg += strlen (parg);
242             parg++;
243             if (parg >= pend) {
244                 const char *errtext = "--nowplaying expects format argument";
245                 if (sendback) {
246                     snprintf (sendback, sbsize, "error %s\n", errtext);
247                     return 0;
248                 }
249                 else {
250                     fprintf (stderr, "%s\n", errtext);
251                     return -1;
252                 }
253             }
254             char out[2048];
255             playItem_t *curr = streamer_get_playing_track ();
256             char *script = tf_compile (parg);
257             if (script) {
258                 ddb_tf_context_t ctx = {
259                     ._size = sizeof (ddb_tf_context_t),
260                     .it = (DB_playItem_t *)curr,
261                 };
262                 tf_eval (&ctx, script, out, sizeof (out));
263                 tf_free (script);
264             }
265             else {
266                 *out = 0;
267             }
268             if (curr) {
269                 pl_item_unref (curr);
270             }
271             if (sendback) {
272                 snprintf (sendback, sbsize, "nowplaying %s", out);
273             }
274             else {
275                 fwrite (out, 1, strlen (out), stdout);
276                 return 1; // exit
277             }
278         }
279         else if (!strcmp (parg, "--next")) {
280             messagepump_push (DB_EV_NEXT, 0, 0, 0);
281             return 0;
282         }
283         else if (!strcmp (parg, "--prev")) {
284             messagepump_push (DB_EV_PREV, 0, 0, 0);
285             return 0;
286         }
287         else if (!strcmp (parg, "--play")) {
288             messagepump_push (DB_EV_PLAY_CURRENT, 0, 0, 0);
289             return 0;
290         }
291         else if (!strcmp (parg, "--stop")) {
292             messagepump_push (DB_EV_STOP, 0, 0, 0);
293             return 0;
294         }
295         else if (!strcmp (parg, "--pause")) {
296             messagepump_push (DB_EV_PAUSE, 0, 0, 0);
297             return 0;
298         }
299         else if (!strcmp (parg, "--toggle-pause")) {
300             messagepump_push (DB_EV_TOGGLE_PAUSE, 0, 0, 0);
301             return 0;
302         }
303         else if (!strcmp (parg, "--play-pause")) {
304             int state = deadbeef->get_output ()->state ();
305             if (state == OUTPUT_STATE_PLAYING) {
306                 deadbeef->sendmessage (DB_EV_PAUSE, 0, 0, 0);
307             }
308             else {
309                 deadbeef->sendmessage (DB_EV_PLAY_CURRENT, 0, 0, 0);
310             }
311             return 0;
312         }
313         else if (!strcmp (parg, "--random")) {
314             messagepump_push (DB_EV_PLAY_RANDOM, 0, 0, 0);
315             return 0;
316         }
317         else if (!strcmp (parg, "--queue")) {
318             queue = 1;
319         }
320         else if (!strcmp (parg, "--quit")) {
321             messagepump_push (DB_EV_TERMINATE, 0, 0, 0);
322         }
323         else if (!strcmp (parg, "--sm-client-id")) {
324             parg += strlen (parg);
325             parg++;
326             if (parg < pend) {
327                 parg += strlen (parg);
328                 parg++;
329             }
330             continue;
331         }
332         else if (!strcmp (parg, "--gui")) {
333             // need to skip --gui here, it is handled in the client cmdline
334             parg += strlen (parg);
335             parg++;
336             if (parg >= pend) {
337                 break;
338             }
339             parg += strlen (parg);
340             parg++;
341             continue;
342         }
343         else if (parg[0] != '-') {
344             break; // unknown option is filename
345         }
346         parg += strlen (parg);
347         parg++;
348     }
349     if (parg < pend) {
350         if (conf_get_int ("cli_add_to_specific_playlist", 1)) {
351             char str[200];
352             conf_get_str ("cli_add_playlist_name", "Default", str, sizeof (str));
353             int idx = plt_find (str);
354             if (idx < 0) {
355                 idx = plt_add (plt_get_count (), str);
356             }
357             if (idx >= 0) {
358                 plt_set_curr_idx (idx);
359             }
360         }
361         playlist_t *curr_plt = plt_get_curr ();
362         if (plt_add_files_begin (curr_plt, 0) != 0) {
363             plt_unref (curr_plt);
364             snprintf (sendback, sbsize, "it's not allowed to add files to playlist right now, because another file adding operation is in progress. please try again later.");
365             return 0;
366         }
367         // add files
368         if (!queue) {
369             plt_clear (curr_plt);
370             messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
371             plt_reset_cursor (curr_plt);
372         }
373         while (parg < pend) {
374             char resolved[PATH_MAX];
375             const char *pname;
376             if (realpath (parg, resolved)) {
377                 pname = resolved;
378             }
379             else {
380                 pname = parg;
381             }
382             if (deadbeef->plt_add_dir2 (0, (ddb_playlist_t*)curr_plt, pname, NULL, NULL) < 0) {
383                 if (deadbeef->plt_add_file2 (0, (ddb_playlist_t*)curr_plt, pname, NULL, NULL) < 0) {
384                     int ab = 0;
385                     playItem_t *it = plt_load2 (0, curr_plt, NULL, pname, &ab, NULL, NULL);
386                     if (!it) {
387                         fprintf (stderr, "failed to add file or folder %s\n", pname);
388                     }
389                 }
390             }
391             parg += strlen (parg);
392             parg++;
393         }
394         pl_save_current ();
395         messagepump_push (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
396         plt_add_files_end (curr_plt, 0);
397         plt_unref (curr_plt);
398         if (!queue) {
399             messagepump_push (DB_EV_PLAY_NUM, 0, 0, 0);
400             return 2; // don't reload playlist at startup
401         }
402     }
403     return 0;
404 }
405 
406 static struct sockaddr_un srv_local;
407 static struct sockaddr_un srv_remote;
408 static unsigned srv_socket;
409 
410 #if USE_ABSTRACT_SOCKET_NAME
411 static char server_id[] = "\0deadbeefplayer";
412 #endif
413 
414 int
server_start(void)415 server_start (void) {
416     fprintf (stderr, "server_start\n");
417     srv_socket = socket (AF_UNIX, SOCK_STREAM, 0);
418     int flags;
419     flags = fcntl (srv_socket, F_GETFL,0);
420     if (flags == -1) {
421         perror ("fcntl F_GETFL");
422         return -1;
423     }
424     if (fcntl(srv_socket, F_SETFL, flags | O_NONBLOCK) < 0) {
425         perror ("fcntl F_SETFL");
426         return -1;
427     }
428     memset (&srv_local, 0, sizeof (srv_local));
429     srv_local.sun_family = AF_UNIX;
430 
431 #if USE_ABSTRACT_SOCKET_NAME
432     memcpy (srv_local.sun_path, server_id, sizeof (server_id));
433     int len = offsetof(struct sockaddr_un, sun_path) + sizeof (server_id)-1;
434 #else
435     char *socketdirenv = getenv ("DDB_SOCKET_DIR");
436     snprintf (srv_local.sun_path, sizeof (srv_local.sun_path), "%s/socket", socketdirenv ? socketdirenv : dbconfdir);
437     if (unlink(srv_local.sun_path) < 0) {
438         perror ("INFO: unlink socket");
439     }
440     int len = offsetof(struct sockaddr_un, sun_path) + strlen (srv_local.sun_path);
441 #endif
442 
443     if (bind(srv_socket, (struct sockaddr *)&srv_local, len) < 0) {
444         perror ("bind");
445         return -1;
446     }
447 
448     if (listen(srv_socket, 5) == -1) {
449         perror("listen");
450         return -1;
451     }
452     return 0;
453 }
454 
455 void
server_close(void)456 server_close (void) {
457     if (srv_socket) {
458         close (srv_socket);
459         srv_socket = 0;
460     }
461 }
462 
463 // Read the whole message till end-of-stream
464 char*
read_entire_message(int sockfd,int * size)465 read_entire_message (int sockfd, int *size) {
466     int bufsize = 4096; // initial buffer size, will expand if
467                         // the actual package turns out to be bigger
468     char *buf = (char*) malloc(bufsize);
469     int rdp = 0;
470 
471     for (;;) {
472         if (rdp >= bufsize) {
473             int newsize = bufsize * 2;
474             char *newbuf = (char*) malloc(newsize);
475             memcpy(newbuf, buf, rdp);
476             free(buf);
477             buf = newbuf;
478             bufsize = newsize;
479         }
480 
481         int rd = recv(sockfd, buf + rdp, bufsize - rdp, 0);
482         if (rd < 0) {
483             if (errno == EAGAIN) {
484                 usleep (50000);
485                 continue;
486             }
487             free(buf);
488             return NULL;
489         }
490         if (rd == 0) {
491             break;
492         }
493         rdp += rd;
494     }
495 
496     *size = rdp;
497     return buf;
498 }
499 
500 int
server_update(void)501 server_update (void) {
502     // handle remote stuff
503     int t = sizeof (srv_remote);
504     unsigned s2;
505     s2 = accept(srv_socket, (struct sockaddr *)&srv_remote, &t);
506     if (s2 == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
507         perror("accept");
508         return -1;
509     }
510     else if (s2 != -1) {
511         int size = -1;
512         char *buf = read_entire_message(s2, &size);
513         char sendback[1024] = "";
514         if (size > 0) {
515             if (size == 1 && buf[0] == 0) {
516                 // FIXME: that should be called right after activation of gui plugin
517                 messagepump_push (DB_EV_ACTIVATED, 0, 0, 0);
518             }
519             else {
520                 server_exec_command_line (buf, size, sendback, sizeof (sendback));
521             }
522         }
523         if (sendback[0]) {
524             // send nowplaying back to client
525             send (s2, sendback, strlen (sendback)+1, 0);
526         }
527         else {
528             send (s2, "", 1, 0);
529         }
530         close(s2);
531 
532         if (buf) {
533             free(buf);
534         }
535     }
536     return 0;
537 }
538 
539 static uintptr_t server_tid;
540 static int server_terminate;
541 
542 void
server_loop(void * ctx)543 server_loop (void *ctx) {
544 #ifdef __linux__
545     prctl (PR_SET_NAME, "deadbeef-server", 0, 0, 0, 0);
546 #endif
547     fd_set rds;
548     int ret;
549     struct timeval timeout = {0, 0};
550 
551     FD_ZERO(&rds);
552     while (!server_terminate) {
553         FD_SET(srv_socket, &rds);
554         timeout.tv_usec = 500000;
555         if ((ret = select(srv_socket + 1, &rds, NULL, NULL, &timeout)) < 0 && errno != EINTR) {
556             perror("select");
557             exit (-1);
558         }
559         if (ret > 0) {
560             if (server_update () < 0) {
561                 messagepump_push (DB_EV_TERMINATE, 0, 0, 0);
562             }
563         }
564     }
565 }
566 
567 void
save_resume_state(void)568 save_resume_state (void) {
569     playItem_t *trk = streamer_get_playing_track ();
570     DB_output_t *output = plug_get_output ();
571     float playpos = -1;
572     int playtrack = -1;
573     int playlist = streamer_get_current_playlist ();
574     int paused = (output->state () == OUTPUT_STATE_PAUSED);
575     if (trk && playlist >= 0) {
576         playtrack = str_get_idx_of (trk);
577         playpos = streamer_get_playpos ();
578         pl_item_unref (trk);
579     }
580 
581     conf_set_float ("resume.position", playpos);
582     conf_set_int ("resume.track", playtrack);
583     conf_set_int ("resume.playlist", playlist);
584     conf_set_int ("resume.paused", paused);
585 }
586 
587 void
player_mainloop(void)588 player_mainloop (void) {
589     for (;;) {
590         uint32_t msg;
591         uintptr_t ctx;
592         uint32_t p1;
593         uint32_t p2;
594         int term = 0;
595         while (messagepump_pop(&msg, &ctx, &p1, &p2) != -1) {
596             // broadcast to all plugins
597             DB_plugin_t **plugs = plug_get_list ();
598             for (int n = 0; plugs[n]; n++) {
599                 if (plugs[n]->message) {
600                     plugs[n]->message (msg, ctx, p1, p2);
601                 }
602             }
603             if (!term) {
604                 DB_output_t *output = plug_get_output ();
605                 switch (msg) {
606                 case DB_EV_REINIT_SOUND:
607                     plug_reinit_sound ();
608                     streamer_reset (1);
609                     conf_save ();
610                     break;
611                 case DB_EV_TERMINATE:
612                     {
613                         save_resume_state ();
614 
615                         playqueue_clear ();
616 
617                         // stop streaming and playback before unloading plugins
618                         DB_output_t *output = plug_get_output ();
619                         output->stop ();
620                         streamer_free ();
621                         output->free ();
622                         term = 1;
623                     }
624                     break;
625                 case DB_EV_PLAY_CURRENT:
626                     streamer_play_current_track ();
627                     break;
628                 case DB_EV_PLAY_NUM:
629                     playqueue_clear ();
630                     streamer_set_nextsong (p1, 4);
631                     break;
632                 case DB_EV_STOP:
633                     streamer_set_nextsong (-2, 0);
634                     break;
635                 case DB_EV_NEXT:
636                     streamer_move_to_nextsong (1);
637                     break;
638                 case DB_EV_PREV:
639                     streamer_move_to_prevsong (1);
640                     break;
641                 case DB_EV_PAUSE:
642                     if (output->state () != OUTPUT_STATE_PAUSED) {
643                         output->pause ();
644                         messagepump_push (DB_EV_PAUSED, 0, 1, 0);
645                     }
646                     break;
647                 case DB_EV_TOGGLE_PAUSE:
648                     if (output->state () == OUTPUT_STATE_PAUSED) {
649                         streamer_play_current_track ();
650                     }
651                     else {
652                         output->pause ();
653                         messagepump_push (DB_EV_PAUSED, 0, 1, 0);
654                     }
655                     break;
656                 case DB_EV_PLAY_RANDOM:
657                     streamer_move_to_randomsong (1);
658                     break;
659                 case DB_EV_CONFIGCHANGED:
660                     conf_save ();
661                     streamer_configchanged ();
662                     junk_configchanged ();
663                     break;
664                 case DB_EV_SEEK:
665                     streamer_set_seek (p1 / 1000.f);
666                     break;
667                 }
668             }
669             if (msg >= DB_EV_FIRST && ctx) {
670                 messagepump_event_free ((ddb_event_t *)ctx);
671             }
672         }
673         if (term) {
674             return;
675         }
676         messagepump_wait ();
677     }
678 }
679 
680 #ifdef __GLIBC__
681 void
sigsegv_handler(int sig)682 sigsegv_handler (int sig) {
683     fprintf (stderr, "Segmentation Fault\n");
684     int j, nptrs;
685 #define SIZE 100
686     void *buffer[100];
687     char **strings;
688 
689     nptrs = backtrace(buffer, SIZE);
690     printf("backtrace() returned %d addresses\n", nptrs);
691 
692     /* The call
693      * backtrace_symbols_fd(buffer,
694      * nptrs,
695      * STDOUT_FILENO)
696      would produce similar output to the following: */
697 
698     strings = backtrace_symbols(buffer, nptrs);
699     if (strings == NULL) {
700         perror("backtrace_symbols");
701         exit(EXIT_FAILURE);
702     }
703 
704     for (j = 0; j < nptrs; j++)
705         printf("%s\n", strings[j]);
706 
707     free(strings);
708     exit (0);
709 }
710 #endif
711 
712 void
restore_resume_state(void)713 restore_resume_state (void) {
714     DB_output_t *output = plug_get_output ();
715     if (conf_get_int ("resume_last_session", 0) && output->state () == OUTPUT_STATE_STOPPED) {
716         int plt = conf_get_int ("resume.playlist", -1);
717         int track = conf_get_int ("resume.track", -1);
718         float pos = conf_get_float ("resume.position", -1);
719         int paused = conf_get_int ("resume.paused", 0);
720         trace ("resume: track %d pos %f playlist %d\n", track, pos, plt);
721         if (plt >= 0 && track >= 0 && pos >= 0) {
722             streamer_lock (); // need to hold streamer thread to make the resume operation atomic
723             streamer_set_current_playlist (plt);
724             streamer_set_nextsong (track, paused ? 2 : 3);
725             streamer_set_seek (pos);
726             streamer_unlock ();
727         }
728     }
729 }
730 
731 uintptr_t mainloop_tid;
732 
733 DB_plugin_t *
plug_get_gui(void)734 plug_get_gui (void) {
735     struct DB_plugin_s **plugs = plug_get_list ();
736     for (int i = 0; plugs[i]; i++) {
737         if (plugs[i]->type == DB_PLUGIN_GUI) {
738             return plugs[i];
739         }
740     }
741     return NULL;
742 }
743 
744 void
main_cleanup_and_quit(void)745 main_cleanup_and_quit (void) {
746     // terminate server and wait for completion
747     if (server_tid) {
748         server_terminate = 1;
749         thread_join (server_tid);
750         server_tid = 0;
751     }
752 
753     // save config
754     pl_save_all ();
755     conf_save ();
756 
757     // delete legacy session file
758     {
759         char sessfile[1024]; // $HOME/.config/deadbeef/session
760         if (snprintf (sessfile, sizeof (sessfile), "%s/deadbeef/session", confdir) < sizeof (sessfile)) {
761             unlink (sessfile);
762         }
763     }
764 
765     // stop receiving messages from outside
766     server_close ();
767 
768     // plugins might still hold references to playitems,
769     // and query configuration in background
770     // so unload everything 1st before final cleanup
771     plug_disconnect_all ();
772     plug_unload_all ();
773 
774     // at this point we can simply do exit(0), but let's clean up for debugging
775     pl_free (); // may access conf_*
776     conf_free ();
777 
778     fprintf (stderr, "messagepump_free\n");
779     messagepump_free ();
780     fprintf (stderr, "plug_cleanup\n");
781     plug_cleanup ();
782 
783     fprintf (stderr, "hej-hej!\n");
784 }
785 
786 static void
mainloop_thread(void * ctx)787 mainloop_thread (void *ctx) {
788     // this runs until DB_EV_TERMINATE is sent (blocks right here)
789     player_mainloop ();
790 
791     // tell the gui thread to finish
792     DB_plugin_t *gui = plug_get_gui ();
793 #if HAVE_COCOAUI
794     main_cleanup_and_quit();
795 #endif
796     if (gui) {
797         gui->stop ();
798     }
799     return;
800 }
801 
802 int
main(int argc,char * argv[])803 main (int argc, char *argv[]) {
804     int portable = 0;
805 #if STATICLINK
806     int staticlink = 1;
807 #else
808     int staticlink = 0;
809 #endif
810 #if PORTABLE
811     portable = 1;
812     if (!realpath (argv[0], dbinstalldir)) {
813         strcpy (dbinstalldir, argv[0]);
814     }
815     char *e = strrchr (dbinstalldir, '/');
816     if (e) {
817         *e = 0;
818     }
819     else {
820         fprintf (stderr, "couldn't determine install folder from path %s\n", argv[0]);
821         exit (-1);
822     }
823 #else
824     if (!realpath (argv[0], dbinstalldir)) {
825         strcpy (dbinstalldir, argv[0]);
826     }
827     char *e = strrchr (dbinstalldir, '/');
828     if (e) {
829         *e = 0;
830         struct stat st;
831         char checkpath[PATH_MAX];
832         snprintf (checkpath, sizeof (checkpath), "%s/.ddb_portable", dbinstalldir);
833         if (!stat (checkpath, &st)) {
834             if (S_ISREG (st.st_mode)) {
835                 portable = 1;
836             }
837         }
838     }
839     if (!portable) {
840         strcpy (dbinstalldir, PREFIX);
841     }
842 #endif
843 
844 #ifdef __GLIBC__
845     signal (SIGSEGV, sigsegv_handler);
846 #endif
847     setlocale (LC_ALL, "");
848     setlocale (LC_NUMERIC, "C");
849 #ifdef ENABLE_NLS
850 //    fprintf (stderr, "enabling gettext support: package=" PACKAGE ", dir=" LOCALEDIR "...\n");
851     if (portable) {
852         char localedir[PATH_MAX];
853         snprintf (localedir, sizeof (localedir), "%s/locale", dbinstalldir);
854         bindtextdomain (PACKAGE, localedir);
855     }
856     else {
857         bindtextdomain (PACKAGE, LOCALEDIR);
858     }
859 	bind_textdomain_codeset (PACKAGE, "UTF-8");
860 	textdomain (PACKAGE);
861 #endif
862 
863     fprintf (stderr, "starting deadbeef " VERSION "%s%s\n", staticlink ? " [static]" : "", portable ? " [portable]" : "");
864     srand (time (NULL));
865 #ifdef __linux__
866     prctl (PR_SET_NAME, "deadbeef-main", 0, 0, 0, 0);
867 #endif
868 
869 #if PORTABLE_FULL
870     if (snprintf (confdir, sizeof (confdir), "%s/config", dbinstalldir) > sizeof (confdir)) {
871         fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
872         return -1;
873     }
874 
875     strcpy (dbconfdir, confdir);
876 
877     if (snprintf (confdir, sizeof (confdir), "%s/cache", dbcachedir) > sizeof (confdir)) {
878         fprintf (stderr, "fatal: too long cache path %s\n", dbcachedir);
879         return -1;
880     }
881 #else
882     char *homedir = getenv ("HOME");
883     if (!homedir) {
884         fprintf (stderr, "unable to find home directory. stopping.\n");
885         return -1;
886     }
887 
888     char *xdg_conf_dir = getenv ("XDG_CONFIG_HOME");
889     if (xdg_conf_dir) {
890         if (snprintf (confdir, sizeof (confdir), "%s", xdg_conf_dir) > sizeof (confdir)) {
891             fprintf (stderr, "fatal: XDG_CONFIG_HOME value is too long: %s\n", xdg_conf_dir);
892             return -1;
893         }
894     }
895     else {
896         if (snprintf (confdir, sizeof (confdir), "%s/" SYS_CONFIG_DIR, homedir) > sizeof (confdir)) {
897             fprintf (stderr, "fatal: HOME value is too long: %s\n", homedir);
898             return -1;
899         }
900     }
901     if (snprintf (dbconfdir, sizeof (dbconfdir), "%s/deadbeef", confdir) > sizeof (dbconfdir)) {
902         fprintf (stderr, "fatal: out of memory while configuring\n");
903         return -1;
904     }
905     mkdir (confdir, 0755);
906 
907     const char *xdg_cache = getenv("XDG_CACHE_HOME");
908     const char *cache_root = xdg_cache ? xdg_cache : getenv("HOME");
909     if (snprintf(dbcachedir, sizeof (dbcachedir), xdg_cache ? "%s/deadbeef/" : "%s/.cache/deadbeef/", cache_root) >= sizeof (dbcachedir)) {
910         fprintf (stderr, "fatal: too long cache path %s\n", dbcachedir);
911         return -1;
912     }
913 #endif
914 
915 
916     if (portable) {
917         if (snprintf (dbdocdir, sizeof (dbdocdir), "%s/doc", dbinstalldir) > sizeof (dbdocdir)) {
918             fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
919             return -1;
920         }
921 #ifdef HAVE_COCOAUI
922         char respath[PATH_MAX];
923         cocoautil_get_resources_path (respath, sizeof (respath));
924         if (snprintf (dbplugindir, sizeof (dbplugindir), "%s", respath) > sizeof (dbplugindir)) {
925             fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
926             return -1;
927         }
928 #else
929         if (snprintf (dbplugindir, sizeof (dbplugindir), "%s/plugins", dbinstalldir) > sizeof (dbplugindir)) {
930             fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
931             return -1;
932         }
933 #endif
934         if (snprintf (dbpixmapdir, sizeof (dbpixmapdir), "%s/pixmaps", dbinstalldir) > sizeof (dbpixmapdir)) {
935             fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
936             return -1;
937         }
938         mkdir (dbplugindir, 0755);
939     }
940     else {
941         if (snprintf (dbdocdir, sizeof (dbdocdir), "%s", DOCDIR) > sizeof (dbdocdir)) {
942             fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
943             return -1;
944         }
945         char *env_plugin_dir = getenv ("DEADBEEF_PLUGIN_DIR");
946         if (env_plugin_dir) {
947             strncpy (dbplugindir, env_plugin_dir, sizeof(dbplugindir));
948             if (dbplugindir[sizeof(dbplugindir) - 1] != 0) {
949                 fprintf (stderr, "fatal: too long plugin path %s\n", env_plugin_dir);
950                 return -1;
951             }
952         }
953         else if (snprintf (dbplugindir, sizeof (dbplugindir), "%s/deadbeef", LIBDIR) > sizeof (dbplugindir)) {
954             fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
955             return -1;
956         }
957         if (snprintf (dbpixmapdir, sizeof (dbpixmapdir), "%s/share/deadbeef/pixmaps", PREFIX) > sizeof (dbpixmapdir)) {
958             fprintf (stderr, "fatal: too long install path %s\n", dbinstalldir);
959             return -1;
960         }
961     }
962 
963     for (int i = 1; i < argc; i++) {
964         // help, version and nowplaying are executed with any filter
965         if (!strcmp (argv[i], "--help") || !strcmp (argv[i], "-h")) {
966             print_help ();
967             return 0;
968         }
969         else if (!strcmp (argv[i], "--version")) {
970             fprintf (stderr, "DeaDBeeF " VERSION " Copyright © 2009-2016 Alexey Yakovenko\n");
971             return 0;
972         }
973         else if (!strcmp (argv[i], "--gui")) {
974             if (i == argc-1) {
975                 break;
976             }
977             i++;
978             strncpy (use_gui_plugin, argv[i], sizeof(use_gui_plugin) - 1);
979             use_gui_plugin[sizeof(use_gui_plugin) - 1] = 0;
980         }
981     }
982 
983     trace ("installdir: %s\n", dbinstalldir);
984     trace ("confdir: %s\n", confdir);
985     trace ("docdir: %s\n", dbdocdir);
986     trace ("plugindir: %s\n", dbplugindir);
987     trace ("pixmapdir: %s\n", dbpixmapdir);
988 
989     mkdir (dbconfdir, 0755);
990 
991     int size = 0;
992     char *cmdline = prepare_command_line (argc, argv, &size);
993 
994     // try to connect to remote player
995     int s, len;
996     struct sockaddr_un remote;
997 
998     if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
999         perror("socket");
1000         exit(1);
1001     }
1002 
1003     memset (&remote, 0, sizeof (remote));
1004     remote.sun_family = AF_UNIX;
1005 #if USE_ABSTRACT_SOCKET_NAME
1006     memcpy (remote.sun_path, server_id, sizeof (server_id));
1007     len = offsetof(struct sockaddr_un, sun_path) + sizeof (server_id)-1;
1008 #else
1009     char *socketdirenv = getenv ("DDB_SOCKET_DIR");
1010     snprintf (remote.sun_path, sizeof (remote.sun_path), "%s/socket", socketdirenv ? socketdirenv : dbconfdir);
1011     len = offsetof(struct sockaddr_un, sun_path) + strlen (remote.sun_path);
1012 #endif
1013     if (connect(s, (struct sockaddr *)&remote, len) == 0) {
1014         // pass args to remote and exit
1015         if (send(s, cmdline, size, 0) == -1) {
1016             perror ("send");
1017             exit (-1);
1018         }
1019         // end of message
1020         shutdown(s, SHUT_WR);
1021 
1022         int sz = -1;
1023         char *out = read_entire_message(s, &sz);
1024         if (sz == -1) {
1025             fprintf (stderr, "failed to pass args to remote!\n");
1026             exit (-1);
1027         }
1028         else {
1029             // check if that's nowplaying response
1030             const char np[] = "nowplaying ";
1031             const char err[] = "error ";
1032             if (!strncmp (out, np, sizeof (np)-1)) {
1033                 const char *prn = &out[sizeof (np)-1];
1034                 fwrite (prn, 1, strlen (prn), stdout);
1035             }
1036             else if (!strncmp (out, err, sizeof (err)-1)) {
1037                 const char *prn = &out[sizeof (err)-1];
1038                 fwrite (prn, 1, strlen (prn), stderr);
1039             }
1040             else if (sz > 0 && out[0]) {
1041                 fprintf (stderr, "%s\n", out);
1042             }
1043         }
1044         if (out) {
1045             free (out);
1046         }
1047         close (s);
1048         exit (0);
1049     }
1050 //    else {
1051 //        perror ("INFO: failed to connect to existing session:");
1052 //    }
1053     close(s);
1054 
1055     // become a server
1056     if (server_start () < 0) {
1057         exit (-1);
1058     }
1059 
1060     // hack: report nowplaying
1061     if (!strcmp (cmdline, "--nowplaying")) {
1062         char nothing[] = "nothing";
1063         fwrite (nothing, 1, sizeof (nothing)-1, stdout);
1064         return 0;
1065     }
1066 
1067     pl_init ();
1068     conf_init ();
1069     conf_load (); // required by some plugins at startup
1070 
1071     if (use_gui_plugin[0]) {
1072         conf_set_str ("gui_plugin", use_gui_plugin);
1073     }
1074 
1075     conf_set_str ("deadbeef_version", VERSION);
1076 
1077     volume_set_db (conf_get_float ("playback.volume", 0)); // volume need to be initialized before plugins start
1078 
1079     messagepump_init (); // required to push messages while handling commandline
1080     if (plug_load_all ()) { // required to add files to playlist from commandline
1081         exit (-1);
1082     }
1083     pl_load_all ();
1084     plt_set_curr_idx (conf_get_int ("playlist.current", 0));
1085 
1086     // execute server commands in local context
1087     int noloadpl = 0;
1088     if (argc > 1) {
1089         int res = server_exec_command_line (cmdline, size, NULL, 0);
1090         // some of the server commands ran on 1st instance should terminate it
1091         if (res == 2) {
1092             noloadpl = 1;
1093         }
1094         else if (res > 0) {
1095             exit (0);
1096         }
1097         else if (res < 0) {
1098             exit (-1);
1099         }
1100     }
1101 
1102     free (cmdline);
1103 
1104 #if 0
1105     signal (SIGTERM, sigterm_handler);
1106     atexit (atexit_handler); // helps to save in simple cases
1107 #endif
1108 
1109     streamer_init ();
1110 
1111     plug_connect_all ();
1112     messagepump_push (DB_EV_PLUGINSLOADED, 0, 0, 0);
1113 
1114     if (!noloadpl) {
1115         restore_resume_state ();
1116     }
1117 
1118     server_tid = thread_start (server_loop, NULL);
1119 
1120     mainloop_tid = thread_start (mainloop_thread, NULL);
1121 
1122     DB_plugin_t *gui = plug_get_gui ();
1123     if (gui) {
1124         gui->start ();
1125     }
1126 
1127     fprintf (stderr, "gui plugin has quit; waiting for mainloop thread to finish\n");
1128     thread_join (mainloop_tid);
1129 
1130     main_cleanup_and_quit ();
1131     return 0;
1132 }
1133 
1134