I am a pedant and I like if my audiofiles have correct homogenous tags and names. I dislike missing album or artist field and filenames like “01 – Track 01”. That’s why I adore Easytag (http://projects.gnome.org/easytag/) – a robust tag editor with tasty features like tag encoding conversion or retrieving tags from filenames.
I found only one problem in Easytag. It allows to open the selected file with an audio player. The thing is that if you use Audacious, then it will occasionally switch to its default skin.
For example, I have chosen Classic 1.3 skin. When I select Run Audio Player, my Audacious reverts to Default skin.
Several tests shown that this problem happens only with archived skins, so, it was definitely a sign that the problem was in Audacious itself.
Easytag allows to provide a command line to launch audio player, so let’s set it as audacious --verbose -p
and run Easytag from a terminal to see debug output produced by Audacious.
That’s what I was looking for:
pluginenum.c:142 [plugin_load]: Loading plugin: /usr/lib64/audacious/General/skins.so. plugin-registry.c:483 [plugin_register_loaded]: Loaded plugin: /usr/lib64/audacious/General/skins.so ui_skin.c:145 [active_skin_load]: /usr/share/audacious/Skins/Classic1.3.zip ui_skin.c:931 [skin_load_nolock]: Attempt to load skin "/usr/share/audacious/Skins/Classic1.3.zip" ui_skin.c:946 [skin_load_nolock]: Attempt to load archive util.c:377 [archive_decompress]: Attempt to execute "unzip >/dev/null -o -j "/usr/share/audacious/Skins/Classic1.3.zip" -d /tmp/audacious.XXw2EDC1" util.c:381 [archive_decompress]: could not execute cmd unzip >/dev/null -o -j "/usr/share/audacious/Skins/Classic1.3.zip" -d /tmp/audacious.XXw2EDC1 ui_skin.c:948 [skin_load_nolock]: Unable to extract skin archive (/usr/share/audacious/Skins/Classic1.3.zip) ui_skin.c:1049 [skin_load]: loading failed ui_skin.c:149 [active_skin_load]: loading failed ui_skin.c:423 [init_skins]: Unable to load skin (/usr/share/audacious/Skins/Classic1.3.zip), trying default... ui_skin.c:145 [active_skin_load]: /usr/share/audacious/Skins/Default ui_skin.c:931 [skin_load_nolock]: Attempt to load skin "/usr/share/audacious/Skins/Default"
As far as we know, Audacious has a highly modular architecture, that’s why it has a special skins plugin. This plugin runs unzip
subprocess to unpack the skin archive and switches to Default skin if it has encountered problems.
But… what problems? Let’s check up the output directory:
$ ls /tmp/audacious.XXw2EDC1 balance.png eq_ex.png main.png nums_ex.png pledit.png posbar.png skin-classic.hints text.png viscolor.txt cbuttons.png eqmain.png monoster.png playpaus.png pledit.txt shufrep.png skin.hints titlebar.png volume.png
So, unzip
had done his job, but Audacious complained! It’s time to look at skin plugin source in hope that it will throw light.
$ cd audacious-plugins-3.2.2/src/skins $ find . -type f -exec grep -B 4 -A 3 -nH -e "could not execute cmd" {} + ./util.c-377- AUDDBG("Attempt to execute \"%s\"\n", cmd); ./util.c-378- ./util.c-379- if (system(cmd) != 0) ./util.c-380- { ./util.c:381: AUDDBG("could not execute cmd %s\n", cmd); ./util.c-382- g_free(cmd); ./util.c-383- return NULL; ./util.c-384- }
Now we see that Audacious calls system
function from standard C library. According to the manual,
system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed… The value returned is -1 on error (e.g., fork(2) failed), and the return status of the command otherwise.
Still no ideas. Let’s look at system
implementation. We can find it in glibc-2.15/sysdeps/posix/system.c
. It’s quite straightforward. system
manipulates SIGINT
and SIGQUIT
handling, calls fork
and waitpid
to retrieve the command exit status.
fork
was successful since archive was really unzipped. What problems can happen in waitpid
?
ECHILD (for wait()) The calling process does not have any unwaited-for
children.ECHILD (for waitpid() or waitid()) The process specified by pid (wait‐
pid()) or idtype and id (waitid()) does not exist or is not a
child of the calling process. (This can happen for one’s own
child if the action for SIGCHLD is set to SIG_IGN. See also the
Linux Notes section about threads.)EINTR WNOHANG was not set and an unblocked signal or a SIGCHLD was
caught; see signal(7).EINVAL The options argument was invalid.
This can happen for one's own child if the action for SIGCHLD is set to SIG_IGN
looks suspicious. Grep it!
$ cd easytag-2.1.7 $ find . -type f -exec grep -B 1 -A 1 -nH -e "SIG_IGN" {} + ./src/easytag.c-171- // Must handle this signal to avoid zombie of applications executed (ex: xmms) ./src/easytag.c:172: signal(SIGCHLD,SIG_IGN); // Fix me! : can't run nautilus 1.0.6 with "Browse Directory With" ./src/easytag.c-173-#endif
We are on the right way! Easytag sets SIGCHLD
handler to SIG_IGN
, that’s why its child processes do not become zombies. But Audacious inherits this SIG_IGN
behavior (the manual says that child processes inherit such signal handlers from parents) and cannot perform a successful waitpid
. That’s why system
function returns failure even for a successful unzipping.
It’s very easy to fix the bug. The problem is in Easytag, not in Audacious. It’s enough to set a normal handler for SIGCHLD
. The handler will be called each time when a child process exits. This process resides in zombie state. The handler simply calls wait
(this friend of waitpid
waits for _any_ child process) thus removing zombie’s control block and freeing kernel resources that were still used by the zombie:
static void sigchld_handler(int signum) { wait(NULL); } static void setup_sigchld() { struct sigaction sa = {0}; sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa, NULL); }
Now replace signal(SIGCHLD,SIG_IGN)
with setup_sigchld()
, compile, and run Easytag – and any archived skin will be rendered without problems!
My patch was accepted to EasyTAG:
http://git.gnome.org/browse/easytag/commit/?id=e84f769cc57d3f33b163ec17107a84c967a98baa