Commit 2c8c14a4 authored by Lorenzo Faletra's avatar Lorenzo Faletra

add new launcher updater rewritten in golang

parent 4c0cb8f0
all:
/usr/bin/go build update-launchers.go
/usr/bin/strip update-launchers
DPkg::Post-Invoke { "[ ! -x /usr/share/parrot-menu/update-parrot-menu ] || /usr/share/parrot-menu/update-parrot-menu wait_dpkg"; }
DPkg::Post-Invoke { "[ ! -x /usr/share/parrot-menu/update-launchers ] || /usr/share/parrot-menu/update-launchers wait_dpkg"; }
parrot-menu (2:2019.05.19) testing; urgency=medium
* Rewrite the menu lanucher updater in golang.
-- Lorenzo "Palinuro" Faletra <palinuro@parrotsec.org> Sun, 19 May 2019 19:53:27 +0200
parrot-menu (2:2019.04.10) testing; urgency=medium
* Update airgeddon launcher.
......
......@@ -2,12 +2,12 @@ Source: parrot-menu
Section: parrot
Priority: optional
Maintainer: Lorenzo "Palinuro" Faletra <palinuro@parrotsec.org>
Build-Depends: debhelper
Build-Depends: debhelper, golang-go
Standards-Version: 3.8.4
Package: parrot-menu
Architecture: all
Depends: ${misc:Depends}, ${perl:Depends}, gksu, libdpkg-perl, libfile-fcntllock-perl, xdg-utils
Architecture: any
Depends: ${misc:Depends}, gksu, xdg-utils
Breaks: dradis (<< 3.1.0~rc2)
Replaces: mate-menus
Description: Parrot GNU/Linux custom menu
......
......@@ -5,6 +5,6 @@ bin/* usr/bin/
desktop-directories/*.directory usr/share/desktop-directories/
desktop-files/*.desktop usr/share/parrot-menu/applications/
apt.conf.d/99parrot-menu etc/apt/apt.conf.d/
update-parrot-menu usr/share/parrot-menu/
update-launchers usr/share/parrot-menu/
menu-icons/* usr/share/icons/
dconf/parrot-menu etc/dconf/db/local.d/
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
)
type launcher struct {
name string
path string
filename string
deb string
sandboxed bool
todelete bool
toupdate bool
}
func main() {
fmt.Println("Scanning application launchers")
// get list of files in /usr/share/parrot-menu/applications (source)
sfiles, err := ioutil.ReadDir("/usr/share/parrot-menu/applications/")
if err != nil {
log.Fatal(err)
}
// get list of files in /usr/share/applications (destination)
dfiles, err := ioutil.ReadDir("/usr/share/applications/")
if err != nil {
log.Fatal(err)
}
// get list of installed packages
tmp, _ := exec.Command("bash", "-c", "apt list --installed | cut -d'/' -f1").Output()
packages := strings.Split(string(tmp), "\n")
fmt.Println(len(packages))
// create source and destination structures
source := make([]launcher, len(sfiles))
destination := make([]launcher, len(dfiles))
// initialize source structure
sfinished := make(chan bool)
var spath string
for i, f := range sfiles {
spath = fmt.Sprintf("/usr/share/parrot-menu/applications/%s", f.Name())
go getLauncherContent(sfinished, &source[i], spath, f.Name())
}
// initialize destination structure
dfinished := make(chan bool)
var dpath string
for i, f := range dfiles {
dpath = fmt.Sprintf("/usr/share/applications/%s", f.Name())
go getLauncherContent(dfinished, &destination[i], dpath, f.Name())
}
// wait for initialization goroutines to finish
for i := 0; i < len(dfiles); i++ {
<-dfinished
}
for i := 0; i < len(sfiles); i++ {
<-sfinished
}
fmt.Println("Updating active launchers")
removeOldLaunchers(destination)
copyActiveLaunchers(source, packages)
exec.Command("sync")
fmt.Println("Done")
}
func getLauncherContent(finished chan bool, s *launcher, path string, filename string) {
// set path
s.path = path
// set filename
s.filename = filename
// set name
tmpname, _ := exec.Command("bash", "-c",
fmt.Sprintf("grep Name= %s | sed -e 's/Name=//g'", path)).Output()
s.name = strings.Trim(string(tmpname), "\n")
// set package name
tmpdeb, _ := exec.Command("bash", "-c",
fmt.Sprintf("grep X-Parrot-Package= %s | sed -e 's/X-Parrot-Package=//g'", path)).Output()
s.deb = strings.Trim(string(tmpdeb), "\n")
// send signal
finished <- true
}
func removeOldLaunchers(files []launcher) {
for _, f := range files {
if f.deb != "" {
os.Remove(f.path)
}
}
}
func copyActiveLaunchers(launchers []launcher, packages []string) {
for _, l := range launchers {
for _, p := range packages {
if l.deb == p {
in, err := os.Open(l.path)
if err != nil {
fmt.Printf("[WARNING] Can't find source launcher to copy: %s\n", l.path)
}
defer in.Close()
out, err := os.Create(fmt.Sprintf("/usr/share/applications/%s", l.filename))
if err != nil {
fmt.Printf("[WARNING] Can't create target launcher to write: %s%s\n", "/usr/share/applications/", l.filename)
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
fmt.Printf("[WARNING] Can't create new launcher for %s\n", l.name)
}
}
}
}
}
#!/usr/bin/perl
use warnings;
use strict;
use Dpkg::ErrorHandling;
use Dpkg::IPC;
use File::FcntlLock;
use File::Copy;
use File::Path qw(make_path);
use constant {
INCOMING_APPDIR => "/usr/share/parrot-menu/applications",
OUTGOING_APPDIR => "/usr/share/applications",
DPKG_LOCKFILE => "/var/lib/dpkg/lock",
PARROT_MENU_LOCKFILE => "/var/lock/parrot-menu",
};
our %pkg2desktop;
our %desktop2pkg;
our %installed;
make_path(OUTGOING_APPDIR, { mode => 0755 });
my $lock = get_lock(); # Can stop here if another instance is already running
parse_parrot_desktop_files();
my $arg = shift @ARGV || "";
wait_dpkg() if (defined $ENV{'DPKG_RUNNING_VERSION'} or $arg eq "wait_dpkg");
# Scan installed packages to identify desktop files to install
foreach my $pkg (list_installed_packages()) {
$installed{$pkg} = 1;
next unless exists $pkg2desktop{$pkg};
foreach my $desktop (@{$pkg2desktop{$pkg}}) {
my $target = OUTGOING_APPDIR . "/$desktop";
my $source = INCOMING_APPDIR . "/$desktop";
take_control_of($target);
copy($source, $target);
}
}
# Scan installed desktop files to remove the entries that are no longer
# relevant
FILE: foreach my $desktop (list_installed_desktop_files()) {
my $ref_file = INCOMING_APPDIR . "/$desktop";
my $out_file = OUTGOING_APPDIR . "/$desktop";
next unless is_managed($out_file);
# Drop unknown files
unless (-e $ref_file and exists $desktop2pkg{$desktop}) {
release_desktop_file($out_file);
next;
}
# Ensure the corresponding package is still installed
foreach my $pkg (@{$desktop2pkg{$desktop}}) {
next FILE if $installed{$pkg};
}
release_desktop_file($out_file);
}
close($lock);
### HELPER FUNCTIONS
sub release_desktop_file {
my ($file) = @_;
my $infos = get_dpkg_infos($file);
unlink($file) or syserr("can't remove %s", $file);
remove_diversion($file) if $infos->{'diverted'};
}
sub take_control_of {
my ($target) = @_;
return if not -e $target; # No conflicting file, go ahead
return if is_managed($target); # Already managed, nothing else to do
my $infos = get_dpkg_infos($target);
if ($infos->{'known'} and not $infos->{'diverted'}) {
divert($target);
}
}
sub get_dpkg_infos {
my ($target) = @_;
my $pipe;
my $pid = spawn(exec => [ 'dpkg-query', '-S', $target ],
to_pipe => \$pipe, error_to_file => "/dev/null",
env => { LC_ALL => 'C' });
my $res = {
diverted => 0,
known => 0,
};
while (<$pipe>) {
if (/^(diversion by|local diversion)/) {
$res->{'diverted'} = 1;
next;
}
$res->{'known'} = 1 if /: \Q$target\E/;
}
wait_child($pid, cmdline => "dpkg-query -S $target", nocheck => 1);
return $res;
}
sub divert {
my ($target) = @_;
spawn(exec => [ "dpkg-divert", "--local", "--rename", "--divert",
"$target.disabled-by-parrot-menu", "--add", $target ],
wait_child => 1, to_file => "/dev/null");
}
sub remove_diversion {
my ($target) = @_;
spawn(exec => [ "dpkg-divert", "--local", "--rename", "--divert",
"$target.disabled-by-parrot-menu", "--remove", $target ],
wait_child => 1, to_file => "/dev/null");
}
sub is_managed {
my ($target) = @_;
my $res = 0;
open(my $fh, "<", $target) or syserr("can't open %s", $target);
if (grep { /^X-Parrot-Package=/i } <$fh>) {
$res = 1;
}
close($fh);
return $res;
}
sub list_installed_packages {
my (@list, $pipe);
my $pid = spawn(exec => [ 'dpkg-query', '-f', '${Package} ${Status}\n', '-W' ],
to_pipe => \$pipe);
while (<$pipe>) {
my ($pkg, $want, $ok, $status) = split;
if ($status ne "not-installed" and $status ne "config-files") {
push @list, $pkg;
}
}
wait_child($pid, cmdline => "dpkg-query");
return @list;
}
sub list_installed_desktop_files {
opendir(my $dh, OUTGOING_APPDIR) or syserr("can't opendir %s", OUTGOING_APPDIR);
my @files = grep(/\.desktop$/, readdir($dh));
closedir($dh);
return @files;
}
sub parse_parrot_desktop_files {
opendir(my $dh, INCOMING_APPDIR) or syserr("can't opendir %s", INCOMING_APPDIR);
foreach my $file (readdir $dh) {
next unless ($file =~ /\.desktop$/);
my $path = INCOMING_APPDIR . "/$file";
open(my $fh, "<", $path) or syserr("can't open %s", $path);
my $found = 0;
while (<$fh>) {
if (/X-Parrot-Package=\s*(.*)$/i) {
$found = 1;
foreach my $pkg (split(/\s+|\s*,\s*/, $1)) {
$pkg2desktop{$pkg} = [] unless exists $pkg2desktop{$pkg};
$desktop2pkg{$file} = [] unless exists $desktop2pkg{$file};
push @{$pkg2desktop{$pkg}}, $file;
push @{$desktop2pkg{$file}}, $pkg;
}
}
}
close($fh);
unless ($found) {
warning("%s is missing the X-Parrot-Package header", $path);
}
}
closedir($dh);
}
sub get_lock {
my $lockfile = PARROT_MENU_LOCKFILE;
system("mkdir -p /run/lock /var/lock; touch $lockfile") unless -e $lockfile;
open my $fh, '>>', $lockfile or syserr("Can't open %s", $lockfile);
my $fs = new File::FcntlLock l_type => F_WRLCK;
if (not $fs->lock($fh, F_SETLK)) {
exit 0; # Failed to lock, another instance is already running
}
return $fh;
}
sub wait_dpkg {
return unless -e DPKG_LOCKFILE;
open my $fh, '<', DPKG_LOCKFILE or syserr("Can't open %s", DPKG_LOCKFILE);
my $fs = new File::FcntlLock l_type => F_WRLCK;
# Wait until dpkg releases the lock and doesn't grab it back in the
# next 5 seconds
my $count = 0;
while ($count < 2) {
if (can_lock($fh, $fs)) {
$count++;
} else {
$count = 0;
}
sleep(5);
}
close $fh;
}
sub can_lock {
my ($fh, $fs) = @_;
$fs->l_type(F_WRLCK);
if (not $fs->lock($fh, F_GETLK)) {
error("Failed to analyze lock on %s: %s", DPKG_LOCKFILE, $fs->error());
}
return $fs->l_type == F_UNLCK;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment