From 9273ba607ba4489bcd8ca20ae05b353fca482ca5 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Mon, 7 Dec 2020 11:56:19 -0600 Subject: [PATCH 01/23] Will now do things pure Go instead of running commands *If it's working* --- go.mod | 6 ++--- go.sum | 37 +++++++++++++++++++++++++---- src/appimaged/appimage.go | 48 ++++++++++++++++++++++++++++++++++++-- src/appimaged/thumbnail.go | 34 +++++++++++++++++++++------ 4 files changed, 109 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 89ecf8ca..f26206fb 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,10 @@ module github.com/probonopd/go-appimage go 1.13 require ( + github.com/CalebQ42/squashfs v0.3.0 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 - github.com/adrg/xdg v0.2.1 + github.com/adrg/xdg v0.2.3 github.com/alokmenghrajani/gpgeez v0.0.0-20161206084504-1a06f1c582f9 github.com/coreos/go-systemd/v22 v22.0.0 github.com/eclipse/paho.mqtt.golang v1.2.0 @@ -24,7 +25,6 @@ require ( github.com/sabhiram/pngr v0.0.0-20180419043407-2df49b015d4b // indirect github.com/shirou/gopsutil v2.20.2+incompatible github.com/shuheiktgw/go-travis v0.2.4 - github.com/smartystreets/goconvey v1.6.4 // indirect github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 github.com/urfave/cli/v2 v2.2.0 @@ -32,6 +32,6 @@ require ( golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 - gopkg.in/ini.v1 v1.55.0 + gopkg.in/ini.v1 v1.62.0 gopkg.in/src-d/go-git.v4 v4.13.1 ) diff --git a/go.sum b/go.sum index 2e620f00..6ecea548 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= +github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= +github.com/CalebQ42/squashfs v0.3.0 h1:GekQxIML8DBv5a6hzvlDEExcxe5VfpBj/LIYS7wsD9c= +github.com/CalebQ42/squashfs v0.3.0/go.mod h1:3uSbOlPm0FeFtKwcPhPcPEIM4HQQTA3W01GOf9RqMeg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:fMi9ZZ/it4orHj3xWrM6cLkVFcCbkXQALFUiNtHtCPs= github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249/go.mod h1:iU1PxQMQwoHZZWmMKrMkrNlY+3+p9vxIjpZOVyxWa0g= -github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic= -github.com/adrg/xdg v0.2.1/go.mod h1:ZuOshBmzV4Ta+s23hdfFZnBsdzmoR3US0d7ErpqSbTQ= +github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= +github.com/adrg/xdg v0.2.3 h1:GxXngdYxNDkoUvZXjNJGwqZxWXi43MKbOOlA/00qZi4= +github.com/adrg/xdg v0.2.3/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alokmenghrajani/gpgeez v0.0.0-20161206084504-1a06f1c582f9 h1:Zio/mdDEpJDG1yeH9y2Kcb9ATWXkE7WIBkO+IMqRbbM= @@ -20,6 +25,7 @@ github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -41,12 +47,16 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj2I9TP8Jc+a7lge3QWn9DKE7NCwfc= @@ -62,14 +72,19 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo= github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -106,6 +121,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= @@ -118,6 +135,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= @@ -161,11 +182,16 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapK golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= -gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= @@ -175,3 +201,6 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/appimaged/appimage.go b/src/appimaged/appimage.go index 3014bd69..d816234f 100644 --- a/src/appimaged/appimage.go +++ b/src/appimaged/appimage.go @@ -23,6 +23,7 @@ import ( "github.com/probonopd/go-appimage/internal/helpers" "go.lsp.dev/uri" ) +import "github.com/CalebQ42/squashfs" // Handles AppImage files. // Currently it is using using a static build of mksquashfs/unsquashfs @@ -41,6 +42,7 @@ type AppImage struct { rawcontents string updateinformation string niceName string + reader *squashfs.Reader } // NewAppImage creates an AppImage object from the location defined by path. @@ -81,6 +83,25 @@ func NewAppImage(path string) AppImage { if ai.imagetype > 1 { ai.offset = helpers.CalculateElfSize(ai.path) } + if ai.imagetype == 2 { + //Run this in an inline func so we can handle errors more elegently. If there's a problem with the library, the unsquashfs tool will probably still work. + ai.reader = func() *squashfs.Reader { + aiFil, err := os.Open(path) + if err != nil { + return nil + } + stat, err := aiFil.Stat() + if err != nil { + return nil + } + secReader := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) + reader, err := squashfs.NewSquashfsReader(secReader) + if err != nil { + return nil + } + return reader + }() + } ui, err := ai.ReadUpdateInformation() if err == nil && ui != "" { ai.updateinformation = ui @@ -105,6 +126,18 @@ func (ai AppImage) discoverContents() { if ai.imagetype == 1 { cmd = exec.Command("bsdtar", "-t", ai.path) } else if ai.imagetype == 2 { + if ai.reader != nil { + files, err := ai.reader.GetAllFiles() + //this will allow it to fallback to using unsquashfs if there is problems + if err == nil { + out := make([]string, 0) + for _, file := range files { + out = append(out, file.Path()) + } + ai.rawcontents = strings.Join(out, "\n") + return + } + } cmd = exec.Command("unsquashfs", "-f", "-n", "-ll", "-o", strconv.FormatInt(ai.offset, 10), "-d ''", ai.path) } if *verbosePtr == true { @@ -224,13 +257,13 @@ func (ai AppImage) Validate() error { func (ai AppImage) _integrate() { // log.Println("integrate called on:", ai.path) - + // Return immediately if the filename extension is not .AppImage or .app if (strings.HasSuffix(ai.path, ".AppImage") != true) && (strings.HasSuffix(ai.path, ".app") != true) { // log.Println("No .AppImage suffix:", ai.path) return } - + // Return immediately if this is not an AppImage if ai.imagetype < 0 { // log.Println("Not an AppImage:", ai.path) @@ -353,6 +386,17 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string) error _, err = runCommand(cmd) return err } else if ai.imagetype == 2 { + if ai.reader != nil { + file := ai.reader.GetFileAtPath(filepath) + if file != nil { //so we can fall back to command based extraction. + errs := file.ExtractTo(destinationdirpath) + if len(errs) != 0 { + //just return the first error + return errs[0] + } + return nil + } + } cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.offset, 10), "-d", destinationdirpath, ai.path, filepath) _, err = runCommand(cmd) return err diff --git a/src/appimaged/thumbnail.go b/src/appimaged/thumbnail.go index 75f52680..fd37d413 100644 --- a/src/appimaged/thumbnail.go +++ b/src/appimaged/thumbnail.go @@ -2,13 +2,6 @@ package main import ( "bufio" - "github.com/adrg/xdg" - issvg "github.com/h2non/go-is-svg" - "github.com/probonopd/go-appimage/internal/helpers" - "github.com/sabhiram/png-embed" // For embedding metadata into PNG - . "github.com/srwiley/oksvg" // https://github.com/niemeyer/gopkg/issues/72 - . "github.com/srwiley/rasterx" - "gopkg.in/ini.v1" "image" "image/png" "io/ioutil" @@ -18,6 +11,14 @@ import ( "path/filepath" "strconv" "time" + + "github.com/adrg/xdg" + issvg "github.com/h2non/go-is-svg" + "github.com/probonopd/go-appimage/internal/helpers" + pngembed "github.com/sabhiram/png-embed" // For embedding metadata into PNG + . "github.com/srwiley/oksvg" // https://github.com/niemeyer/gopkg/issues/72 + . "github.com/srwiley/rasterx" + "gopkg.in/ini.v1" ) /* The thumbnail cache directory is prefixed with $XDG_CACHE_DIR/ and the leading dot removed @@ -47,6 +48,25 @@ func (ai AppImage) extractDirIconAsThumbnail() { // cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.offset, 10), "-d", thumbnailcachedir, ai.path, ".DirIcon") // runCommand(cmd) // } + + //this will try to extract the thumbnail, or goes back to command based extraction if it fails. + if ai.reader != nil { + thumbnail := ai.reader.GetFileAtPath(".DirIcon") + if thumbnail != nil { + if thumbnail.IsSymlink() { + thumbnail = thumbnail.GetSymlinkFile() + } + if thumbnail != nil { + errs := thumbnail.ExtractTo(thumbnailcachedir) + if len(errs) == 0 { + err := os.Rename(thumbnailcachedir+"/"+thumbnail.Name, thumbnailcachedir+"/.DirIcon") + if err == nil { + return + } + } + } + } + } err := ai.ExtractFile(".DirIcon", thumbnailcachedir) if err != nil { // Too verbose From cc859794ed47bd098b1826302e44a29a3eac0a97 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Wed, 9 Dec 2020 08:38:36 -0600 Subject: [PATCH 02/23] Automatically resolve symlink's for thumbnail Try to prefil some desktop entry information from the appimage's desktop file --- go.mod | 2 +- go.sum | 8 +++-- src/appimaged/desktop.go | 66 ++++++++++++++++++++++++++++---------- src/appimaged/thumbnail.go | 17 ++++------ 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index f26206fb..b09e950b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/probonopd/go-appimage go 1.13 require ( - github.com/CalebQ42/squashfs v0.3.0 + github.com/CalebQ42/squashfs v0.3.2 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 github.com/adrg/xdg v0.2.3 diff --git a/go.sum b/go.sum index 6ecea548..168af82f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= -github.com/CalebQ42/squashfs v0.3.0 h1:GekQxIML8DBv5a6hzvlDEExcxe5VfpBj/LIYS7wsD9c= -github.com/CalebQ42/squashfs v0.3.0/go.mod h1:3uSbOlPm0FeFtKwcPhPcPEIM4HQQTA3W01GOf9RqMeg= +github.com/CalebQ42/squashfs v0.3.2 h1:q4YZUWvKP3SCd/XsmjHW6vLOIS+wFQVGxUKEblTC3qg= +github.com/CalebQ42/squashfs v0.3.2/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:fMi9ZZ/it4orHj3xWrM6cLkVFcCbkXQALFUiNtHtCPs= @@ -70,6 +70,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -94,6 +96,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA= +github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/src/appimaged/desktop.go b/src/appimaged/desktop.go index 4507e655..9c26afb8 100644 --- a/src/appimaged/desktop.go +++ b/src/appimaged/desktop.go @@ -10,10 +10,12 @@ import ( "log" "os" "path/filepath" + "strings" "time" "golang.org/x/sys/unix" + "github.com/CalebQ42/squashfs" "github.com/adrg/xdg" "github.com/probonopd/go-appimage/internal/helpers" "gopkg.in/ini.v1" @@ -49,37 +51,67 @@ func writeDesktopFile(ai AppImage) { // } // BLOCKED: To do this in a halfway decent way, we need to improve // ai.ExtractFile() so that it resolves symlinks! - - cfg := ini.Empty() - + var cfg *ini.File ini.PrettyFormat = false + startingPoint := false //An easy way to tell if extracting the desktop file worked. + arg0abs, err := filepath.Abs(os.Args[0]) // FIXME: KDE seems to have a problem when the AppImage is on a partition of which the disklabel contains "_"? // Then the desktop file won't run the application - - arg0abs, err := filepath.Abs(os.Args[0]) if err != nil { log.Println(err) } + if ai.reader != nil { + desktopFil := ai.reader.GetFileAtPath("*.desktop") + if desktopFil == nil { + //If there isn't a top level .desktop file, try to find one SOMEWHERE. + //TODO: Try to look at some predetermined location first before seaching everywhere. + desktopFil = ai.reader.FindFile(func(fil *squashfs.File) bool { + return strings.HasSuffix(fil.Name, ".desktop") + }) + } + if desktopFil != nil { + errs := desktopFil.ExtractSymlink(desktopcachedir) + if len(errs) == 0 { + err = os.Rename(desktopcachedir+desktopFil.Name, desktopcachedir+filename) + if err == nil { + startingPoint = true + } + } + } + } + if !startingPoint { + cfg = ini.Empty() + cfg.Section("Desktop Entry").Key("Type").SetValue("Application") + cfg.Section("Desktop Entry").Key("Name").SetValue(ai.niceName) + thumbnail := ThumbnailsDirNormal + ai.md5 + ".png" + cfg.Section("Desktop Entry").Key("Icon").SetValue(thumbnail) + // Construct the Name entry based on the actual filename + // so that renaming the file in the file manager results in a changed name in the menu + // FIXME: If the thumbnail is not generated here but by another external thumbnailer, it may not be fast enough + time.Sleep(1 * time.Second) + } else { + cfg, err = ini.Load(desktopcachedir + filename) + if err != nil { + cfg = ini.Empty() + cfg.Section("Desktop Entry").Key("Type").SetValue("Application") + cfg.Section("Desktop Entry").Key("Name").SetValue(ai.niceName) + thumbnail := ThumbnailsDirNormal + ai.md5 + ".png" + cfg.Section("Desktop Entry").Key("Icon").SetValue(thumbnail) + // Construct the Name entry based on the actual filename + // so that renaming the file in the file manager results in a changed name in the menu + // FIXME: If the thumbnail is not generated here but by another external thumbnailer, it may not be fast enough + time.Sleep(1 * time.Second) + } + } - cfg.Section("Desktop Entry").Key("Exec").SetValue(arg0abs + " wrap \"" + ai.path + "\"") // Resolve to a full path + cfg.Section("Desktop Entry").Key("Exec").SetValue(arg0abs + " wrap \"" + ai.path + "\"") // Resolve to a full path cfg.Section("Desktop Entry").Key(ExecLocationKey).SetValue(ai.path) cfg.Section("Desktop Entry").Key("TryExec").SetValue(arg0abs) // Resolve to a full path - cfg.Section("Desktop Entry").Key("Type").SetValue("Application") - // Construct the Name entry based on the actual filename - // so that renaming the file in the file manager results in a changed name in the menu - - cfg.Section("Desktop Entry").Key("Name").SetValue(ai.niceName) - - thumbnail := ThumbnailsDirNormal + ai.md5 + ".png" - // FIXME: If the thumbnail is not generated here but by another external thumbnailer, it may not be fast enough - time.Sleep(1 * time.Second) // For icons, use absolute paths. This way icons start working // without having to restart the desktop, and possibly // we can even get around messing around with the XDG icon spec // that expects different sizes of icons in different directories - - cfg.Section("Desktop Entry").Key("Icon").SetValue(thumbnail) /* if _, err := os.Stat(thumbnail); err == nil { // Thumbnail exists, then we use it as the Icon in the desktop file diff --git a/src/appimaged/thumbnail.go b/src/appimaged/thumbnail.go index fd37d413..6c6c7571 100644 --- a/src/appimaged/thumbnail.go +++ b/src/appimaged/thumbnail.go @@ -53,18 +53,15 @@ func (ai AppImage) extractDirIconAsThumbnail() { if ai.reader != nil { thumbnail := ai.reader.GetFileAtPath(".DirIcon") if thumbnail != nil { - if thumbnail.IsSymlink() { - thumbnail = thumbnail.GetSymlinkFile() - } - if thumbnail != nil { - errs := thumbnail.ExtractTo(thumbnailcachedir) - if len(errs) == 0 { - err := os.Rename(thumbnailcachedir+"/"+thumbnail.Name, thumbnailcachedir+"/.DirIcon") - if err == nil { - return - } + errs := thumbnail.ExtractSymlink(thumbnailcachedir) + if len(errs) == 0 { + err := os.Rename(thumbnailcachedir+"/"+thumbnail.Name, thumbnailcachedir+"/.DirIcon") + if err == nil { + return } } + thumbnail.Close() + return } } err := ai.ExtractFile(".DirIcon", thumbnailcachedir) From a9ba98ddb4ba40b423c96753d6b6fe3a2c32b9ad Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Thu, 10 Dec 2020 11:45:53 -0600 Subject: [PATCH 03/23] Initial commit for seperated library. --- src/goappimage/README.md | 5 + src/goappimage/appimage.go | 345 +++++++++++++++++++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 src/goappimage/README.md create mode 100644 src/goappimage/appimage.go diff --git a/src/goappimage/README.md b/src/goappimage/README.md new file mode 100644 index 00000000..bec45206 --- /dev/null +++ b/src/goappimage/README.md @@ -0,0 +1,5 @@ +# GoAppImage + +AppImage manipulation from Go. + +Currently tries to read the squashfs using pure go (using [this library](https://github.com/CalebQ42/squashfs)). I that doesn't work, falls back to calling `unsquashfs`. \ No newline at end of file diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go new file mode 100644 index 00000000..3d27a912 --- /dev/null +++ b/src/goappimage/appimage.go @@ -0,0 +1,345 @@ +package goappimage + +import ( + "bytes" + "crypto/md5" + "encoding/hex" + "io/ioutil" + "net/url" + + "gopkg.in/ini.v1" + + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + "io" + + "github.com/CalebQ42/squashfs" + "github.com/adrg/xdg" + "github.com/probonopd/go-appimage/internal/helpers" + "go.lsp.dev/uri" +) + +// AppImage handles AppImage files. +// Currently it is using using a static build of mksquashfs/unsquashfs +// but eventually may be rewritten to do things natively in Go +type AppImage struct { + Path string + ImageType int + URI string + Md5 string + Offset int64 + UpdateInformation string + NiceName string + reader *squashfs.Reader +} + +const execLocationKey = helpers.ExecLocationKey + +// NewAppImage creates an AppImage object from the location defined by path. +// The AppImage object will also be created if path does not exist, +// because the AppImage that used to be there may need to be removed +// and for this the functions of an AppImage are needed. +// Non-existing and invalid AppImages will have type -1. +func NewAppImage(path string) AppImage { + + ai := AppImage{Path: path, ImageType: -1} + + // If we got a temp file, exit immediately + // E.g., ignore typical Internet browser temporary files used during download + if strings.HasSuffix(path, ".temp") || + strings.HasSuffix(path, "~") || + strings.HasSuffix(path, ".part") || + strings.HasSuffix(path, ".partial") || + strings.HasSuffix(path, ".zs-old") || + strings.HasSuffix(path, ".crdownload") { + return ai + } + ai.URI = strings.TrimSpace(string(uri.File(filepath.Clean(ai.Path)))) + ai.Md5 = ai.calculateMD5filenamepart() // Need this also for non-existing AppImages for removal + ai.ImageType = ai.determineImageType() + // Don't waste more time if the file is not actually an AppImage + if ai.ImageType < 0 { + return ai + } + ai.NiceName = ai.calculateNiceName() + if ai.ImageType < 1 { + return ai + } + if ai.ImageType > 1 { + ai.Offset = helpers.CalculateElfSize(ai.Path) + } + if ai.ImageType == 2 { + //Run this in an inline func so we can handle errors more elegently. If there's a problem with the library, the unsquashfs tool will probably still work. + ai.reader = func() *squashfs.Reader { + aiFil, err := os.Open(path) + if err != nil { + return nil + } + stat, err := aiFil.Stat() + if err != nil { + return nil + } + secReader := io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset) + reader, err := squashfs.NewSquashfsReader(secReader) + if err != nil { + return nil + } + return reader + }() + } + ui, err := ai.ReadUpdateInformation() + if err == nil && ui != "" { + ai.UpdateInformation = ui + } + // ai.discoverContents() // Only do when really needed since this is slow + // log.Println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX rawcontents:", ai.rawcontents) + // Besides, for whatever reason it is not working properly yet + + return ai +} + +func (ai AppImage) calculateMD5filenamepart() string { + hasher := md5.New() + hasher.Write([]byte(ai.URI)) + return hex.EncodeToString(hasher.Sum(nil)) +} + +func (ai AppImage) calculateNiceName() string { + niceName := filepath.Base(ai.Path) + niceName = strings.Replace(niceName, ".AppImage", "", -1) + niceName = strings.Replace(niceName, ".appimage", "", -1) + niceName = strings.Replace(niceName, "-x86_64", "", -1) + niceName = strings.Replace(niceName, "-i386", "", -1) + niceName = strings.Replace(niceName, "-i686", "", -1) + niceName = strings.Replace(niceName, "-", " ", -1) + niceName = strings.Replace(niceName, "_", " ", -1) + return niceName +} + +func runCommand(cmd *exec.Cmd, verbose bool) (io.Writer, error) { + if verbose == true { + log.Printf("runCommand: %q\n", cmd) + } + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + // printError("runCommand", err) + // log.Println(cmd.Stdout) + return cmd.Stdout, err +} + +// Check whether we have an AppImage at all. +// Return image type, or -1 if it is not an AppImage +func (ai AppImage) determineImageType() int { + // log.Println("appimage: ", ai.path) + f, err := os.Open(ai.Path) + // printError("appimage", err) + if err != nil { + return -1 // If we were not able to open the file, then we report that it is not an AppImage + } + + info, err := os.Stat(ai.Path) + if err != nil { + return -1 + } + + // Directories cannot be AppImages, so return fast + if info.IsDir() { + return -1 + } + + // Very small files cannot be AppImages, so return fast + if info.Size() < 100*1024 { + return -1 + } + + if helpers.CheckMagicAtOffset(f, "414902", 8) == true { + return 2 + } + + if helpers.CheckMagicAtOffset(f, "414901", 8) == true { + return 1 + } + + // ISO9660 files that are also ELF files + if helpers.CheckMagicAtOffset(f, "7f454c", 0) == true && helpers.CheckMagicAtOffset(f, "4344303031", 32769) == true { + return 1 + } + + return -1 +} + +// func (ai AppImage) setExecBit(verbose bool) { + +// err := os.Chmod(ai.Path, 0755) +// if err == nil { +// if verbose == true { +// log.Println("appimage: Set executable bit on", ai.Path) +// } +// } +// // printError("appimage", err) // Do not print error since AppImages on read-only media are common +// } + +// Validate checks the quality of an AppImage and sends desktop notification, returns error or nil +// TODO: Add more checks and reuse this in appimagetool +// func (ai AppImage) Validate(verbose bool) error { +// if verbose == true { +// log.Println("Validating AppImage", ai.Path) +// } +// // Check validity of the updateinformation in this AppImage, if it contains some +// if ai.UpdateInformation != "" { +// log.Println("Validating updateinformation in", ai.Path) +// err := helpers.ValidateUpdateInformation(ai.UpdateInformation) +// if err != nil { +// helpers.PrintError("appimage: updateinformation verification", err) +// return err +// } +// } +// return nil +// } + +// func ioReader(file string) io.ReaderAt { +// r, err := os.Open(file) +// defer r.Close() +// helpers.LogError("appimage: elf:", err) +// return r +// } + +// ExtractFile extracts a file from from filepath (which may contain * wildcards) +// in an AppImage to the destinationdirpath. +// Returns err in case of errors, or nil. +// TODO: resolve symlinks +// TODO: Should this be a io.Reader()? +func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, verbose bool) error { + var err error + if ai.ImageType == 1 { + err = os.MkdirAll(destinationdirpath, os.ModePerm) + cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.Path, filepath) + _, err = runCommand(cmd, verbose) + return err + } else if ai.ImageType == 2 { + if ai.reader != nil { + file := ai.reader.GetFileAtPath(filepath) + if file != nil { //so we can fall back to command based extraction. + errs := file.ExtractTo(destinationdirpath) + if len(errs) != 0 { + //just return the first error + return errs[0] + } + return nil + } + } + cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.Offset, 10), "-d", destinationdirpath, ai.Path, filepath) + _, err = runCommand(cmd, verbose) + return err + } + // FIXME: What we may have extracted may well be (until here) broken symlinks... we need to do better than that + return nil +} + +// ReadUpdateInformation reads updateinformation from an AppImage +// Returns updateinformation string and error +func (ai AppImage) ReadUpdateInformation() (string, error) { + aibytes, err := helpers.GetSectionData(ai.Path, ".upd_info") + ui := strings.TrimSpace(string(bytes.Trim(aibytes, "\x00"))) + if err != nil { + return "", err + } + // Don't validate here, we don't want to get warnings all the time. + // We have AppImage.Validate as its own function which we call less frequently than this. + return ui, nil +} + +// LaunchMostRecentAppImage launches an the most recent application for a given +// updateinformation that we found among the integrated AppImages. +// Kinda like poor man's Launch Services. Probably we should make as much use of it as possible. +// Downside: Applications without updateinformation cannot be used in this way. +func LaunchMostRecentAppImage(updateinformation string, args []string, quiet bool) { + if updateinformation == "" { + return + } + if quiet == false { + aipath := FindMostRecentAppImageWithMatchingUpdateInformation(updateinformation) + log.Println("Launching", aipath, args) + cmd := []string{aipath} + cmd = append(cmd, args...) + err := helpers.RunCmdTransparently(cmd) + if err != nil { + helpers.PrintError("LaunchMostRecentAppImage", err) + } + + } +} + +// FindMostRecentAppImageWithMatchingUpdateInformation finds the most recent registered AppImage +// that havs matching upate information embedded +func FindMostRecentAppImageWithMatchingUpdateInformation(updateinformation string) string { + results := FindAppImagesWithMatchingUpdateInformation(updateinformation) + mostRecent := helpers.FindMostRecentFile(results) + return mostRecent +} + +// FindAppImagesWithMatchingUpdateInformation finds registered AppImages +// that have matching upate information embedded +func FindAppImagesWithMatchingUpdateInformation(updateinformation string) []string { + files, err := ioutil.ReadDir(xdg.DataHome + "/applications/") + helpers.LogError("desktop", err) + var results []string + if err != nil { + return results + } + for _, file := range files { + if strings.HasSuffix(file.Name(), ".desktop") && strings.HasPrefix(file.Name(), "appimagekit_") { + + cfg, e := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, // Do not cripple lines hat contain ";" + xdg.DataHome+"/applications/"+file.Name()) + helpers.LogError("desktop", e) + dst := cfg.Section("Desktop Entry").Key(execLocationKey).String() + _, err = os.Stat(dst) + if os.IsNotExist(err) { + log.Println(dst, "does not exist, it is mentioned in", xdg.DataHome+"/applications/"+file.Name()) + continue + } + ai := NewAppImage(dst) + ui, err := ai.ReadUpdateInformation() + if err == nil && ui != "" { + //log.Println("updateinformation:", ui) + // log.Println("updateinformation:", url.QueryEscape(ui)) + unescapedui, _ := url.QueryUnescape(ui) + // log.Println("updateinformation:", unescapedui) + if updateinformation == unescapedui { + results = append(results, ai.Path) + } + } + + continue + } + } + return results +} + +// getFSTime reads FSTime from the AppImage. We are doing this only when it is needed, +// not when an NewAppImage is called +func (ai AppImage) getFSTime() time.Time { + if ai.ImageType == 2 { + result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.Offset, 10), ai.Path).Output() + resstr := strings.TrimSpace(string(bytes.TrimSpace(result))) + if err != nil { + helpers.PrintError("appimage: getFSTime: "+ai.Path, err) + return time.Unix(0, 0) + } + if n, err := strconv.Atoi(resstr); err == nil { + return time.Unix(int64(n), 0) + } + log.Println("appimage: getFSTime:", resstr, "is not an integer.") + return time.Unix(0, 0) + } + log.Println("TODO: Implement getFSTime for type-1 AppImages") + return time.Unix(0, 0) +} From c9934dcb55b6b5cb5074442bbd2a942d972190a7 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Fri, 11 Dec 2020 06:30:04 -0600 Subject: [PATCH 04/23] Simplifed GoAppImage Removed code that's only valuable to appimaged Used goto statements to help with command fallback. --- go.mod | 2 +- go.sum | 4 +- src/appimaged/desktop.go | 4 +- src/appimaged/thumbnail.go | 23 ++-- src/goappimage/appimage.go | 255 ++++++++++++------------------------- 5 files changed, 101 insertions(+), 187 deletions(-) diff --git a/go.mod b/go.mod index b09e950b..77de5c98 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/probonopd/go-appimage go 1.13 require ( - github.com/CalebQ42/squashfs v0.3.2 + github.com/CalebQ42/squashfs v0.3.4 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 github.com/adrg/xdg v0.2.3 diff --git a/go.sum b/go.sum index 168af82f..69d24af3 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= -github.com/CalebQ42/squashfs v0.3.2 h1:q4YZUWvKP3SCd/XsmjHW6vLOIS+wFQVGxUKEblTC3qg= -github.com/CalebQ42/squashfs v0.3.2/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= +github.com/CalebQ42/squashfs v0.3.4 h1:eVwy1UrBouv3iOUFHGIboyqM1Z8n6ZRMEOV/SbQWyx0= +github.com/CalebQ42/squashfs v0.3.4/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:fMi9ZZ/it4orHj3xWrM6cLkVFcCbkXQALFUiNtHtCPs= diff --git a/src/appimaged/desktop.go b/src/appimaged/desktop.go index 9c26afb8..9c921c7f 100644 --- a/src/appimaged/desktop.go +++ b/src/appimaged/desktop.go @@ -67,13 +67,13 @@ func writeDesktopFile(ai AppImage) { //If there isn't a top level .desktop file, try to find one SOMEWHERE. //TODO: Try to look at some predetermined location first before seaching everywhere. desktopFil = ai.reader.FindFile(func(fil *squashfs.File) bool { - return strings.HasSuffix(fil.Name, ".desktop") + return strings.HasSuffix(fil.Name(), ".desktop") }) } if desktopFil != nil { errs := desktopFil.ExtractSymlink(desktopcachedir) if len(errs) == 0 { - err = os.Rename(desktopcachedir+desktopFil.Name, desktopcachedir+filename) + err = os.Rename(desktopcachedir+desktopFil.Name(), desktopcachedir+filename) if err == nil { startingPoint = true } diff --git a/src/appimaged/thumbnail.go b/src/appimaged/thumbnail.go index 6c6c7571..2fc52925 100644 --- a/src/appimaged/thumbnail.go +++ b/src/appimaged/thumbnail.go @@ -52,18 +52,21 @@ func (ai AppImage) extractDirIconAsThumbnail() { //this will try to extract the thumbnail, or goes back to command based extraction if it fails. if ai.reader != nil { thumbnail := ai.reader.GetFileAtPath(".DirIcon") - if thumbnail != nil { - errs := thumbnail.ExtractSymlink(thumbnailcachedir) - if len(errs) == 0 { - err := os.Rename(thumbnailcachedir+"/"+thumbnail.Name, thumbnailcachedir+"/.DirIcon") - if err == nil { - return - } - } - thumbnail.Close() - return + if thumbnail == nil { + goto fallback + } + errs := thumbnail.ExtractSymlink(thumbnailcachedir) + if len(errs) > 0 { + goto fallback } + err := os.Rename(thumbnailcachedir+"/"+thumbnail.Name(), thumbnailcachedir+"/.DirIcon") + if err != nil { + goto fallback + } + thumbnail.Close() + return } +fallback: err := ai.ExtractFile(".DirIcon", thumbnailcachedir) if err != nil { // Too verbose diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index 3d27a912..9f7e445c 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -2,12 +2,6 @@ package goappimage import ( "bytes" - "crypto/md5" - "encoding/hex" - "io/ioutil" - "net/url" - - "gopkg.in/ini.v1" "log" "os" @@ -20,22 +14,30 @@ import ( "io" "github.com/CalebQ42/squashfs" - "github.com/adrg/xdg" "github.com/probonopd/go-appimage/internal/helpers" - "go.lsp.dev/uri" ) +/* + +TODO List: +* Provide a way to get the desktop file, or at least an ini.File representation of it. +* Provide a way to get thumbnail. +* Check if there IS an update +* Download said update + +*/ + // AppImage handles AppImage files. // Currently it is using using a static build of mksquashfs/unsquashfs // but eventually may be rewritten to do things natively in Go +// +// None of this is currently NEEDED to be exposed to the library user to edit. type AppImage struct { - Path string - ImageType int - URI string - Md5 string - Offset int64 - UpdateInformation string - NiceName string + path string + offset int64 + updateInformation string + niceName string + imageType int reader *squashfs.Reader } @@ -47,9 +49,7 @@ const execLocationKey = helpers.ExecLocationKey // and for this the functions of an AppImage are needed. // Non-existing and invalid AppImages will have type -1. func NewAppImage(path string) AppImage { - - ai := AppImage{Path: path, ImageType: -1} - + ai := AppImage{path: path, imageType: -1} // If we got a temp file, exit immediately // E.g., ignore typical Internet browser temporary files used during download if strings.HasSuffix(path, ".temp") || @@ -60,58 +60,48 @@ func NewAppImage(path string) AppImage { strings.HasSuffix(path, ".crdownload") { return ai } - ai.URI = strings.TrimSpace(string(uri.File(filepath.Clean(ai.Path)))) - ai.Md5 = ai.calculateMD5filenamepart() // Need this also for non-existing AppImages for removal - ai.ImageType = ai.determineImageType() + ai.imageType = ai.determineImageType() // Don't waste more time if the file is not actually an AppImage - if ai.ImageType < 0 { + if ai.imageType < 0 { return ai } - ai.NiceName = ai.calculateNiceName() - if ai.ImageType < 1 { + ai.niceName = ai.calculateNiceName() + if ai.imageType < 1 { return ai } - if ai.ImageType > 1 { - ai.Offset = helpers.CalculateElfSize(ai.Path) + if ai.imageType > 1 { + ai.offset = helpers.CalculateElfSize(ai.path) } - if ai.ImageType == 2 { - //Run this in an inline func so we can handle errors more elegently. If there's a problem with the library, the unsquashfs tool will probably still work. - ai.reader = func() *squashfs.Reader { - aiFil, err := os.Open(path) - if err != nil { - return nil - } - stat, err := aiFil.Stat() - if err != nil { - return nil - } - secReader := io.NewSectionReader(aiFil, ai.Offset, stat.Size()-ai.Offset) - reader, err := squashfs.NewSquashfsReader(secReader) - if err != nil { - return nil - } - return reader - }() - } - ui, err := ai.ReadUpdateInformation() - if err == nil && ui != "" { - ai.UpdateInformation = ui + if ai.imageType == 2 { + //Try to populate the ai.Reader to make it easier to use and get information. + //The library is still very new, so we can fallback to command based functions if necessary. + aiFil, err := os.Open(path) + if err != nil { + return ai + } + stat, err := aiFil.Stat() + if err != nil { + return ai + } + secReader := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) + reader, err := squashfs.NewSquashfsReader(secReader) + if err != nil { + return ai + } + ai.reader = reader + //TODO: possibly get some info (such as name) from the appimage's files, specifically it's desktop file. } - // ai.discoverContents() // Only do when really needed since this is slow - // log.Println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX rawcontents:", ai.rawcontents) - // Besides, for whatever reason it is not working properly yet - return ai } -func (ai AppImage) calculateMD5filenamepart() string { - hasher := md5.New() - hasher.Write([]byte(ai.URI)) - return hex.EncodeToString(hasher.Sum(nil)) +//Name is the "nice" name of the AppImage. +func (ai AppImage) Name() string { + return ai.niceName } func (ai AppImage) calculateNiceName() string { - niceName := filepath.Base(ai.Path) + //TODO: have this as a fallback to reading the appimage's .desktop file + niceName := filepath.Base(ai.path) niceName = strings.Replace(niceName, ".AppImage", "", -1) niceName = strings.Replace(niceName, ".appimage", "", -1) niceName = strings.Replace(niceName, "-x86_64", "", -1) @@ -138,63 +128,45 @@ func runCommand(cmd *exec.Cmd, verbose bool) (io.Writer, error) { // Return image type, or -1 if it is not an AppImage func (ai AppImage) determineImageType() int { // log.Println("appimage: ", ai.path) - f, err := os.Open(ai.Path) + f, err := os.Open(ai.path) // printError("appimage", err) if err != nil { return -1 // If we were not able to open the file, then we report that it is not an AppImage } - - info, err := os.Stat(ai.Path) + info, err := os.Stat(ai.path) if err != nil { return -1 } - // Directories cannot be AppImages, so return fast if info.IsDir() { return -1 } - // Very small files cannot be AppImages, so return fast if info.Size() < 100*1024 { return -1 } - if helpers.CheckMagicAtOffset(f, "414902", 8) == true { return 2 } - if helpers.CheckMagicAtOffset(f, "414901", 8) == true { return 1 } - // ISO9660 files that are also ELF files if helpers.CheckMagicAtOffset(f, "7f454c", 0) == true && helpers.CheckMagicAtOffset(f, "4344303031", 32769) == true { return 1 } - return -1 } -// func (ai AppImage) setExecBit(verbose bool) { - -// err := os.Chmod(ai.Path, 0755) -// if err == nil { -// if verbose == true { -// log.Println("appimage: Set executable bit on", ai.Path) -// } -// } -// // printError("appimage", err) // Do not print error since AppImages on read-only media are common -// } - // Validate checks the quality of an AppImage and sends desktop notification, returns error or nil // TODO: Add more checks and reuse this in appimagetool // func (ai AppImage) Validate(verbose bool) error { // if verbose == true { -// log.Println("Validating AppImage", ai.Path) +// log.Println("Validating AppImage", ai.path) // } // // Check validity of the updateinformation in this AppImage, if it contains some // if ai.UpdateInformation != "" { -// log.Println("Validating updateinformation in", ai.Path) +// log.Println("Validating updateinformation in", ai.path) // err := helpers.ValidateUpdateInformation(ai.UpdateInformation) // if err != nil { // helpers.PrintError("appimage: updateinformation verification", err) @@ -204,13 +176,6 @@ func (ai AppImage) determineImageType() int { // return nil // } -// func ioReader(file string) io.ReaderAt { -// r, err := os.Open(file) -// defer r.Close() -// helpers.LogError("appimage: elf:", err) -// return r -// } - // ExtractFile extracts a file from from filepath (which may contain * wildcards) // in an AppImage to the destinationdirpath. // Returns err in case of errors, or nil. @@ -218,24 +183,27 @@ func (ai AppImage) determineImageType() int { // TODO: Should this be a io.Reader()? func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, verbose bool) error { var err error - if ai.ImageType == 1 { + if ai.imageType == 1 { + //TODO: possibly replace this with a library err = os.MkdirAll(destinationdirpath, os.ModePerm) - cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.Path, filepath) + cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.path, filepath) _, err = runCommand(cmd, verbose) return err - } else if ai.ImageType == 2 { + } else if ai.imageType == 2 { if ai.reader != nil { file := ai.reader.GetFileAtPath(filepath) - if file != nil { //so we can fall back to command based extraction. - errs := file.ExtractTo(destinationdirpath) - if len(errs) != 0 { - //just return the first error - return errs[0] - } - return nil + if file == nil { + goto commandFallback } + errs := file.ExtractTo(destinationdirpath) + if len(errs) > 0 { + goto commandFallback + } + file.Close() + return nil } - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.Offset, 10), "-d", destinationdirpath, ai.Path, filepath) + commandFallback: + cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.Itoa(int(ai.offset)), "-d", destinationdirpath, ai.path, filepath) _, err = runCommand(cmd, verbose) return err } @@ -245,93 +213,37 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, verbo // ReadUpdateInformation reads updateinformation from an AppImage // Returns updateinformation string and error -func (ai AppImage) ReadUpdateInformation() (string, error) { - aibytes, err := helpers.GetSectionData(ai.Path, ".upd_info") - ui := strings.TrimSpace(string(bytes.Trim(aibytes, "\x00"))) +// +// Not needed until a proper interface for upgrading is implemented. +func (ai AppImage) readUpdateInformation() (string, error) { + aibytes, err := helpers.GetSectionData(ai.path, ".upd_info") if err != nil { return "", err } + ui := strings.TrimSpace(string(bytes.Trim(aibytes, "\x00"))) // Don't validate here, we don't want to get warnings all the time. // We have AppImage.Validate as its own function which we call less frequently than this. return ui, nil } -// LaunchMostRecentAppImage launches an the most recent application for a given -// updateinformation that we found among the integrated AppImages. -// Kinda like poor man's Launch Services. Probably we should make as much use of it as possible. -// Downside: Applications without updateinformation cannot be used in this way. -func LaunchMostRecentAppImage(updateinformation string, args []string, quiet bool) { - if updateinformation == "" { - return - } - if quiet == false { - aipath := FindMostRecentAppImageWithMatchingUpdateInformation(updateinformation) - log.Println("Launching", aipath, args) - cmd := []string{aipath} - cmd = append(cmd, args...) - err := helpers.RunCmdTransparently(cmd) - if err != nil { - helpers.PrintError("LaunchMostRecentAppImage", err) - } - - } -} - -// FindMostRecentAppImageWithMatchingUpdateInformation finds the most recent registered AppImage -// that havs matching upate information embedded -func FindMostRecentAppImageWithMatchingUpdateInformation(updateinformation string) string { - results := FindAppImagesWithMatchingUpdateInformation(updateinformation) - mostRecent := helpers.FindMostRecentFile(results) - return mostRecent -} - -// FindAppImagesWithMatchingUpdateInformation finds registered AppImages -// that have matching upate information embedded -func FindAppImagesWithMatchingUpdateInformation(updateinformation string) []string { - files, err := ioutil.ReadDir(xdg.DataHome + "/applications/") - helpers.LogError("desktop", err) - var results []string - if err != nil { - return results - } - for _, file := range files { - if strings.HasSuffix(file.Name(), ".desktop") && strings.HasPrefix(file.Name(), "appimagekit_") { - - cfg, e := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, // Do not cripple lines hat contain ";" - xdg.DataHome+"/applications/"+file.Name()) - helpers.LogError("desktop", e) - dst := cfg.Section("Desktop Entry").Key(execLocationKey).String() - _, err = os.Stat(dst) - if os.IsNotExist(err) { - log.Println(dst, "does not exist, it is mentioned in", xdg.DataHome+"/applications/"+file.Name()) - continue - } - ai := NewAppImage(dst) - ui, err := ai.ReadUpdateInformation() - if err == nil && ui != "" { - //log.Println("updateinformation:", ui) - // log.Println("updateinformation:", url.QueryEscape(ui)) - unescapedui, _ := url.QueryUnescape(ui) - // log.Println("updateinformation:", unescapedui) - if updateinformation == unescapedui { - results = append(results, ai.Path) - } - } - - continue - } - } - return results -} - // getFSTime reads FSTime from the AppImage. We are doing this only when it is needed, // not when an NewAppImage is called func (ai AppImage) getFSTime() time.Time { - if ai.ImageType == 2 { - result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.Offset, 10), ai.Path).Output() + if ai.imageType == 1 { + fil, err := os.Open(ai.path) + if err != nil { + return time.Unix(0, 0) + } + stat, _ := fil.Stat() + return stat.ModTime() + } else if ai.imageType == 2 { + if ai.reader != nil { + return ai.reader.ModTime() + } + result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.offset, 10), ai.path).Output() resstr := strings.TrimSpace(string(bytes.TrimSpace(result))) if err != nil { - helpers.PrintError("appimage: getFSTime: "+ai.Path, err) + helpers.PrintError("appimage: getFSTime: "+ai.path, err) return time.Unix(0, 0) } if n, err := strconv.Atoi(resstr); err == nil { @@ -340,6 +252,5 @@ func (ai AppImage) getFSTime() time.Time { log.Println("appimage: getFSTime:", resstr, "is not an integer.") return time.Unix(0, 0) } - log.Println("TODO: Implement getFSTime for type-1 AppImages") return time.Unix(0, 0) } From 5bcd85cbc01fd9ccc51ed16798f1928987d0db21 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Fri, 11 Dec 2020 13:48:32 -0600 Subject: [PATCH 05/23] More work on the goappimage library. Will now try to parse the desktop file at creation and get the AppImage's name from it ExtractFile can now (for type 2 with a working reader) resolve symlinks Added the ability to get the icon (a bit half baked right now though) Changed getFSTime to ModTime to be consistent with os.File. --- src/goappimage/README.md | 2 +- src/goappimage/appimage.go | 162 +++++++++++++++++++------------------ 2 files changed, 84 insertions(+), 80 deletions(-) diff --git a/src/goappimage/README.md b/src/goappimage/README.md index bec45206..5acad358 100644 --- a/src/goappimage/README.md +++ b/src/goappimage/README.md @@ -2,4 +2,4 @@ AppImage manipulation from Go. -Currently tries to read the squashfs using pure go (using [this library](https://github.com/CalebQ42/squashfs)). I that doesn't work, falls back to calling `unsquashfs`. \ No newline at end of file +Currently tries to read the squashfs using pure go (using [this library](https://github.com/CalebQ42/squashfs)). If that doesn't work, falls back to calling `unsquashfs`. \ No newline at end of file diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index 9f7e445c..b159ad3f 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -2,8 +2,8 @@ package goappimage import ( "bytes" + "errors" - "log" "os" "os/exec" "path/filepath" @@ -15,6 +15,7 @@ import ( "github.com/CalebQ42/squashfs" "github.com/probonopd/go-appimage/internal/helpers" + "gopkg.in/ini.v1" ) /* @@ -28,15 +29,12 @@ TODO List: */ // AppImage handles AppImage files. -// Currently it is using using a static build of mksquashfs/unsquashfs -// but eventually may be rewritten to do things natively in Go -// -// None of this is currently NEEDED to be exposed to the library user to edit. type AppImage struct { + Name string + Desktop *ini.File //The AppImages main .desktop file as an ini.File. Only available on type 2 AppImages right now. path string offset int64 updateInformation string - niceName string imageType int reader *squashfs.Reader } @@ -65,16 +63,12 @@ func NewAppImage(path string) AppImage { if ai.imageType < 0 { return ai } - ai.niceName = ai.calculateNiceName() - if ai.imageType < 1 { - return ai - } if ai.imageType > 1 { ai.offset = helpers.CalculateElfSize(ai.path) } if ai.imageType == 2 { //Try to populate the ai.Reader to make it easier to use and get information. - //The library is still very new, so we can fallback to command based functions if necessary. + //The library is still very new, so we can always fallback to command based functions if necessary. aiFil, err := os.Open(path) if err != nil { return ai @@ -89,16 +83,22 @@ func NewAppImage(path string) AppImage { return ai } ai.reader = reader - //TODO: possibly get some info (such as name) from the appimage's files, specifically it's desktop file. + //try to load up the desktop file for some information. + desktopFil := reader.GetFileAtPath("*.desktop") + if desktopFil != nil { + defer desktopFil.Close() + ai.Desktop, err = ini.Load(desktopFil) + if err == nil { + ai.Name = ai.Desktop.Section("Desktop Entry").Key("Name").Value() + } + } + } + if ai.Name == "" { + ai.Name = ai.calculateNiceName() } return ai } -//Name is the "nice" name of the AppImage. -func (ai AppImage) Name() string { - return ai.niceName -} - func (ai AppImage) calculateNiceName() string { //TODO: have this as a fallback to reading the appimage's .desktop file niceName := filepath.Base(ai.path) @@ -112,18 +112,6 @@ func (ai AppImage) calculateNiceName() string { return niceName } -func runCommand(cmd *exec.Cmd, verbose bool) (io.Writer, error) { - if verbose == true { - log.Printf("runCommand: %q\n", cmd) - } - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - // printError("runCommand", err) - // log.Println(cmd.Stdout) - return cmd.Stdout, err -} - // Check whether we have an AppImage at all. // Return image type, or -1 if it is not an AppImage func (ai AppImage) determineImageType() int { @@ -158,36 +146,19 @@ func (ai AppImage) determineImageType() int { return -1 } -// Validate checks the quality of an AppImage and sends desktop notification, returns error or nil -// TODO: Add more checks and reuse this in appimagetool -// func (ai AppImage) Validate(verbose bool) error { -// if verbose == true { -// log.Println("Validating AppImage", ai.path) -// } -// // Check validity of the updateinformation in this AppImage, if it contains some -// if ai.UpdateInformation != "" { -// log.Println("Validating updateinformation in", ai.path) -// err := helpers.ValidateUpdateInformation(ai.UpdateInformation) -// if err != nil { -// helpers.PrintError("appimage: updateinformation verification", err) -// return err -// } -// } -// return nil -// } - -// ExtractFile extracts a file from from filepath (which may contain * wildcards) -// in an AppImage to the destinationdirpath. -// Returns err in case of errors, or nil. -// TODO: resolve symlinks -// TODO: Should this be a io.Reader()? -func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, verbose bool) error { +//ExtractFile extracts a file from from filepath (which may contain * wildcards) +//in an AppImage to the destinationdirpath. +// +//If resolveSymlinks is true, any files that would be a symlink, the file or folder +//being linked is extracted in it's place. This is currently only supported on type 2 AppImages. +//TODO: make resolveSymlinks work, even if it's a type 1 appimage or using commands. +func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resolveSymlinks bool) error { var err error if ai.imageType == 1 { //TODO: possibly replace this with a library err = os.MkdirAll(destinationdirpath, os.ModePerm) cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.path, filepath) - _, err = runCommand(cmd, verbose) + _, err = runCommand(cmd) return err } else if ai.imageType == 2 { if ai.reader != nil { @@ -195,7 +166,12 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, verbo if file == nil { goto commandFallback } - errs := file.ExtractTo(destinationdirpath) + var errs []error + if resolveSymlinks { + errs = file.ExtractSymlink(destinationdirpath) + } else { + errs = file.ExtractTo(destinationdirpath) + } if len(errs) > 0 { goto commandFallback } @@ -204,53 +180,81 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, verbo } commandFallback: cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.Itoa(int(ai.offset)), "-d", destinationdirpath, ai.path, filepath) - _, err = runCommand(cmd, verbose) + _, err = runCommand(cmd) return err } // FIXME: What we may have extracted may well be (until here) broken symlinks... we need to do better than that return nil } +//This would actually simplify a bunch of things. +//TODO: func (ai AppImage) ExtractFileReader(filepath string, resolveSymlinks bool) (io.Reader, error){} + +//Icon tries to get the AppImage's icon and returns it as a io.ReadCloser. +func (ai AppImage) Icon() (io.ReadCloser, error) { + if ai.imageType == 1 { + //TODO + } else if ai.imageType == 2 { + if ai.reader != nil { + iconFil := ai.reader.GetFileAtPath(".DirIcon") + if iconFil == nil { + goto commandFallback + } + if iconFil.IsSymlink() { + iconFil = iconFil.GetSymlinkFile() + if iconFil == nil { + //If we've gotten this far, the reader is probably working properly and shouldn't fallback to commands. + return nil, errors.New("Icon is a symlink to a file outside the AppImage") //TODO: give the path to where it's pointing + } + } + return iconFil, nil + } + commandFallback: + //TODO + } + return nil, errors.New("Icon couldn't be found") +} + +func runCommand(cmd *exec.Cmd) (io.Writer, error) { + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + // printError("runCommand", err) + // log.Println(cmd.Stdout) + return cmd.Stdout, err +} + // ReadUpdateInformation reads updateinformation from an AppImage -// Returns updateinformation string and error -// -// Not needed until a proper interface for upgrading is implemented. func (ai AppImage) readUpdateInformation() (string, error) { aibytes, err := helpers.GetSectionData(ai.path, ".upd_info") if err != nil { return "", err } ui := strings.TrimSpace(string(bytes.Trim(aibytes, "\x00"))) - // Don't validate here, we don't want to get warnings all the time. - // We have AppImage.Validate as its own function which we call less frequently than this. return ui, nil } -// getFSTime reads FSTime from the AppImage. We are doing this only when it is needed, -// not when an NewAppImage is called -func (ai AppImage) getFSTime() time.Time { - if ai.imageType == 1 { - fil, err := os.Open(ai.path) - if err != nil { - return time.Unix(0, 0) - } - stat, _ := fil.Stat() - return stat.ModTime() - } else if ai.imageType == 2 { - if ai.reader != nil { - return ai.reader.ModTime() - } +//ModTime is the time the AppImage was edited/created. If the AppImage is type 2, +//it will try to get that information from the squashfs, if not, it returns the file's ModTime. +func (ai AppImage) ModTime() time.Time { + if ai.reader != nil { + return ai.reader.ModTime() + } + if ai.imageType == 2 { result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.offset, 10), ai.path).Output() resstr := strings.TrimSpace(string(bytes.TrimSpace(result))) if err != nil { - helpers.PrintError("appimage: getFSTime: "+ai.path, err) - return time.Unix(0, 0) + goto fallback } if n, err := strconv.Atoi(resstr); err == nil { return time.Unix(int64(n), 0) } - log.Println("appimage: getFSTime:", resstr, "is not an integer.") + } +fallback: + fil, err := os.Open(ai.path) + if err != nil { return time.Unix(0, 0) } - return time.Unix(0, 0) + stat, _ := fil.Stat() + return stat.ModTime() } From 4dd4ae2339591fb9eaa99db60b1603c3c30e60b5 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 12 Dec 2020 04:44:26 -0600 Subject: [PATCH 06/23] Expirementing with using a library for type 1 --- go.mod | 1 + go.sum | 3 ++ src/goappimage/appimage.go | 38 +++++++++++++++----- src/goappimage/appimage_test.go | 62 +++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 src/goappimage/appimage_test.go diff --git a/go.mod b/go.mod index 77de5c98..4a612e22 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/hashicorp/go-version v1.2.0 + github.com/kdomanski/iso9660 v0.2.0 github.com/otiai10/copy v1.1.1 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 github.com/prometheus/procfs v0.0.10 diff --git a/go.sum b/go.sum index 69d24af3..45d576e3 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kdomanski/iso9660 v0.2.0 h1:RMKfXdkq6bRs2ktomORF/sYAMY4An93IhUbHIiwFbTA= +github.com/kdomanski/iso9660 v0.2.0/go.mod h1:LY50s7BlG+ES6V99oxYGd0ub9giLrKdHZb3LLOweBj0= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc= @@ -139,6 +141,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index b159ad3f..c3d6a8a1 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -3,7 +3,7 @@ package goappimage import ( "bytes" "errors" - + "io" "os" "os/exec" "path/filepath" @@ -11,9 +11,8 @@ import ( "strings" "time" - "io" - "github.com/CalebQ42/squashfs" + iso "github.com/kdomanski/iso9660" "github.com/probonopd/go-appimage/internal/helpers" "gopkg.in/ini.v1" ) @@ -156,10 +155,10 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resol var err error if ai.imageType == 1 { //TODO: possibly replace this with a library - err = os.MkdirAll(destinationdirpath, os.ModePerm) - cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.path, filepath) - _, err = runCommand(cmd) - return err + // err = os.MkdirAll(destinationdirpath, os.ModePerm) + // cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.path, filepath) + // _, err = runCommand(cmd) + // return err } else if ai.imageType == 2 { if ai.reader != nil { file := ai.reader.GetFileAtPath(filepath) @@ -188,7 +187,30 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resol } //This would actually simplify a bunch of things. -//TODO: func (ai AppImage) ExtractFileReader(filepath string, resolveSymlinks bool) (io.Reader, error){} +func (ai AppImage) ExtractFileReader(filepath string, resolveSymlinks bool) (io.ReadCloser, error) { + if ai.imageType == 1 { + fil, err := os.Open(ai.path) + if err != nil { + return nil, err + } + isoRdr, err := iso.OpenImage(fil) + if err != nil { + return nil, err + } + } + if ai.reader != nil { + + } + // commandFallback: + // This will allows us to fallback to commands if necessary for either type. + // Will probably extract the file to a temp file using os.TempFile and delete it when Close() is called. + if ai.imageType == 1 { + + } else if ai.imageType == 2 { + + } + return nil, errors.New("Uh Oh") +} //Icon tries to get the AppImage's icon and returns it as a io.ReadCloser. func (ai AppImage) Icon() (io.ReadCloser, error) { diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go new file mode 100644 index 00000000..023f8dd5 --- /dev/null +++ b/src/goappimage/appimage_test.go @@ -0,0 +1,62 @@ +package goappimage + +import ( + "os" + "testing" +) + +const type1TestURL = "https://bintray.com/probono/AppImages/download_file?file_path=Audacity-2.1.2.glibc2.15-x86_64.AppImage" +const type1TestFilename = "Audacity-2.1.2.glibc2.15-x86_64.AppImage" + +func TestAppImageType1(t *testing.T) { + testImg := getAppImage(1, t) + ai := NewAppImage(testImg) + if ai.imageType == -1 { + t.Fatal("Not an appimage") + } + _, err := ai.ExtractFileReader("*.desktop", false) + if err != nil { + t.Fatal(err) + } + t.Fatal("No Problem") +} + +func getAppImage(imageType int, t *testing.T) string { + // var url string + var filename string + switch imageType { + case 1: + // url = type1TestURL + filename = type1TestFilename + default: + t.Fatal("What are you doing here?") + } + wdDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + //make sure we have a testing dir + _, err = os.Open(wdDir + "/testing") + if os.IsNotExist(err) { + err = os.Mkdir(wdDir+"/testing", os.ModePerm) + if err != nil { + t.Fatal(err) + } + _, err = os.Open(wdDir + "/testing") + if err != nil { + t.Fatal(err) + } + } else if err != nil { + t.Fatal(err) + } + _, err = os.Open(wdDir + "/testing/" + filename) + if os.IsNotExist(err) { + //TODO: download stuff + } else if err != nil { + t.Fatal(err) + } + return wdDir + "/testing/" + filename +} + +func downloadTestImage(imageType int, t *testing.T) { +} From 176419b1ed14317213ed54bb9583a7821727c2f4 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 12 Dec 2020 22:08:15 -0600 Subject: [PATCH 07/23] A bit more work on ExtractFileReader --- src/goappimage/appimage.go | 25 +++++++++++++++++++------ src/goappimage/appimage_test.go | 3 ++- src/goappimage/archivereader.go | 8 ++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/goappimage/archivereader.go diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index c3d6a8a1..bd9a9fe2 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -186,8 +186,24 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resol return nil } -//This would actually simplify a bunch of things. -func (ai AppImage) ExtractFileReader(filepath string, resolveSymlinks bool) (io.ReadCloser, error) { +//ExtractFileReader tries to get an io.ReadCloser for the file at filepath. +//Returns an error if the path is pointing to a folder. If the path is pointing to a symlink, +//it will try to return the file being pointed to, but only if it's within the AppImage. +// +//This will try to use a native Go library, but if that fails it will fallback to using +//unsquashfs or bsdtar by extracting the file to the temp directory (defined by os.TempDir) +//that gets deleted when close is called on the returned ReadCloser. +func (ai AppImage) ExtractFileReader(filepath string) (io.ReadCloser, error) { + if ai.reader != nil { + fil := ai.reader.GetFileAtPath(filepath) + if fil == nil { + goto commandFallback + } + if fil.IsSymlink() { + fil = fil.GetSymlinkFile() + } + return fil, nil + } if ai.imageType == 1 { fil, err := os.Open(ai.path) if err != nil { @@ -198,10 +214,7 @@ func (ai AppImage) ExtractFileReader(filepath string, resolveSymlinks bool) (io. return nil, err } } - if ai.reader != nil { - - } - // commandFallback: +commandFallback: // This will allows us to fallback to commands if necessary for either type. // Will probably extract the file to a temp file using os.TempFile and delete it when Close() is called. if ai.imageType == 1 { diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go index 023f8dd5..bce9d7e6 100644 --- a/src/goappimage/appimage_test.go +++ b/src/goappimage/appimage_test.go @@ -14,10 +14,11 @@ func TestAppImageType1(t *testing.T) { if ai.imageType == -1 { t.Fatal("Not an appimage") } - _, err := ai.ExtractFileReader("*.desktop", false) + rdr, err := ai.ExtractFileReader("*.desktop") if err != nil { t.Fatal(err) } + defer rdr.Close() t.Fatal("No Problem") } diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go new file mode 100644 index 00000000..96c72cf2 --- /dev/null +++ b/src/goappimage/archivereader.go @@ -0,0 +1,8 @@ +package goappimage + +import "io" + +//TODO: easy way to interact with both type 1 and type 2 appimages. +type archiveReader interface { + GetFileAtPath(path string, resolveSymlink bool) (io.ReadCloser, error) +} From 3b971d65b6b3a8e7af656bd81620de3f9fa948a6 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sun, 13 Dec 2020 06:05:38 -0600 Subject: [PATCH 08/23] Working on archiverReader type --- src/goappimage/archivereader.go | 82 ++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index 96c72cf2..f41baad6 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -1,8 +1,86 @@ package goappimage -import "io" +import ( + "errors" + "io" + "os" + "path" + "strings" + + "github.com/CalebQ42/squashfs" + "github.com/kdomanski/iso9660" +) //TODO: easy way to interact with both type 1 and type 2 appimages. type archiveReader interface { - GetFileAtPath(path string, resolveSymlink bool) (io.ReadCloser, error) + //rdr is the retuned reader, symlink says if the file at path is a symlink. If this is true, rdr will be nil. + GetFileAtPath(path string, resolveSymlink bool) (rdr io.ReadCloser, symlink bool, err error) +} + +type type2Reader struct { + reader *squashfs.Reader +} + +func newType2Reader(ai *AppImage) (*type2Reader, error) { + fil, err := os.Open(ai.path) + if err != nil { + return nil, err + } + stat, _ := fil.Stat() + rdr, err := squashfs.NewSquashfsReader(io.NewSectionReader(fil, ai.offset, stat.Size()-ai.offset)) + return &type2Reader{ + reader: rdr, + }, nil +} + +func (r *type2Reader) GetFileAtPath(filepath string, resolveSymlink bool) (io.ReadCloser, bool, error) { + fil := r.reader.GetFileAtPath(filepath) + if fil == nil { + return nil, false, errors.New("Not able to find file " + filepath) + } + if resolveSymlink { + sym := fil.GetSymlinkFile() + if sym == nil { + + } + } + return nil, true, nil } + +type type1Reader struct { + image *iso9660.Image +} + +func newType1Reader(ai *AppImage) (*type1Reader, error) { + fil, err := os.Open(ai.path) + if err != nil { + return nil, err + } + img, err := iso9660.OpenImage(fil) + if err != nil { + return nil, err + } + return &type1Reader{ + image: img, + }, nil +} + +func (r *type1Reader) GetFileAtPath(filepath string, resolveSymlink bool) (io.ReadCloser, bool, error) { + filepath = strings.Trim(path.Clean(filepath), "/") + dir, err := r.image.RootDir() + if err != nil { + return nil, false, err + } + children, err := dir.GetChildren() + if err != nil { + return nil, false, err + } + split := strings.Split(filepath, "/") + index := 0 + for _, child := range children { + name := path.Base(child.Name()) + if name == + } +} + +func From b31ab94855300af576e0b8b0371c4dfdfe744dc5 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Thu, 17 Dec 2020 02:38:03 -0600 Subject: [PATCH 09/23] Gave up on archiveReader Implemented symlink resolution for type 1 AppImages. --- go.mod | 3 +- go.sum | 7 +-- src/goappimage/appimage.go | 94 ++++++++++++++++++--------------- src/goappimage/appimage_test.go | 36 ++++++++++--- src/goappimage/archivereader.go | 86 ------------------------------ src/goappimage/type1utils.go | 25 +++++++++ 6 files changed, 108 insertions(+), 143 deletions(-) delete mode 100644 src/goappimage/archivereader.go create mode 100644 src/goappimage/type1utils.go diff --git a/go.mod b/go.mod index 4a612e22..cbaa455c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/probonopd/go-appimage go 1.13 require ( - github.com/CalebQ42/squashfs v0.3.4 + github.com/CalebQ42/squashfs v0.3.5 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 github.com/adrg/xdg v0.2.3 @@ -17,7 +17,6 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/hashicorp/go-version v1.2.0 - github.com/kdomanski/iso9660 v0.2.0 github.com/otiai10/copy v1.1.1 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 github.com/prometheus/procfs v0.0.10 diff --git a/go.sum b/go.sum index 45d576e3..2306a609 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= -github.com/CalebQ42/squashfs v0.3.4 h1:eVwy1UrBouv3iOUFHGIboyqM1Z8n6ZRMEOV/SbQWyx0= -github.com/CalebQ42/squashfs v0.3.4/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= +github.com/CalebQ42/squashfs v0.3.5 h1:EniKQIvaGkPIz/6WebIZGFnEV9Viio6cyRPMJs0B+/Y= +github.com/CalebQ42/squashfs v0.3.5/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:fMi9ZZ/it4orHj3xWrM6cLkVFcCbkXQALFUiNtHtCPs= @@ -68,8 +68,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kdomanski/iso9660 v0.2.0 h1:RMKfXdkq6bRs2ktomORF/sYAMY4An93IhUbHIiwFbTA= -github.com/kdomanski/iso9660 v0.2.0/go.mod h1:LY50s7BlG+ES6V99oxYGd0ub9giLrKdHZb3LLOweBj0= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc= @@ -141,7 +139,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index bd9a9fe2..6fa29eee 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -6,13 +6,13 @@ import ( "io" "os" "os/exec" + "path" "path/filepath" "strconv" "strings" "time" "github.com/CalebQ42/squashfs" - iso "github.com/kdomanski/iso9660" "github.com/probonopd/go-appimage/internal/helpers" "gopkg.in/ini.v1" ) @@ -29,13 +29,13 @@ TODO List: // AppImage handles AppImage files. type AppImage struct { - Name string - Desktop *ini.File //The AppImages main .desktop file as an ini.File. Only available on type 2 AppImages right now. + reader *squashfs.Reader + Desktop *ini.File path string - offset int64 updateInformation string - imageType int - reader *squashfs.Reader + Name string + offset int64 + imageType int //The AppImages main .desktop file as an ini.File. Only available on type 2 AppImages right now. } const execLocationKey = helpers.ExecLocationKey @@ -45,7 +45,7 @@ const execLocationKey = helpers.ExecLocationKey // because the AppImage that used to be there may need to be removed // and for this the functions of an AppImage are needed. // Non-existing and invalid AppImages will have type -1. -func NewAppImage(path string) AppImage { +func NewAppImage(path string) (*AppImage, error) { ai := AppImage{path: path, imageType: -1} // If we got a temp file, exit immediately // E.g., ignore typical Internet browser temporary files used during download @@ -55,12 +55,12 @@ func NewAppImage(path string) AppImage { strings.HasSuffix(path, ".partial") || strings.HasSuffix(path, ".zs-old") || strings.HasSuffix(path, ".crdownload") { - return ai + return nil, errors.New("Given path is a temporary file") } ai.imageType = ai.determineImageType() // Don't waste more time if the file is not actually an AppImage if ai.imageType < 0 { - return ai + return nil, errors.New("Given path is NOT an AppImage") } if ai.imageType > 1 { ai.offset = helpers.CalculateElfSize(ai.path) @@ -68,18 +68,14 @@ func NewAppImage(path string) AppImage { if ai.imageType == 2 { //Try to populate the ai.Reader to make it easier to use and get information. //The library is still very new, so we can always fallback to command based functions if necessary. - aiFil, err := os.Open(path) - if err != nil { - return ai - } + aiFil, _ := os.Open(path) stat, err := aiFil.Stat() if err != nil { - return ai + return &ai, err } - secReader := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) - reader, err := squashfs.NewSquashfsReader(secReader) + reader, err := squashfs.NewSquashfsReader(io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset)) if err != nil { - return ai + return &ai, nil } ai.reader = reader //try to load up the desktop file for some information. @@ -95,7 +91,7 @@ func NewAppImage(path string) AppImage { if ai.Name == "" { ai.Name = ai.calculateNiceName() } - return ai + return &ai, nil } func (ai AppImage) calculateNiceName() string { @@ -145,20 +141,43 @@ func (ai AppImage) determineImageType() int { return -1 } -//ExtractFile extracts a file from from filepath (which may contain * wildcards) -//in an AppImage to the destinationdirpath. +//ExtractFile extracts a file from from filepath (which may contain * wildcards) in an AppImage to the destinationdirpath. // -//If resolveSymlinks is true, any files that would be a symlink, the file or folder -//being linked is extracted in it's place. This is currently only supported on type 2 AppImages. -//TODO: make resolveSymlinks work, even if it's a type 1 appimage or using commands. +//If resolveSymlinks is true, if the filepath specified is a symlink, the actual file is extracted in it's place. +//On type 2 AppImages, this behavior is recursive if extracting a folder. +//resolveSymlinks will have no effect on absolute symlinks (symlinks that start at root). func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resolveSymlinks bool) error { var err error if ai.imageType == 1 { - //TODO: possibly replace this with a library - // err = os.MkdirAll(destinationdirpath, os.ModePerm) - // cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.path, filepath) - // _, err = runCommand(cmd) - // return err + err = os.MkdirAll(destinationdirpath, os.ModePerm) + if err != nil { + return err + } + name := path.Base(filepath) + filepath = strings.TrimPrefix(filepath, "/") + destinationdirpath = strings.TrimSuffix(destinationdirpath, "/") + tmpDir := destinationdirpath + "/" + ".temp" + err = os.Mkdir(tmpDir, os.ModePerm) + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + if resolveSymlinks { + filepath, err = ai.getSymlinkLocation(filepath) + if err != nil { + return err //The only way to get an error is if the bsdtar command spits out an error for filepath, so actual extraction will fail. + } + } + cmd := exec.Command("bsdtar", "-C", tmpDir, "-xf", ai.path, filepath) + _, err = runCommand(cmd) + if err != nil { + return err + } + err = os.Rename(tmpDir+"/"+filepath, destinationdirpath+"/"+name) + if err != nil { + return err + } + return err } else if ai.imageType == 2 { if ai.reader != nil { file := ai.reader.GetFileAtPath(filepath) @@ -205,22 +224,11 @@ func (ai AppImage) ExtractFileReader(filepath string) (io.ReadCloser, error) { return fil, nil } if ai.imageType == 1 { - fil, err := os.Open(ai.path) - if err != nil { - return nil, err - } - isoRdr, err := iso.OpenImage(fil) - if err != nil { - return nil, err - } } commandFallback: // This will allows us to fallback to commands if necessary for either type. // Will probably extract the file to a temp file using os.TempFile and delete it when Close() is called. - if ai.imageType == 1 { - - } else if ai.imageType == 2 { - + if ai.imageType == 2 { } return nil, errors.New("Uh Oh") } @@ -250,13 +258,11 @@ func (ai AppImage) Icon() (io.ReadCloser, error) { return nil, errors.New("Icon couldn't be found") } -func runCommand(cmd *exec.Cmd) (io.Writer, error) { +func runCommand(cmd *exec.Cmd) (bytes.Buffer, error) { var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() - // printError("runCommand", err) - // log.Println(cmd.Stdout) - return cmd.Stdout, err + return out, err } // ReadUpdateInformation reads updateinformation from an AppImage diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go index bce9d7e6..b478be50 100644 --- a/src/goappimage/appimage_test.go +++ b/src/goappimage/appimage_test.go @@ -5,20 +5,40 @@ import ( "testing" ) -const type1TestURL = "https://bintray.com/probono/AppImages/download_file?file_path=Audacity-2.1.2.glibc2.15-x86_64.AppImage" -const type1TestFilename = "Audacity-2.1.2.glibc2.15-x86_64.AppImage" +const type1TestURL = "https://bintray.com/probono/AppImages/download_file?file_path=Blender-2.78c.glibc2.17-x86_64.AppImage" +const type1TestFilename = "Blender-2.78c.glibc2.17-x86_64.AppImage" func TestAppImageType1(t *testing.T) { + wdDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + //make sure we have a testing dir + _, err = os.Open(wdDir + "/testing") + if os.IsNotExist(err) { + err = os.Mkdir(wdDir+"/testing", os.ModePerm) + if err != nil { + t.Fatal(err) + } + _, err = os.Open(wdDir + "/testing") + if err != nil { + t.Fatal(err) + } + } else if err != nil { + t.Fatal(err) + } testImg := getAppImage(1, t) - ai := NewAppImage(testImg) + ai, err := NewAppImage(testImg) + if err != nil { + t.Fatal(err) + } if ai.imageType == -1 { t.Fatal("Not an appimage") } - rdr, err := ai.ExtractFileReader("*.desktop") + err = ai.ExtractFile("usr/bin/lib/libGL.so", wdDir+"/testing/", true) //this tests nested extraction AND symlink resolution. if err != nil { t.Fatal(err) } - defer rdr.Close() t.Fatal("No Problem") } @@ -52,7 +72,11 @@ func getAppImage(imageType int, t *testing.T) string { } _, err = os.Open(wdDir + "/testing/" + filename) if os.IsNotExist(err) { - //TODO: download stuff + downloadTestImage(imageType, t) + _, err = os.Open(wdDir + "/testing/" + filename) + if err != nil { + t.Fatal(err) + } } else if err != nil { t.Fatal(err) } diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go deleted file mode 100644 index f41baad6..00000000 --- a/src/goappimage/archivereader.go +++ /dev/null @@ -1,86 +0,0 @@ -package goappimage - -import ( - "errors" - "io" - "os" - "path" - "strings" - - "github.com/CalebQ42/squashfs" - "github.com/kdomanski/iso9660" -) - -//TODO: easy way to interact with both type 1 and type 2 appimages. -type archiveReader interface { - //rdr is the retuned reader, symlink says if the file at path is a symlink. If this is true, rdr will be nil. - GetFileAtPath(path string, resolveSymlink bool) (rdr io.ReadCloser, symlink bool, err error) -} - -type type2Reader struct { - reader *squashfs.Reader -} - -func newType2Reader(ai *AppImage) (*type2Reader, error) { - fil, err := os.Open(ai.path) - if err != nil { - return nil, err - } - stat, _ := fil.Stat() - rdr, err := squashfs.NewSquashfsReader(io.NewSectionReader(fil, ai.offset, stat.Size()-ai.offset)) - return &type2Reader{ - reader: rdr, - }, nil -} - -func (r *type2Reader) GetFileAtPath(filepath string, resolveSymlink bool) (io.ReadCloser, bool, error) { - fil := r.reader.GetFileAtPath(filepath) - if fil == nil { - return nil, false, errors.New("Not able to find file " + filepath) - } - if resolveSymlink { - sym := fil.GetSymlinkFile() - if sym == nil { - - } - } - return nil, true, nil -} - -type type1Reader struct { - image *iso9660.Image -} - -func newType1Reader(ai *AppImage) (*type1Reader, error) { - fil, err := os.Open(ai.path) - if err != nil { - return nil, err - } - img, err := iso9660.OpenImage(fil) - if err != nil { - return nil, err - } - return &type1Reader{ - image: img, - }, nil -} - -func (r *type1Reader) GetFileAtPath(filepath string, resolveSymlink bool) (io.ReadCloser, bool, error) { - filepath = strings.Trim(path.Clean(filepath), "/") - dir, err := r.image.RootDir() - if err != nil { - return nil, false, err - } - children, err := dir.GetChildren() - if err != nil { - return nil, false, err - } - split := strings.Split(filepath, "/") - index := 0 - for _, child := range children { - name := path.Base(child.Name()) - if name == - } -} - -func diff --git a/src/goappimage/type1utils.go b/src/goappimage/type1utils.go new file mode 100644 index 00000000..71942464 --- /dev/null +++ b/src/goappimage/type1utils.go @@ -0,0 +1,25 @@ +package goappimage + +import ( + "os/exec" + "path" + "strings" +) + +//Tries to get the location the symlink at filepath is pointing to. If file is not a symlink, returns filepath. Only for type 1 +func (ai AppImage) getSymlinkLocation(filepath string) (string, error) { + cmd := exec.Command("bsdtar", "-f", ai.path, "-tv", filepath) + wrt, err := runCommand(cmd) + if err != nil { + return filepath, err + } + output := strings.TrimSuffix(string(wrt.Bytes()), "\n") + if index := strings.Index(output, "->"); index != -1 { //signifies symlink + symlinkedFile := output[index+3:] + if strings.HasPrefix(symlinkedFile, "/") { + return filepath, nil //we can't help with absolute symlinks... + } + return ai.getSymlinkLocation(path.Dir(filepath) + "/" + symlinkedFile) + } + return filepath, nil +} From 3c992dd047f0f524cb001d1400f14af2f228508f Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 19 Dec 2020 14:41:04 -0600 Subject: [PATCH 10/23] Re-implemented archiveReader. Still need to make it work with the main library though. --- go.mod | 22 +-- go.sum | 59 ++++--- src/goappimage/appimage.go | 31 +--- src/goappimage/appimage_test.go | 4 +- src/goappimage/archivereader.go | 300 ++++++++++++++++++++++++++++++++ src/goappimage/type1utils.go | 25 --- 6 files changed, 355 insertions(+), 86 deletions(-) create mode 100644 src/goappimage/archivereader.go delete mode 100644 src/goappimage/type1utils.go diff --git a/go.mod b/go.mod index cbaa455c..ceb9efdf 100644 --- a/go.mod +++ b/go.mod @@ -8,30 +8,30 @@ require ( github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 github.com/adrg/xdg v0.2.3 github.com/alokmenghrajani/gpgeez v0.0.0-20161206084504-1a06f1c582f9 - github.com/coreos/go-systemd/v22 v22.0.0 - github.com/eclipse/paho.mqtt.golang v1.2.0 + github.com/coreos/go-systemd/v22 v22.1.0 + github.com/eclipse/paho.mqtt.golang v1.3.0 github.com/esiqveland/notify v0.9.1 github.com/go-ole/go-ole v1.2.4 // indirect github.com/godbus/dbus/v5 v5.0.3 github.com/google/go-github v17.0.0+incompatible github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c - github.com/hashicorp/go-version v1.2.0 - github.com/otiai10/copy v1.1.1 + github.com/hashicorp/go-version v1.2.1 + github.com/otiai10/copy v1.3.0 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 - github.com/prometheus/procfs v0.0.10 + github.com/prometheus/procfs v0.2.0 github.com/rjeczalik/notify v0.9.2 github.com/sabhiram/png-embed v0.0.0-20180421025336-149afe9a3ccb github.com/sabhiram/pngr v0.0.0-20180419043407-2df49b015d4b // indirect - github.com/shirou/gopsutil v2.20.2+incompatible - github.com/shuheiktgw/go-travis v0.2.4 + github.com/shirou/gopsutil v3.20.11+incompatible + github.com/shuheiktgw/go-travis v0.3.1 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 - github.com/urfave/cli/v2 v2.2.0 + github.com/urfave/cli/v2 v2.3.0 go.lsp.dev/uri v0.3.0 - golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 - golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect - golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 + golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 + golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect + golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e gopkg.in/ini.v1 v1.62.0 gopkg.in/src-d/go-git.v4 v4.13.1 ) diff --git a/go.sum b/go.sum index 2306a609..6b6d2798 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -29,8 +29,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/eclipse/paho.mqtt.golang v1.3.0 h1:MU79lqr3FKNKbSrGN7d7bNYqh8MwWW7Zcx0iG+VIw9I= +github.com/eclipse/paho.mqtt.golang v1.3.0/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/esiqveland/notify v0.9.1 h1:hX6ZD3FCQJXI46AzUM/iWekcMfnZ9TPE4uIu9Hrn1D4= @@ -57,12 +57,14 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj2I9TP8Jc+a7lge3QWn9DKE7NCwfc= github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -87,14 +89,14 @@ github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo= -github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.3.0 h1:Z0OIFgj8hyI18YVzgPXpp652vv0NggCGWaNKSPN9KU8= +github.com/otiai10/copy v1.3.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= +github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA= github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -105,8 +107,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 h1:m55h8x868XCJE/gt885caYVx8HfaMv8S5ow9eW+D+tg= github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7/go.mod h1:euJYoVMkwvAORq0XTRM2kj2B+NDbWmoEJbXIIR3M7sI= -github.com/prometheus/procfs v0.0.10 h1:QJQN3jYQhkamO4mhfUWqdDH2asK7ONOI9MTWjyAxNKM= -github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -117,10 +119,10 @@ github.com/sabhiram/pngr v0.0.0-20180419043407-2df49b015d4b h1:ks2d0TH6CtUISoThc github.com/sabhiram/pngr v0.0.0-20180419043407-2df49b015d4b/go.mod h1:BzaQ/DolG+VD2GwnmGuSO5gx07vyW/CWNfkfVW71mSw= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v2.20.2+incompatible h1:ucK79BhBpgqQxPASyS2cu9HX8cfDVljBN1WWFvbNvgY= -github.com/shirou/gopsutil v2.20.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shuheiktgw/go-travis v0.2.4 h1:IAnh/Dyv7ql87qtJWUcvR5MM8e5iCDGoENl9VzpxAHc= -github.com/shuheiktgw/go-travis v0.2.4/go.mod h1:RtODX49bvgHTvfzFvGEPFtU0dKVk0D3PyvUQR/63hT0= +github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto= +github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shuheiktgw/go-travis v0.3.1 h1:SAT16mi77ccqogOslnXxBXzXbpeyChaIYUwi2aJpVZY= +github.com/shuheiktgw/go-travis v0.3.1/go.mod h1:avnFFDqJDdRHwlF9tgqvYi3asQCm/HGL8aLxYiKa4Yg= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -143,8 +145,8 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= @@ -153,10 +155,10 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -165,6 +167,8 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -174,8 +178,13 @@ golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -204,7 +213,7 @@ gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index 6fa29eee..f7f7708e 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "github.com/CalebQ42/squashfs" "github.com/probonopd/go-appimage/internal/helpers" "gopkg.in/ini.v1" ) @@ -29,7 +28,7 @@ TODO List: // AppImage handles AppImage files. type AppImage struct { - reader *squashfs.Reader + reader archiveReader Desktop *ini.File path string updateInformation string @@ -65,23 +64,11 @@ func NewAppImage(path string) (*AppImage, error) { if ai.imageType > 1 { ai.offset = helpers.CalculateElfSize(ai.path) } - if ai.imageType == 2 { - //Try to populate the ai.Reader to make it easier to use and get information. - //The library is still very new, so we can always fallback to command based functions if necessary. - aiFil, _ := os.Open(path) - stat, err := aiFil.Stat() - if err != nil { - return &ai, err - } - reader, err := squashfs.NewSquashfsReader(io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset)) - if err != nil { - return &ai, nil - } - ai.reader = reader + err := ai.populateReader() + if err == nil { //try to load up the desktop file for some information. - desktopFil := reader.GetFileAtPath("*.desktop") - if desktopFil != nil { - defer desktopFil.Close() + desktopFil, err := ai.reader.FileReader("*.desktop") + if err == nil { ai.Desktop, err = ini.Load(desktopFil) if err == nil { ai.Name = ai.Desktop.Section("Desktop Entry").Key("Name").Value() @@ -201,17 +188,12 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resol _, err = runCommand(cmd) return err } - // FIXME: What we may have extracted may well be (until here) broken symlinks... we need to do better than that return nil } //ExtractFileReader tries to get an io.ReadCloser for the file at filepath. //Returns an error if the path is pointing to a folder. If the path is pointing to a symlink, -//it will try to return the file being pointed to, but only if it's within the AppImage. -// -//This will try to use a native Go library, but if that fails it will fallback to using -//unsquashfs or bsdtar by extracting the file to the temp directory (defined by os.TempDir) -//that gets deleted when close is called on the returned ReadCloser. +// //it will try to return the file being pointed to, but only if it's within the AppImage. func (ai AppImage) ExtractFileReader(filepath string) (io.ReadCloser, error) { if ai.reader != nil { fil := ai.reader.GetFileAtPath(filepath) @@ -229,6 +211,7 @@ commandFallback: // This will allows us to fallback to commands if necessary for either type. // Will probably extract the file to a temp file using os.TempFile and delete it when Close() is called. if ai.imageType == 2 { + //TODO } return nil, errors.New("Uh Oh") } diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go index b478be50..0976de0e 100644 --- a/src/goappimage/appimage_test.go +++ b/src/goappimage/appimage_test.go @@ -1,6 +1,7 @@ package goappimage import ( + "fmt" "os" "testing" ) @@ -32,10 +33,11 @@ func TestAppImageType1(t *testing.T) { if err != nil { t.Fatal(err) } + fmt.Println("name", ai.Name) if ai.imageType == -1 { t.Fatal("Not an appimage") } - err = ai.ExtractFile("usr/bin/lib/libGL.so", wdDir+"/testing/", true) //this tests nested extraction AND symlink resolution. + _, err = newType1Reader(testImg) if err != nil { t.Fatal(err) } diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go new file mode 100644 index 00000000..8b01a5b8 --- /dev/null +++ b/src/goappimage/archivereader.go @@ -0,0 +1,300 @@ +package goappimage + +import ( + "bytes" + "errors" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "sort" + "strings" + + "github.com/CalebQ42/squashfs" +) + +type archiveReader interface { + //FileReader returns an io.ReadCloser for a file at the given path. + //If the given path is a symlink, it will return the link's reader. + //If the symlink is to an absolute path, an error is returned. + //Returns an error if the given path is a directory. + FileReader(path string) (io.ReadCloser, error) + //IsDir returns if the given path points to a directory. + IsDir(path string) bool + //SymlinkPath returns where the symlink at path is pointing. + //If the given path is not a symlink, just returns the given path. + SymlinkPath(path string) string + //Contains returns if the given path is contained in the archive. + Contains(path string) bool + //ListFiles returns a list of filenames at the given directory. + //Returns nil if the given path is a symlink, file, or isn't contained. + ListFiles(path string) []string + //ExtractTo extracts the file/folder at path to the folder at destination. + ExtractTo(path, destination string, resolveSymlinks bool) error +} + +func (ai *AppImage) populateReader() (err error) { + if ai.imageType == 1 { + ai.reader, err = newType1Reader(ai.path) + } else if ai.imageType == 2 { + ai.reader, err = newType2Reader(ai) + return err + } + return errors.New("HIII") +} + +type type2Reader struct { + rdr *squashfs.Reader +} + +func newType2Reader(ai *AppImage) (*type2Reader, error) { + aiFil, err := os.Open(ai.path) + if err != nil { + return nil, err + } + stat, _ := aiFil.Stat() + aiRdr := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) + squashRdr, err := squashfs.NewSquashfsReader(aiRdr) + if err != nil { + return nil, err + } + return &type2Reader{ + rdr: squashRdr, + }, nil +} + +func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { + fil := r.rdr.GetFileAtPath(path) + if fil == nil { + return nil, errors.New("Can't find file at: " + path) + } + if fil.IsSymlink() { + fil = fil.GetSymlinkFileRecursive() + if fil == nil { + return nil, errors.New("Can't resolve symlink at: " + path) + } + } + if fil.IsDir() { + return nil, errors.New("Path is a directory: " + path) + } + return fil, nil +} + +func (r *type2Reader) IsDir(path string) bool { + fil := r.rdr.GetFileAtPath(path) + if fil == nil { + return false + } + if fil.IsSymlink() { + fil = fil.GetSymlinkFileRecursive() + if fil == nil { + return false + } + } + return fil.IsDir() +} + +func (r *type2Reader) SymlinkPath(path string) string { + fil := r.rdr.GetFileAtPath(path) + if fil == nil { + return path + } + if fil.IsSymlink() { + fil = fil.GetSymlinkFile() + } + return path +} + +func (r *type2Reader) Contains(path string) bool { + fil := r.rdr.GetFileAtPath(path) + return fil != nil +} + +func (r *type2Reader) ListFiles(path string) []string { + fil := r.rdr.GetFileAtPath(path) + if fil == nil { + return nil + } + if fil.IsSymlink() { + fil = fil.GetSymlinkFileRecursive() + if fil == nil { + return nil + } + } + if !fil.IsDir() { + return nil + } + children, err := fil.GetChildren() + if err != nil { + return nil + } + out := make([]string, 0) + for _, child := range children { + out = append(out, child.Name()) + } + return out +} + +func (r *type2Reader) ExtractTo(path, destination string, resolveSymlinks bool) error { + fil := r.rdr.GetFileAtPath(path) + if fil == nil { + return nil + } + if fil.IsSymlink() && resolveSymlinks { + tmp := fil.GetSymlinkFileRecursive() + if tmp != nil { + errs := tmp.ExtractTo(destination) + if len(errs) > 0 { + return errs[0] + } + return nil + } + } + errs := fil.ExtractTo(destination) + if len(errs) > 0 { + return errs[0] + } + return nil +} + +type type1Reader struct { + structure map[string][]string //[folder]files + path string +} + +func newType1Reader(filepath string) (*type1Reader, error) { + cmd := exec.Command("bsdtar", "-f", filepath, "-t") + wrt, err := runCommand(cmd) + if err != nil { + return nil, err + } + containedFiles := strings.Split(string(wrt.Bytes()), "\n") + var rdr type1Reader + rdr.path = filepath + rdr.structure = make(map[string][]string) + for _, contained := range containedFiles { + contained = path.Clean(contained) + if contained == "" || contained == "." { + continue + } + fileName := path.Base(contained) + dir := path.Dir(contained) + if !strings.Contains(contained, "/") { + dir = "/" + } + if rdr.structure[dir] == nil { + rdr.structure[dir] = make([]string, 0) + } + rdr.structure[dir] = append(rdr.structure[dir], fileName) + } + for folds := range rdr.structure { + sort.Strings(rdr.structure[folds]) + } + return nil, nil +} + +func (r *type1Reader) getSymlinkLocation(filepath string) (string, error) { + cmd := exec.Command("bsdtar", "-f", r.path, "-tv", filepath) + wrt, err := runCommand(cmd) + if err != nil { + return filepath, err + } + output := strings.TrimSuffix(string(wrt.Bytes()), "\n") + if index := strings.Index(output, "->"); index != -1 { //signifies symlink + symlinkedFile := output[index+3:] + if strings.HasPrefix(symlinkedFile, "/") { + return filepath, nil //we can't help with absolute symlinks... + } + return r.getSymlinkLocation(path.Dir(filepath) + "/" + symlinkedFile) + } + return filepath, nil +} + +func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { + if !r.Contains(filepath) { + return nil, errors.New("File not found at: " + filepath) + } + cmd := exec.Command("bsdtar", "-f", r.path, "-xO", filepath) + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return nil, err + } + return ioutil.NopCloser(&out), nil +} + +func (r *type1Reader) IsDir(filepath string) bool { + filepath = strings.TrimPrefix(filepath, "/") + filepath = path.Clean(filepath) + if filepath == "" { + return true //this means they're asking if root is a dir..... + } + return r.structure[filepath] != nil +} + +func (r *type1Reader) SymlinkPath(filepath string) string { + cmd := exec.Command("bsdtar", "-f", r.path, "-tv", filepath) + wrt, err := runCommand(cmd) + if err != nil { + return filepath + } + output := strings.TrimSuffix(string(wrt.Bytes()), "\n") + output = strings.Split(output, "\n")[0] //Make sure we are only getting the first value that matches + if index := strings.Index(output, "->"); index != -1 { //signifies symlink + return output[index+3:] + } + return filepath +} + +func (r *type1Reader) Contains(filepath string) bool { + filepath = strings.TrimPrefix(filepath, "/") + filepath = path.Clean(filepath) + dir := path.Dir(filepath) + name := path.Base(filepath) + if dir == "" { + dir = "/" + } + return sort.SearchStrings(r.structure[dir], name) != len(r.structure[dir]) +} +func (r *type1Reader) ListFiles(filepath string) []string { + filepath = strings.TrimPrefix(filepath, "/") + filepath = path.Clean(filepath) + if filepath == "" { + return r.structure["/"] + } + return r.structure[filepath] +} + +func (r *type1Reader) ExtractTo(filepath, destination string, resolveSymlinks bool) error { + err := os.MkdirAll(destination, os.ModePerm) + if err != nil { + return err + } + name := path.Base(filepath) + filepath = strings.TrimPrefix(filepath, "/") + destination = strings.TrimSuffix(destination, "/") + tmpDir := destination + "/" + ".temp" + err = os.Mkdir(tmpDir, os.ModePerm) + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + if resolveSymlinks { + filepath, err = r.getSymlinkLocation(filepath) + if err != nil { + return err //The only way to get an error is if the bsdtar command spits out an error for filepath, so actual extraction will fail. + } + } + cmd := exec.Command("bsdtar", "-C", tmpDir, "-f", r.path, "-x", filepath) + _, err = runCommand(cmd) + if err != nil { + return err + } + err = os.Rename(tmpDir+"/"+filepath, destination+"/"+name) + if err != nil { + return err + } + return err +} diff --git a/src/goappimage/type1utils.go b/src/goappimage/type1utils.go deleted file mode 100644 index 71942464..00000000 --- a/src/goappimage/type1utils.go +++ /dev/null @@ -1,25 +0,0 @@ -package goappimage - -import ( - "os/exec" - "path" - "strings" -) - -//Tries to get the location the symlink at filepath is pointing to. If file is not a symlink, returns filepath. Only for type 1 -func (ai AppImage) getSymlinkLocation(filepath string) (string, error) { - cmd := exec.Command("bsdtar", "-f", ai.path, "-tv", filepath) - wrt, err := runCommand(cmd) - if err != nil { - return filepath, err - } - output := strings.TrimSuffix(string(wrt.Bytes()), "\n") - if index := strings.Index(output, "->"); index != -1 { //signifies symlink - symlinkedFile := output[index+3:] - if strings.HasPrefix(symlinkedFile, "/") { - return filepath, nil //we can't help with absolute symlinks... - } - return ai.getSymlinkLocation(path.Dir(filepath) + "/" + symlinkedFile) - } - return filepath, nil -} From cb5c2f30d4332d6e3345d75027ce6341f109f5af Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sun, 20 Dec 2020 16:25:28 -0600 Subject: [PATCH 11/23] Maybe finished with the library? archiveReader should work properly for both type1 and type2 Mainly just need to test everything right now. --- src/goappimage/appimage.go | 110 +++++--------------------------- src/goappimage/archivereader.go | 106 ++++++++++++++++++++++-------- 2 files changed, 95 insertions(+), 121 deletions(-) diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index f7f7708e..2d5646e2 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -6,7 +6,6 @@ import ( "io" "os" "os/exec" - "path" "path/filepath" "strconv" "strings" @@ -82,7 +81,6 @@ func NewAppImage(path string) (*AppImage, error) { } func (ai AppImage) calculateNiceName() string { - //TODO: have this as a fallback to reading the appimage's .desktop file niceName := filepath.Base(ai.path) niceName = strings.Replace(niceName, ".AppImage", "", -1) niceName = strings.Replace(niceName, ".appimage", "", -1) @@ -134,61 +132,15 @@ func (ai AppImage) determineImageType() int { //On type 2 AppImages, this behavior is recursive if extracting a folder. //resolveSymlinks will have no effect on absolute symlinks (symlinks that start at root). func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resolveSymlinks bool) error { - var err error - if ai.imageType == 1 { - err = os.MkdirAll(destinationdirpath, os.ModePerm) - if err != nil { - return err - } - name := path.Base(filepath) - filepath = strings.TrimPrefix(filepath, "/") - destinationdirpath = strings.TrimSuffix(destinationdirpath, "/") - tmpDir := destinationdirpath + "/" + ".temp" - err = os.Mkdir(tmpDir, os.ModePerm) - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) - if resolveSymlinks { - filepath, err = ai.getSymlinkLocation(filepath) - if err != nil { - return err //The only way to get an error is if the bsdtar command spits out an error for filepath, so actual extraction will fail. - } - } - cmd := exec.Command("bsdtar", "-C", tmpDir, "-xf", ai.path, filepath) - _, err = runCommand(cmd) - if err != nil { - return err - } - err = os.Rename(tmpDir+"/"+filepath, destinationdirpath+"/"+name) - if err != nil { - return err - } - return err - } else if ai.imageType == 2 { - if ai.reader != nil { - file := ai.reader.GetFileAtPath(filepath) - if file == nil { - goto commandFallback - } - var errs []error - if resolveSymlinks { - errs = file.ExtractSymlink(destinationdirpath) - } else { - errs = file.ExtractTo(destinationdirpath) - } - if len(errs) > 0 { - goto commandFallback - } - file.Close() - return nil - } - commandFallback: + if ai.reader != nil { + return ai.reader.ExtractTo(filepath, destinationdirpath, resolveSymlinks) + } + if ai.imageType == 2 { cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.Itoa(int(ai.offset)), "-d", destinationdirpath, ai.path, filepath) - _, err = runCommand(cmd) + _, err := runCommand(cmd) return err } - return nil + return errors.New("Unable to extract") } //ExtractFileReader tries to get an io.ReadCloser for the file at filepath. @@ -196,47 +148,15 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resol // //it will try to return the file being pointed to, but only if it's within the AppImage. func (ai AppImage) ExtractFileReader(filepath string) (io.ReadCloser, error) { if ai.reader != nil { - fil := ai.reader.GetFileAtPath(filepath) - if fil == nil { - goto commandFallback - } - if fil.IsSymlink() { - fil = fil.GetSymlinkFile() - } - return fil, nil + return ai.reader.FileReader(filepath) } - if ai.imageType == 1 { - } -commandFallback: - // This will allows us to fallback to commands if necessary for either type. - // Will probably extract the file to a temp file using os.TempFile and delete it when Close() is called. - if ai.imageType == 2 { - //TODO - } - return nil, errors.New("Uh Oh") + return nil, errors.New("Unable to get reader for " + filepath) } -//Icon tries to get the AppImage's icon and returns it as a io.ReadCloser. -func (ai AppImage) Icon() (io.ReadCloser, error) { - if ai.imageType == 1 { - //TODO - } else if ai.imageType == 2 { - if ai.reader != nil { - iconFil := ai.reader.GetFileAtPath(".DirIcon") - if iconFil == nil { - goto commandFallback - } - if iconFil.IsSymlink() { - iconFil = iconFil.GetSymlinkFile() - if iconFil == nil { - //If we've gotten this far, the reader is probably working properly and shouldn't fallback to commands. - return nil, errors.New("Icon is a symlink to a file outside the AppImage") //TODO: give the path to where it's pointing - } - } - return iconFil, nil - } - commandFallback: - //TODO +//Thumbnail tries to get the AppImage's thumbnail and returns it as a io.ReadCloser. +func (ai AppImage) Thumbnail() (io.ReadCloser, error) { + if ai.reader != nil { + return ai.reader.FileReader(".DirIcon") } return nil, errors.New("Icon couldn't be found") } @@ -261,10 +181,10 @@ func (ai AppImage) readUpdateInformation() (string, error) { //ModTime is the time the AppImage was edited/created. If the AppImage is type 2, //it will try to get that information from the squashfs, if not, it returns the file's ModTime. func (ai AppImage) ModTime() time.Time { - if ai.reader != nil { - return ai.reader.ModTime() - } if ai.imageType == 2 { + if ai.reader != nil { + return ai.reader.(*type2Reader).rdr.ModTime() + } result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.offset, 10), ai.path).Output() resstr := strings.TrimSpace(string(bytes.TrimSpace(result))) if err != nil { diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index 8b01a5b8..707a16d5 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -25,6 +25,10 @@ type archiveReader interface { //SymlinkPath returns where the symlink at path is pointing. //If the given path is not a symlink, just returns the given path. SymlinkPath(path string) string + //SymlinkPath is similiar to SymlinkPath, but will recursively try + //to get the symlink's path. If the location is outside the archive, + //the initial path is returned. + SymlinkPathRecursive(path string) string //Contains returns if the given path is contained in the archive. Contains(path string) bool //ListFiles returns a list of filenames at the given directory. @@ -101,7 +105,21 @@ func (r *type2Reader) SymlinkPath(path string) string { return path } if fil.IsSymlink() { - fil = fil.GetSymlinkFile() + return fil.SymlinkPath() + } + return path +} + +func (r *type2Reader) SymlinkPathRecursive(path string) string { + fil := r.rdr.GetFileAtPath(path) + if fil == nil { + return path + } + if fil.IsSymlink() { + tmpLoc := r.SymlinkPathRecursive(fil.Path()) + if tmpLoc != fil.Path() { + return tmpLoc + } } return path } @@ -159,8 +177,9 @@ func (r *type2Reader) ExtractTo(path, destination string, resolveSymlinks bool) } type type1Reader struct { - structure map[string][]string //[folder]files + structure map[string][]string //[folder]File path string + folders []string } func newType1Reader(filepath string) (*type1Reader, error) { @@ -180,41 +199,59 @@ func newType1Reader(filepath string) (*type1Reader, error) { } fileName := path.Base(contained) dir := path.Dir(contained) + dir = path.Clean(dir) if !strings.Contains(contained, "/") { dir = "/" } - if rdr.structure[dir] == nil { - rdr.structure[dir] = make([]string, 0) + if rdr.structure[dir] == nil && dir != "/" { + rdr.folders = append(rdr.folders, dir) } rdr.structure[dir] = append(rdr.structure[dir], fileName) } + sort.Strings(rdr.folders) for folds := range rdr.structure { sort.Strings(rdr.structure[folds]) } return nil, nil } -func (r *type1Reader) getSymlinkLocation(filepath string) (string, error) { - cmd := exec.Command("bsdtar", "-f", r.path, "-tv", filepath) - wrt, err := runCommand(cmd) - if err != nil { - return filepath, err - } - output := strings.TrimSuffix(string(wrt.Bytes()), "\n") - if index := strings.Index(output, "->"); index != -1 { //signifies symlink - symlinkedFile := output[index+3:] - if strings.HasPrefix(symlinkedFile, "/") { - return filepath, nil //we can't help with absolute symlinks... +func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { + //I need to make sure that, if there is wildcards, that we only get ONE file. If we get more then one, ALL matching files will be in Stdout. + //Probably a bit spagetti code... + filepath = strings.TrimPrefix(filepath, "/") + filepath = path.Clean(filepath) + var filepathDir string + if strings.Contains(filepath, "/") { + for _, dir := range r.folders { + match, _ := path.Match(dir, path.Dir(filepath)) + if match { + filepathDir = dir + break + } + } + } else { + filepathDir = "/" + } + if filepathDir == "" { + return nil, errors.New("File not found in the archive") + } + var filepathName string + for _, fil := range r.structure[filepathDir] { + match, _ := path.Match(fil, path.Base(filepath)) + if match { + filepathName = fil + break } - return r.getSymlinkLocation(path.Dir(filepath) + "/" + symlinkedFile) } - return filepath, nil -} - -func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { - if !r.Contains(filepath) { - return nil, errors.New("File not found at: " + filepath) + if filepathName == "" { + return nil, errors.New("File not found in the archive") + } + if filepathDir == "/" { + filepath = filepathName + } else { + filepath = filepathDir + "/" + filepathName } + //We're finally sure we have just ONE file, lol cmd := exec.Command("bsdtar", "-f", r.path, "-xO", filepath) var out bytes.Buffer cmd.Stdout = &out @@ -248,6 +285,26 @@ func (r *type1Reader) SymlinkPath(filepath string) string { return filepath } +func (r *type1Reader) SymlinkPathRecursive(filepath string) string { + cmd := exec.Command("bsdtar", "-f", r.path, "-tv", filepath) + wrt, err := runCommand(cmd) + if err != nil { + return filepath + } + output := strings.TrimSuffix(string(wrt.Bytes()), "\n") + if index := strings.Index(output, "->"); index != -1 { //signifies symlink + symlinkedFile := output[index+3:] + if strings.HasPrefix(symlinkedFile, "/") { + return filepath //we can't help with absolute symlinks... + } + tmp := r.SymlinkPathRecursive(path.Dir(filepath) + "/" + symlinkedFile) + if tmp != path.Dir(filepath)+"/"+symlinkedFile { + return tmp + } + } + return filepath +} + func (r *type1Reader) Contains(filepath string) bool { filepath = strings.TrimPrefix(filepath, "/") filepath = path.Clean(filepath) @@ -282,10 +339,7 @@ func (r *type1Reader) ExtractTo(filepath, destination string, resolveSymlinks bo } defer os.RemoveAll(tmpDir) if resolveSymlinks { - filepath, err = r.getSymlinkLocation(filepath) - if err != nil { - return err //The only way to get an error is if the bsdtar command spits out an error for filepath, so actual extraction will fail. - } + filepath = r.SymlinkPathRecursive(filepath) } cmd := exec.Command("bsdtar", "-C", tmpDir, "-f", r.path, "-x", filepath) _, err = runCommand(cmd) From fe58f62e94702a0aaedfc0a884d2a65a1b56e21f Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Mon, 21 Dec 2020 09:54:41 -0600 Subject: [PATCH 12/23] Fixed some issues --- src/goappimage/appimage.go | 38 ++++++++-------- src/goappimage/archivereader.go | 79 +++++++++++++++++++++++---------- 2 files changed, 74 insertions(+), 43 deletions(-) diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index 2d5646e2..928681d4 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -18,8 +18,6 @@ import ( /* TODO List: -* Provide a way to get the desktop file, or at least an ini.File representation of it. -* Provide a way to get thumbnail. * Check if there IS an update * Download said update @@ -27,13 +25,14 @@ TODO List: // AppImage handles AppImage files. type AppImage struct { - reader archiveReader - Desktop *ini.File - path string - updateInformation string - Name string - offset int64 - imageType int //The AppImages main .desktop file as an ini.File. Only available on type 2 AppImages right now. + reader archiveReader + //Desktop is the AppImage's main .desktop file parsed as an ini.File. + Desktop *ini.File + path string + // updateInformation string TODO: add update stuff + Name string + offset int64 + imageType int } const execLocationKey = helpers.ExecLocationKey @@ -129,7 +128,6 @@ func (ai AppImage) determineImageType() int { //ExtractFile extracts a file from from filepath (which may contain * wildcards) in an AppImage to the destinationdirpath. // //If resolveSymlinks is true, if the filepath specified is a symlink, the actual file is extracted in it's place. -//On type 2 AppImages, this behavior is recursive if extracting a folder. //resolveSymlinks will have no effect on absolute symlinks (symlinks that start at root). func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resolveSymlinks bool) error { if ai.reader != nil { @@ -145,11 +143,12 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resol //ExtractFileReader tries to get an io.ReadCloser for the file at filepath. //Returns an error if the path is pointing to a folder. If the path is pointing to a symlink, -// //it will try to return the file being pointed to, but only if it's within the AppImage. +//it will try to return the file being pointed to, but only if it's within the AppImage. func (ai AppImage) ExtractFileReader(filepath string) (io.ReadCloser, error) { if ai.reader != nil { return ai.reader.FileReader(filepath) } + //TODO: possible type2 command fallback, but unsquashfs can't print to Stdout from what I've seen. return nil, errors.New("Unable to get reader for " + filepath) } @@ -168,15 +167,16 @@ func runCommand(cmd *exec.Cmd) (bytes.Buffer, error) { return out, err } +// TODO: implement update functionality // ReadUpdateInformation reads updateinformation from an AppImage -func (ai AppImage) readUpdateInformation() (string, error) { - aibytes, err := helpers.GetSectionData(ai.path, ".upd_info") - if err != nil { - return "", err - } - ui := strings.TrimSpace(string(bytes.Trim(aibytes, "\x00"))) - return ui, nil -} +// func (ai AppImage) readUpdateInformation() (string, error) { +// aibytes, err := helpers.GetSectionData(ai.path, ".upd_info") +// if err != nil { +// return "", err +// } +// ui := strings.TrimSpace(string(bytes.Trim(aibytes, "\x00"))) +// return ui, nil +// } //ModTime is the time the AppImage was edited/created. If the AppImage is type 2, //it will try to get that information from the squashfs, if not, it returns the file's ModTime. diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index 707a16d5..1d91c19c 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -41,6 +41,7 @@ type archiveReader interface { func (ai *AppImage) populateReader() (err error) { if ai.imageType == 1 { ai.reader, err = newType1Reader(ai.path) + return err } else if ai.imageType == 2 { ai.reader, err = newType2Reader(ai) return err @@ -212,18 +213,24 @@ func newType1Reader(filepath string) (*type1Reader, error) { for folds := range rdr.structure { sort.Strings(rdr.structure[folds]) } - return nil, nil + return &rdr, nil } -func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { - //I need to make sure that, if there is wildcards, that we only get ONE file. If we get more then one, ALL matching files will be in Stdout. - //Probably a bit spagetti code... +//makes sure taht the path is nice and only points to ONE file, which is needed if there are wildcards. +//If you were to search for *.desktop, you will get both blender.desktop & /usr/bin/blender.desktop. +// +//Probably a bit spagetti and can be cleaned up. Maybe add a rawPaths variable to type1reader to make +//it easier to find a match with wildcards. +func (r *type1Reader) cleanPath(filepath string) (string, error) { filepath = strings.TrimPrefix(filepath, "/") filepath = path.Clean(filepath) - var filepathDir string - if strings.Contains(filepath, "/") { + if filepath == "" { + return "", nil + } + filepathDir := path.Dir(filepath) + if filepathDir != "." { for _, dir := range r.folders { - match, _ := path.Match(dir, path.Dir(filepath)) + match, _ := path.Match(filepathDir, dir) if match { filepathDir = dir break @@ -233,29 +240,36 @@ func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { filepathDir = "/" } if filepathDir == "" { - return nil, errors.New("File not found in the archive") + return "", errors.New("File not found in the archive") } - var filepathName string + filepathName := path.Base(filepath) for _, fil := range r.structure[filepathDir] { - match, _ := path.Match(fil, path.Base(filepath)) + match, _ := path.Match(filepathName, fil) if match { filepathName = fil break } } if filepathName == "" { - return nil, errors.New("File not found in the archive") + return "", errors.New("File not found in the archive") } if filepathDir == "/" { filepath = filepathName } else { filepath = filepathDir + "/" + filepathName } - //We're finally sure we have just ONE file, lol + return filepath, nil +} + +func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { + filepath, err := r.cleanPath(filepath) + if err != nil { + return nil, err + } cmd := exec.Command("bsdtar", "-f", r.path, "-xO", filepath) var out bytes.Buffer cmd.Stdout = &out - err := cmd.Run() + err = cmd.Run() if err != nil { return nil, err } @@ -263,15 +277,18 @@ func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { } func (r *type1Reader) IsDir(filepath string) bool { - filepath = strings.TrimPrefix(filepath, "/") - filepath = path.Clean(filepath) - if filepath == "" { - return true //this means they're asking if root is a dir..... + filepath, err := r.cleanPath(filepath) + if err != nil { + return false } return r.structure[filepath] != nil } func (r *type1Reader) SymlinkPath(filepath string) string { + filepath, err := r.cleanPath(filepath) + if err != nil { + return filepath + } cmd := exec.Command("bsdtar", "-f", r.path, "-tv", filepath) wrt, err := runCommand(cmd) if err != nil { @@ -286,6 +303,10 @@ func (r *type1Reader) SymlinkPath(filepath string) string { } func (r *type1Reader) SymlinkPathRecursive(filepath string) string { + filepath, err := r.cleanPath(filepath) + if err != nil { + return filepath + } cmd := exec.Command("bsdtar", "-f", r.path, "-tv", filepath) wrt, err := runCommand(cmd) if err != nil { @@ -306,8 +327,10 @@ func (r *type1Reader) SymlinkPathRecursive(filepath string) string { } func (r *type1Reader) Contains(filepath string) bool { - filepath = strings.TrimPrefix(filepath, "/") - filepath = path.Clean(filepath) + filepath, err := r.cleanPath(filepath) + if err != nil { + return false + } dir := path.Dir(filepath) name := path.Base(filepath) if dir == "" { @@ -316,21 +339,29 @@ func (r *type1Reader) Contains(filepath string) bool { return sort.SearchStrings(r.structure[dir], name) != len(r.structure[dir]) } func (r *type1Reader) ListFiles(filepath string) []string { - filepath = strings.TrimPrefix(filepath, "/") - filepath = path.Clean(filepath) + filepath, err := r.cleanPath(filepath) + if err != nil { + return nil + } if filepath == "" { return r.structure["/"] } - return r.structure[filepath] + if r.IsDir(filepath) { + return r.structure[filepath] + } + return nil } func (r *type1Reader) ExtractTo(filepath, destination string, resolveSymlinks bool) error { - err := os.MkdirAll(destination, os.ModePerm) + filepath, err := r.cleanPath(filepath) + if err != nil { + return err + } + err = os.MkdirAll(destination, os.ModePerm) if err != nil { return err } name := path.Base(filepath) - filepath = strings.TrimPrefix(filepath, "/") destination = strings.TrimSuffix(destination, "/") tmpDir := destination + "/" + ".temp" err = os.Mkdir(tmpDir, os.ModePerm) From 7c9187c4a48f1cef29bc3704b8fa91a004372602 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Tue, 22 Dec 2020 06:56:53 -0600 Subject: [PATCH 13/23] Initial integration of goappimage into appimaged --- src/appimaged/appimage.go | 272 ++++++-------------------------- src/appimaged/appimaged.go | 19 +-- src/appimaged/appwrapper.go | 16 +- src/appimaged/dbus.go | 24 +-- src/appimaged/desktop.go | 76 +++------ src/appimaged/mqtt.go | 11 +- src/appimaged/notification.go | 10 +- src/appimaged/prerequisites.go | 9 +- src/appimaged/thumbnail.go | 133 ++++------------ src/goappimage/appimage.go | 23 +-- src/goappimage/archivereader.go | 7 +- 11 files changed, 157 insertions(+), 443 deletions(-) diff --git a/src/appimaged/appimage.go b/src/appimaged/appimage.go index d816234f..4d662bde 100644 --- a/src/appimaged/appimage.go +++ b/src/appimaged/appimage.go @@ -15,34 +15,28 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "strings" "time" "github.com/adrg/xdg" "github.com/probonopd/go-appimage/internal/helpers" + "github.com/probonopd/go-appimage/src/goappimage" "go.lsp.dev/uri" ) -import "github.com/CalebQ42/squashfs" - -// Handles AppImage files. -// Currently it is using using a static build of mksquashfs/unsquashfs -// but eventually may be rewritten to do things natively in Go +// AppImage Handles AppImage files. type AppImage struct { - path string - imagetype int + *goappimage.AppImage uri string md5 string desktopfilename string desktopfilepath string thumbnailfilename string thumbnailfilepath string - offset int64 - rawcontents string updateinformation string - niceName string - reader *squashfs.Reader + // offset int64 + // rawcontents string + // niceName string } // NewAppImage creates an AppImage object from the location defined by path. @@ -50,104 +44,25 @@ type AppImage struct { // because the AppImage that used to be there may need to be removed // and for this the functions of an AppImage are needed. // Non-existing and invalid AppImages will have type -1. -func NewAppImage(path string) AppImage { - - ai := AppImage{path: path, imagetype: 0} - - // If we got a temp file, exit immediately - // E.g., ignore typical Internet browser temporary files used during download - if strings.HasSuffix(path, ".temp") || - strings.HasSuffix(path, "~") || - strings.HasSuffix(path, ".part") || - strings.HasSuffix(path, ".partial") || - strings.HasSuffix(path, ".zs-old") || - strings.HasSuffix(path, ".crdownload") { - ai.imagetype = -1 - return ai +func NewAppImage(path string) (ai *AppImage, err error) { + ai = new(AppImage) + ai.AppImage, err = goappimage.NewAppImage(path) + if err != nil { + return nil, err } - ai.uri = strings.TrimSpace(string(uri.File(filepath.Clean(ai.path)))) + + ai.uri = strings.TrimSpace(string(uri.File(filepath.Clean(ai.Path)))) ai.md5 = ai.calculateMD5filenamepart() // Need this also for non-existing AppImages for removal ai.desktopfilename = "appimagekit_" + ai.md5 + ".desktop" ai.desktopfilepath = xdg.DataHome + "/applications/" + "appimagekit_" + ai.md5 + ".desktop" ai.thumbnailfilename = ai.md5 + ".png" ai.thumbnailfilepath = ThumbnailsDirNormal + "/" + ai.thumbnailfilename - ai.imagetype = ai.determineImageType() - // Don't waste more time if the file is not actually an AppImage - if ai.imagetype < 0 { - return ai - } - ai.niceName = ai.calculateNiceName() - if ai.imagetype < 1 { - return ai - } - if ai.imagetype > 1 { - ai.offset = helpers.CalculateElfSize(ai.path) - } - if ai.imagetype == 2 { - //Run this in an inline func so we can handle errors more elegently. If there's a problem with the library, the unsquashfs tool will probably still work. - ai.reader = func() *squashfs.Reader { - aiFil, err := os.Open(path) - if err != nil { - return nil - } - stat, err := aiFil.Stat() - if err != nil { - return nil - } - secReader := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) - reader, err := squashfs.NewSquashfsReader(secReader) - if err != nil { - return nil - } - return reader - }() - } ui, err := ai.ReadUpdateInformation() if err == nil && ui != "" { ai.updateinformation = ui } - // ai.discoverContents() // Only do when really needed since this is slow - // log.Println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX rawcontents:", ai.rawcontents) - // Besides, for whatever reason it is not working properly yet - return ai -} - -// Fills rawcontents with the raw output of our extraction tools, -// libarchive and unsquashfs. This is a slow operation and should hence only be done -// once we are sure that we really need this information. -// Maybe we should consider to have a fixed directory inside the AppDir -// for everything that should be extracted, or a MANIFEST file. That would save -// us this slow work at runtime -func (ai AppImage) discoverContents() { - // Let's get the listing of files inside the AppImage. We can work on this later on - // to resolve symlinks, and to determine which files to extract in addition to the desktop file and icon - cmd := exec.Command("") - if ai.imagetype == 1 { - cmd = exec.Command("bsdtar", "-t", ai.path) - } else if ai.imagetype == 2 { - if ai.reader != nil { - files, err := ai.reader.GetAllFiles() - //this will allow it to fallback to using unsquashfs if there is problems - if err == nil { - out := make([]string, 0) - for _, file := range files { - out = append(out, file.Path()) - } - ai.rawcontents = strings.Join(out, "\n") - return - } - } - cmd = exec.Command("unsquashfs", "-f", "-n", "-ll", "-o", strconv.FormatInt(ai.offset, 10), "-d ''", ai.path) - } - if *verbosePtr == true { - log.Printf("cmd: %q\n", cmd.String()) - } - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - helpers.LogError("appimage: list files:", err) - ai.rawcontents = out.String() + return ai, nil } func (ai AppImage) calculateMD5filenamepart() string { @@ -156,19 +71,19 @@ func (ai AppImage) calculateMD5filenamepart() string { return hex.EncodeToString(hasher.Sum(nil)) } -func (ai AppImage) calculateNiceName() string { - niceName := filepath.Base(ai.path) - niceName = strings.Replace(niceName, ".AppImage", "", -1) - niceName = strings.Replace(niceName, ".appimage", "", -1) - niceName = strings.Replace(niceName, "-x86_64", "", -1) - niceName = strings.Replace(niceName, "-i386", "", -1) - niceName = strings.Replace(niceName, "-i686", "", -1) - niceName = strings.Replace(niceName, "-", " ", -1) - niceName = strings.Replace(niceName, "_", " ", -1) - return niceName -} - -func runCommand(cmd *exec.Cmd) (io.Writer, error) { +// func (ai AppImage) calculateNiceName() string { +// niceName := filepath.Base(ai.Path()) +// niceName = strings.Replace(niceName, ".AppImage", "", -1) +// niceName = strings.Replace(niceName, ".appimage", "", -1) +// niceName = strings.Replace(niceName, "-x86_64", "", -1) +// niceName = strings.Replace(niceName, "-i386", "", -1) +// niceName = strings.Replace(niceName, "-i686", "", -1) +// niceName = strings.Replace(niceName, "-", " ", -1) +// niceName = strings.Replace(niceName, "_", " ", -1) +// return niceName +// } + +func runCommand(cmd *exec.Cmd) (bytes.Buffer, error) { if *verbosePtr == true { log.Printf("runCommand: %q\n", cmd) } @@ -177,57 +92,15 @@ func runCommand(cmd *exec.Cmd) (io.Writer, error) { err := cmd.Run() // printError("runCommand", err) // log.Println(cmd.Stdout) - return cmd.Stdout, err -} - -// Check whether we have an AppImage at all. -// Return image type, or -1 if it is not an AppImage -func (ai AppImage) determineImageType() int { - // log.Println("appimage: ", ai.path) - f, err := os.Open(ai.path) - - // printError("appimage", err) - if err != nil { - return -1 // If we were not able to open the file, then we report that it is not an AppImage - } - - info, err := os.Stat(ai.path) - if err != nil { - return -1 - } - - // Directories cannot be AppImages, so return fast - if info.IsDir() { - return -1 - } - - // Very small files cannot be AppImages, so return fast - if info.Size() < 100*1024 { - return -1 - } - - if helpers.CheckMagicAtOffset(f, "414902", 8) == true { - return 2 - } - - if helpers.CheckMagicAtOffset(f, "414901", 8) == true { - return 1 - } - - // ISO9660 files that are also ELF files - if helpers.CheckMagicAtOffset(f, "7f454c", 0) == true && helpers.CheckMagicAtOffset(f, "4344303031", 32769) == true { - return 1 - } - - return -1 + return out, err } func (ai AppImage) setExecBit() { - err := os.Chmod(ai.path, 0755) + err := os.Chmod(ai.Path, 0755) if err == nil { if *verbosePtr == true { - log.Println("appimage: Set executable bit on", ai.path) + log.Println("appimage: Set executable bit on", ai.Path) } } // printError("appimage", err) // Do not print error since AppImages on read-only media are common @@ -237,11 +110,11 @@ func (ai AppImage) setExecBit() { // TODO: Add more checks and reuse this in appimagetool func (ai AppImage) Validate() error { if *verbosePtr == true { - log.Println("Validating AppImage", ai.path) + log.Println("Validating AppImage", ai.Path) } // Check validity of the updateinformation in this AppImage, if it contains some if ai.updateinformation != "" { - log.Println("Validating updateinformation in", ai.path) + log.Println("Validating updateinformation in", ai.Path) err := helpers.ValidateUpdateInformation(ai.updateinformation) if err != nil { helpers.PrintError("appimage: updateinformation verification", err) @@ -259,17 +132,11 @@ func (ai AppImage) _integrate() { // log.Println("integrate called on:", ai.path) // Return immediately if the filename extension is not .AppImage or .app - if (strings.HasSuffix(ai.path, ".AppImage") != true) && (strings.HasSuffix(ai.path, ".app") != true) { + if (strings.HasSuffix(ai.Path, ".AppImage") != true) && (strings.HasSuffix(ai.Path, ".app") != true) { // log.Println("No .AppImage suffix:", ai.path) return } - // Return immediately if this is not an AppImage - if ai.imagetype < 0 { - // log.Println("Not an AppImage:", ai.path) - return - } - ai.setExecBit() // For performance reasons, we stop working immediately @@ -278,7 +145,7 @@ func (ai AppImage) _integrate() { // Compare mtime of desktop file and AppImage, similar to // https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#MODIFICATIONS if desktopFileInfo, err := os.Stat(ai.desktopfilepath); err == nil { - if appImageInfo, err := os.Stat(ai.path); err == nil { + if appImageInfo, err := os.Stat(ai.Path); err == nil { diff := desktopFileInfo.ModTime().Sub(appImageInfo.ModTime()) if diff > (time.Duration(0) * time.Second) { // Do nothing if the desktop file is already newer than the AppImage file @@ -316,7 +183,7 @@ func (ai AppImage) _integrate() { // Compare mtime of thumbnail file and AppImage, similar to // https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#MODIFICATIONS if thumbnailFileInfo, err := os.Stat(ai.thumbnailfilepath); err == nil { - if appImageInfo, err := os.Stat(ai.path); err == nil { + if appImageInfo, err := os.Stat(ai.Path); err == nil { diff := thumbnailFileInfo.ModTime().Sub(appImageInfo.ModTime()) if diff > (time.Duration(0) * time.Second) { // Do nothing if the thumbnail file is already newer than the AppImage file @@ -332,7 +199,7 @@ func (ai AppImage) _integrate() { // Do not call this directly. Instead, call IntegrateOrUnintegrate func (ai AppImage) _removeIntegration() { - log.Println("appimage: Remove integration", ai.path) + log.Println("appimage: Remove integration", ai.Path) err := os.Remove(ai.thumbnailfilepath) if err == nil { log.Println("appimage: Deleted", ai.thumbnailfilepath) @@ -348,7 +215,7 @@ func (ai AppImage) _removeIntegration() { err = os.Remove(ai.desktopfilepath) if err == nil { log.Println("appimage: Deleted", ai.desktopfilepath) - sendDesktopNotification("Removed", ai.path, 3000) + sendDesktopNotification("Removed", ai.Path, 3000) } } @@ -359,7 +226,7 @@ func (ai AppImage) _removeIntegration() { // ONLY have this called from a function that limits parallelism and ensures // uniqueness of the AppImages to be processed func (ai AppImage) IntegrateOrUnintegrate() { - if _, err := os.Stat(ai.path); os.IsNotExist(err) { + if _, err := os.Stat(ai.Path); os.IsNotExist(err) { ai._removeIntegration() } else { ai._integrate() @@ -373,42 +240,10 @@ func ioReader(file string) io.ReaderAt { return r } -// ExtractFile extracts a file from from filepath (which may contain * wildcards) -// in an AppImage to the destinationdirpath. -// Returns err in case of errors, or nil. -// TODO: resolve symlinks -// TODO: Should this be a io.Reader()? -func (ai AppImage) ExtractFile(filepath string, destinationdirpath string) error { - var err error - if ai.imagetype == 1 { - err = os.MkdirAll(destinationdirpath, os.ModePerm) - cmd := exec.Command("bsdtar", "-C", destinationdirpath, "-xf", ai.path, filepath) - _, err = runCommand(cmd) - return err - } else if ai.imagetype == 2 { - if ai.reader != nil { - file := ai.reader.GetFileAtPath(filepath) - if file != nil { //so we can fall back to command based extraction. - errs := file.ExtractTo(destinationdirpath) - if len(errs) != 0 { - //just return the first error - return errs[0] - } - return nil - } - } - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.offset, 10), "-d", destinationdirpath, ai.path, filepath) - _, err = runCommand(cmd) - return err - } - // FIXME: What we may have extracted may well be (until here) broken symlinks... we need to do better than that - return nil -} - // ReadUpdateInformation reads updateinformation from an AppImage // Returns updateinformation string and error func (ai AppImage) ReadUpdateInformation() (string, error) { - aibytes, err := helpers.GetSectionData(ai.path, ".upd_info") + aibytes, err := helpers.GetSectionData(ai.Path, ".upd_info") ui := strings.TrimSpace(string(bytes.Trim(aibytes, "\x00"))) if err != nil { return "", err @@ -468,7 +303,10 @@ func FindAppImagesWithMatchingUpdateInformation(updateinformation string) []stri log.Println(dst, "does not exist, it is mentioned in", xdg.DataHome+"/applications/"+file.Name()) continue } - ai := NewAppImage(dst) + ai, err := NewAppImage(dst) + if err != nil { + continue + } ui, err := ai.ReadUpdateInformation() if err == nil && ui != "" { //log.Println("updateinformation:", ui) @@ -476,7 +314,7 @@ func FindAppImagesWithMatchingUpdateInformation(updateinformation string) []stri unescapedui, _ := url.QueryUnescape(ui) // log.Println("updateinformation:", unescapedui) if updateinformation == unescapedui { - results = append(results, ai.path) + results = append(results, ai.Path) } } @@ -485,25 +323,3 @@ func FindAppImagesWithMatchingUpdateInformation(updateinformation string) []stri } return results } - -// getFSTime reads FSTime from the AppImage. We are doing this only when it is needed, -// not when an NewAppImage is called -func (ai AppImage) getFSTime() time.Time { - if ai.imagetype == 2 { - result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.offset, 10), ai.path).Output() - resstr := strings.TrimSpace(string(bytes.TrimSpace(result))) - if err != nil { - helpers.PrintError("appimage: getFSTime: "+ai.path, err) - return time.Unix(0, 0) - } - if n, err := strconv.Atoi(resstr); err == nil { - return time.Unix(int64(n), 0) - } else { - log.Println("appimage: getFSTime:", resstr, "is not an integer.") - return time.Unix(0, 0) - } - } else { - log.Println("TODO: Implement getFSTime for type-1 AppImages") - return time.Unix(0, 0) - } -} diff --git a/src/appimaged/appimaged.go b/src/appimaged/appimaged.go index 73f0b10f..7893624b 100644 --- a/src/appimaged/appimaged.go +++ b/src/appimaged/appimaged.go @@ -87,8 +87,7 @@ var candidateDirectories = []string{ } func main() { - - thisai.path = helpers.Args0() + thisai.Path = helpers.Args0() // As quickly as possible go there if we are invoked from the command line with a command takeCareOfCommandlineCommands() @@ -288,13 +287,16 @@ func moveDesktopFiles() { var sem = make(chan int, 1024) for _, path := range ToBeIntegratedOrUnintegrated { - ai := NewAppImage(path) + ai, err := NewAppImage(path) + if err != nil { + continue + } sem <- 1 wg.Add(1) go func() { defer wg.Done() ai.IntegrateOrUnintegrate() - ToBeIntegratedOrUnintegrated = RemoveFromSlice(ToBeIntegratedOrUnintegrated, ai.path) + ToBeIntegratedOrUnintegrated = RemoveFromSlice(ToBeIntegratedOrUnintegrated, ai.Path) }() <-sem } @@ -451,12 +453,11 @@ func watchDirectoriesReally(watchedDirectories []string) { } else if info.IsDir() == true { // go inotifyWatch(v + "/" + info.Name()) } else if info.IsDir() == false { - ai := NewAppImage(v + "/" + info.Name()) - if ai.imagetype > 0 { - // We must not process too many in parallel here either, so instead of starting a routine - // here we just put it into ToBeIntegratedOrUnintegrated and let the main timer function take care of it - ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.path) + ai, err := NewAppImage(v + "/" + info.Name()) + if err != nil { + continue } + ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.Path) } } helpers.LogError("main: watchDirectoriesReally", err) diff --git a/src/appimaged/appwrapper.go b/src/appimaged/appwrapper.go index 02d490b1..0db1d9bf 100644 --- a/src/appimaged/appwrapper.go +++ b/src/appimaged/appwrapper.go @@ -35,13 +35,13 @@ func appwrap() { // and check them with desktop-file-verify; display notification if verification fails go checkDesktopFiles(os.Args[2]) - ai := NewAppImage(os.Args[2]) + ai, err := NewAppImage(os.Args[2]) - if ai.imagetype > 0 { + if err == nil { // TODO: If we have an AppImage, then check the updateinformation inside the AppImage (or better: lint the AppImage) err := ai.Validate() if err != nil { - sendDesktopNotification(ai.niceName+" is not a proper AppImage", err.Error()+"\nPlease ask the author to fix it.", 30000) + sendDesktopNotification(ai.Name+" is not a proper AppImage", err.Error()+"\nPlease ask the author to fix it.", 30000) } // TODO: If we have an AppImage, then check the desktop file inside the AppImage (or better: lint the AppDir, reuse code from appimagetool) // TODO: If we have an AppImage, then check that the .DirIcon inside the AppImage exists (or better: lint the AppDir, reuse code from appimagetool) @@ -61,9 +61,9 @@ func appwrap() { // If what we launched (and failed) was an AppImage, then use its nice (short) name // to display the error message var appname string - ai := NewAppImage(os.Args[2]) - if ai.imagetype > 0 { - appname = ai.niceName + ai, err := NewAppImage(os.Args[2]) + if err == nil { + appname = ai.Name } else { appname = filepath.Base(os.Args[2]) } @@ -79,12 +79,12 @@ func appwrap() { } // https://github.com/AppImage/AppImageKit/issues/1004 - if strings.Contains(out.String(), "execv error") == true && ai.imagetype > 0 { + if strings.Contains(out.String(), "execv error") == true && err == nil { body = filepath.Base(os.Args[2]) + " is defective, AppRun is missing. \nPlease ask the author to fix it." } // https://github.com/pinnaculum/galacteek/issues/6 - if strings.Contains(out.String(), "Could not load the Qt platform plugin") == true && ai.imagetype > 0 { + if strings.Contains(out.String(), "Could not load the Qt platform plugin") == true && err == nil { body = filepath.Base(os.Args[2]) + " is defective, could not load the Qt platform plugin. \nPlease run on the command line with 'QT_DEBUG_PLUGINS=1' \nto see error messages and ask the author to fix it." } diff --git a/src/appimaged/dbus.go b/src/appimaged/dbus.go index d7f3ac48..0021acdb 100644 --- a/src/appimaged/dbus.go +++ b/src/appimaged/dbus.go @@ -124,8 +124,8 @@ func monitorDbusSessionBus() { if strings.HasPrefix(str, "%!s") == false { log.Println("org.gtk.vfs.Metadata", str) // time.Sleep(1 * time.Second) - ai := NewAppImage(str) - ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.path) + ai, _ := NewAppImage(str) + ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.Path) } } @@ -142,8 +142,8 @@ func monitorDbusSessionBus() { if v.Headers[3].String() == "\"ResourceScoreUpdated\"" { fp := v.Body[2].(string) log.Println("monitor: ResourceScoreUpdated: ", fp) - ai := NewAppImage(fp) - ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.path) + ai, _ := NewAppImage(fp) + ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.Path) } // KDE @@ -186,14 +186,14 @@ func monitorDbusSessionBus() { for _, s := range fromfiles { fp := getFilepath(s) log.Println("monitor: MoveFrom: ", fp) - ai := NewAppImage(fp) - ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.path) + ai, _ := NewAppImage(fp) + ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.Path) } for _, s := range tofiles { fp := getFilepath(s) log.Println("monitor: MoveTo: ", fp) - ai := NewAppImage(fp) - ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.path) + ai, _ := NewAppImage(fp) + ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.Path) } } } @@ -210,8 +210,8 @@ func monitorDbusSessionBus() { for _, s := range tofiles { fp := getFilepath(s) log.Println("monitor: CopyTo: ", fp) - ai := NewAppImage(fp) - ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.path) + ai, _ := NewAppImage(fp) + ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.Path) } } } @@ -224,8 +224,8 @@ func monitorDbusSessionBus() { for _, s := range files { fp := getFilepath(s) log.Println("monitor: CleanupOrDelete: ", fp) - ai := NewAppImage(fp) - ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.path) + ai, _ := NewAppImage(fp) + ToBeIntegratedOrUnintegrated = helpers.AppendIfMissing(ToBeIntegratedOrUnintegrated, ai.Path) } } } diff --git a/src/appimaged/desktop.go b/src/appimaged/desktop.go index 9c921c7f..dfc455ec 100644 --- a/src/appimaged/desktop.go +++ b/src/appimaged/desktop.go @@ -10,12 +10,10 @@ import ( "log" "os" "path/filepath" - "strings" "time" "golang.org/x/sys/unix" - "github.com/CalebQ42/squashfs" "github.com/adrg/xdg" "github.com/probonopd/go-appimage/internal/helpers" "gopkg.in/ini.v1" @@ -61,52 +59,24 @@ func writeDesktopFile(ai AppImage) { if err != nil { log.Println(err) } - if ai.reader != nil { - desktopFil := ai.reader.GetFileAtPath("*.desktop") - if desktopFil == nil { - //If there isn't a top level .desktop file, try to find one SOMEWHERE. - //TODO: Try to look at some predetermined location first before seaching everywhere. - desktopFil = ai.reader.FindFile(func(fil *squashfs.File) bool { - return strings.HasSuffix(fil.Name(), ".desktop") - }) - } - if desktopFil != nil { - errs := desktopFil.ExtractSymlink(desktopcachedir) - if len(errs) == 0 { - err = os.Rename(desktopcachedir+desktopFil.Name(), desktopcachedir+filename) - if err == nil { - startingPoint = true - } - } - } + if ai.Desktop != nil { + startingPoint = true + cfg = ai.Desktop } if !startingPoint { cfg = ini.Empty() cfg.Section("Desktop Entry").Key("Type").SetValue("Application") - cfg.Section("Desktop Entry").Key("Name").SetValue(ai.niceName) + cfg.Section("Desktop Entry").Key("Name").SetValue(ai.Name) thumbnail := ThumbnailsDirNormal + ai.md5 + ".png" cfg.Section("Desktop Entry").Key("Icon").SetValue(thumbnail) // Construct the Name entry based on the actual filename // so that renaming the file in the file manager results in a changed name in the menu // FIXME: If the thumbnail is not generated here but by another external thumbnailer, it may not be fast enough time.Sleep(1 * time.Second) - } else { - cfg, err = ini.Load(desktopcachedir + filename) - if err != nil { - cfg = ini.Empty() - cfg.Section("Desktop Entry").Key("Type").SetValue("Application") - cfg.Section("Desktop Entry").Key("Name").SetValue(ai.niceName) - thumbnail := ThumbnailsDirNormal + ai.md5 + ".png" - cfg.Section("Desktop Entry").Key("Icon").SetValue(thumbnail) - // Construct the Name entry based on the actual filename - // so that renaming the file in the file manager results in a changed name in the menu - // FIXME: If the thumbnail is not generated here but by another external thumbnailer, it may not be fast enough - time.Sleep(1 * time.Second) - } } - cfg.Section("Desktop Entry").Key("Exec").SetValue(arg0abs + " wrap \"" + ai.path + "\"") // Resolve to a full path - cfg.Section("Desktop Entry").Key(ExecLocationKey).SetValue(ai.path) + cfg.Section("Desktop Entry").Key("Exec").SetValue(arg0abs + " wrap \"" + ai.Path + "\"") // Resolve to a full path + cfg.Section("Desktop Entry").Key(ExecLocationKey).SetValue(ai.Path) cfg.Section("Desktop Entry").Key("TryExec").SetValue(arg0abs) // Resolve to a full path // For icons, use absolute paths. This way icons start working // without having to restart the desktop, and possibly @@ -125,7 +95,7 @@ func writeDesktopFile(ai AppImage) { } */ - cfg.Section("Desktop Entry").Key("Comment").SetValue(ai.path) + cfg.Section("Desktop Entry").Key("Comment").SetValue(ai.Path) cfg.Section("Desktop Entry").Key("X-AppImage-Identifier").SetValue(ai.md5) ui := ai.updateinformation if ui != "" { @@ -135,7 +105,7 @@ func writeDesktopFile(ai AppImage) { var actions []string - if isWritable(ai.path) { + if isWritable(ai.Path) { // Add "Move to Trash" action // if the AppImage is writeable (= the user can remove it) // @@ -151,24 +121,24 @@ func writeDesktopFile(ai AppImage) { cfg.Section("Desktop Action Trash").Key("Name").SetValue("Move to Trash") if helpers.IsCommandAvailable("gio") { // A command line tool to move files to the Trash. However, GNOME-specific - cfg.Section("Desktop Action Trash").Key("Exec").SetValue("gio trash \"" + ai.path + "\"") + cfg.Section("Desktop Action Trash").Key("Exec").SetValue("gio trash \"" + ai.Path + "\"") } else if helpers.IsCommandAvailable("kioclient") { // Of course KDE has its own facility for doing the exact same thing - cfg.Section("Desktop Action Trash").Key("Exec").SetValue("kioclient move \"" + ai.path + "\" trash:/") + cfg.Section("Desktop Action Trash").Key("Exec").SetValue("kioclient move \"" + ai.Path + "\" trash:/") } else { // Provide a fallback shell command to prevent parser errors on other desktops - cfg.Section("Desktop Action Trash").Key("Exec").SetValue("mv \"" + ai.path + "\" ~/.local/share/Trash/") + cfg.Section("Desktop Action Trash").Key("Exec").SetValue("mv \"" + ai.Path + "\" ~/.local/share/Trash/") } // Add OpenPortableHome action actions = append(actions, "OpenPortableHome") cfg.Section("Desktop Action OpenPortableHome").Key("Name").SetValue("Open Portable Home in File Manager") - cfg.Section("Desktop Action OpenPortableHome").Key("Exec").SetValue("xdg-open \"" + ai.path + ".home\"") + cfg.Section("Desktop Action OpenPortableHome").Key("Exec").SetValue("xdg-open \"" + ai.Path + ".home\"") // Add CreatePortableHome action actions = append(actions, "CreatePortableHome") cfg.Section("Desktop Action CreatePortableHome").Key("Name").SetValue("Create Portable Home") - cfg.Section("Desktop Action CreatePortableHome").Key("Exec").SetValue("mkdir -p \"" + ai.path + ".home\"") + cfg.Section("Desktop Action CreatePortableHome").Key("Exec").SetValue("mkdir -p \"" + ai.Path + ".home\"") } @@ -182,13 +152,13 @@ func writeDesktopFile(ai AppImage) { // TODO: Actually, we could do the extraction ourselves since we have the extraction logic on board anyways // then we could have a better name for the extracted location, and could handle type-1 as well // TODO: Maybe have a dbus action for extracting AppImages that could be invoked? - if ai.imagetype > 1 { + if ai.Type() > 1 { actions = append(actions, "Extract") cfg.Section("Desktop Action Extract").Key("Name").SetValue("Extract to AppDir") - if isWritable(ai.path) { - cfg.Section("Desktop Action Extract").Key("Exec").SetValue("bash -c \"cd '" + filepath.Clean(ai.path+"/../") + "' && '" + ai.path + "' --appimage-extract" + " && xdg-open '" + filepath.Clean(ai.path+"/../squashfs-root") + "'\"") + if isWritable(ai.Path) { + cfg.Section("Desktop Action Extract").Key("Exec").SetValue("bash -c \"cd '" + filepath.Clean(ai.Path+"/../") + "' && '" + ai.Path + "' --appimage-extract" + " && xdg-open '" + filepath.Clean(ai.Path+"/../squashfs-root") + "'\"") } else { - cfg.Section("Desktop Action Extract").Key("Exec").SetValue("bash -c \"cd ~ && '" + ai.path + "' --appimage-extract" + " && xdg-open ~/squashfs-root\"") + cfg.Section("Desktop Action Extract").Key("Exec").SetValue("bash -c \"cd ~ && '" + ai.Path + "' --appimage-extract" + " && xdg-open ~/squashfs-root\"") } } @@ -198,14 +168,14 @@ func writeDesktopFile(ai AppImage) { if ai.updateinformation != "" { actions = append(actions, "Update") cfg.Section("Desktop Action Update").Key("Name").SetValue("Update") - cfg.Section("Desktop Action Update").Key("Exec").SetValue(os.Args[0] + " update \"" + ai.path + "\"") + cfg.Section("Desktop Action Update").Key("Exec").SetValue(os.Args[0] + " update \"" + ai.Path + "\"") } // Add "Open Containing Folder" action if helpers.IsCommandAvailable("xdg-open") { actions = append(actions, "Show") cfg.Section("Desktop Action Show").Key("Name").SetValue("Open Containing Folder") - cfg.Section("Desktop Action Show").Key("Exec").SetValue("xdg-open \"" + filepath.Clean(ai.path+"/../") + "\"") + cfg.Section("Desktop Action Show").Key("Exec").SetValue("xdg-open \"" + filepath.Clean(ai.Path+"/../") + "\"") } /* @@ -227,19 +197,19 @@ func writeDesktopFile(ai AppImage) { if helpers.IsCommandAvailable("firejail") { actions = append(actions, "Firejail") cfg.Section("Desktop Action Firejail").Key("Name").SetValue("Run in Firejail") - cfg.Section("Desktop Action Firejail").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --appimage \"" + ai.path + "\"") + cfg.Section("Desktop Action Firejail").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --appimage \"" + ai.Path + "\"") actions = append(actions, "FirejailNoNetwork") cfg.Section("Desktop Action FirejailNoNetwork").Key("Name").SetValue("Run in Firejail Without Network Access") - cfg.Section("Desktop Action FirejailNoNetwork").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --net=none --appimage \"" + ai.path + "\"") + cfg.Section("Desktop Action FirejailNoNetwork").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --net=none --appimage \"" + ai.Path + "\"") actions = append(actions, "FirejailPrivate") cfg.Section("Desktop Action FirejailPrivate").Key("Name").SetValue("Run in Private Firejail Sandbox") - cfg.Section("Desktop Action FirejailPrivate").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --private --appimage \"" + ai.path + "\"") + cfg.Section("Desktop Action FirejailPrivate").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --private --appimage \"" + ai.Path + "\"") actions = append(actions, "FirejailOverlayTmpfs") cfg.Section("Desktop Action FirejailOverlayTmpfs").Key("Name").SetValue("Run in Firejail with Temporary Overlay Filesystem") - cfg.Section("Desktop Action FirejailOverlayTmpfs").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --overlay-tmpfs --appimage \"" + ai.path + "\"") + cfg.Section("Desktop Action FirejailOverlayTmpfs").Key("Exec").SetValue("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --overlay-tmpfs --appimage \"" + ai.Path + "\"") } as := "" diff --git a/src/appimaged/mqtt.go b/src/appimaged/mqtt.go index 17ddce57..e35b5714 100644 --- a/src/appimaged/mqtt.go +++ b/src/appimaged/mqtt.go @@ -53,10 +53,9 @@ func SubscribeMQTT(client mqtt.Client, updateinformation string) { if helpers.SliceContains(subscribedMQTTTopics, updateinformation) == true { // We have already subscribed to this; so nothing to do here return - } else { - // Need to do this immediately here, otherwise it comes too late - subscribedMQTTTopics = helpers.AppendIfMissing(subscribedMQTTTopics, updateinformation) } + // Need to do this immediately here, otherwise it comes too late + subscribedMQTTTopics = helpers.AppendIfMissing(subscribedMQTTTopics, updateinformation) time.Sleep(time.Second * 10) // We get retained messages immediately when we subscribe; // at this point our AppImage may not be integrated yet... // Also it's better user experience not to be bombarded with updates immediately at startup. @@ -107,9 +106,9 @@ func SubscribeMQTT(client mqtt.Client, updateinformation string) { } mostRecent := FindMostRecentAppImageWithMatchingUpdateInformation(unescapedui) - ai := NewAppImage(mostRecent) + ai, _ := NewAppImage(mostRecent) - fstime := ai.getFSTime() + fstime := ai.ModTime() log.Println("mqtt:", updateinformation, "reports version", version, "with FSTime", data.FSTime.Unix(), "- we have", mostRecent, "with FSTime", fstime.Unix()) // FIXME: Only notify if the version is newer than what we already have. @@ -140,7 +139,7 @@ func SubscribeMQTT(client mqtt.Client, updateinformation string) { } } } else { - log.Println("mqtt: Not taking action on", ai.niceName, "because FStime is identical") + log.Println("mqtt: Not taking action on", ai.Name, "because FStime is identical") } } diff --git a/src/appimaged/notification.go b/src/appimaged/notification.go index 86cb1eec..5469e086 100644 --- a/src/appimaged/notification.go +++ b/src/appimaged/notification.go @@ -18,7 +18,7 @@ import ( // sendUpdateDesktopNotification sends a desktop notification for an update. // Use this with "go" prefixed to it so that it runs in the background, because it waits // until the user clicks on "Update" or the timeout occurs -func sendUpdateDesktopNotification(ai AppImage, version string, changelog string) { +func sendUpdateDesktopNotification(ai *AppImage, version string, changelog string) { wg := &sync.WaitGroup{} @@ -47,11 +47,11 @@ func sendUpdateDesktopNotification(ai AppImage, version string, changelog string // Create a Notification to send iconName := "software-update-available" n := notify.Notification{ - AppName: ai.niceName, + AppName: ai.Name, ReplacesID: uint32(0), AppIcon: iconName, Summary: "Update available", - Body: ai.niceName + " can be updated to version " + version + ". \nchangelog", + Body: ai.Name + " can be updated to version " + version + ". \nchangelog", Actions: []string{"update", "Update"}, // tuples of (action_key, label) Hints: map[string]dbus.Variant{}, ExpireTimeout: int32(120000), @@ -89,8 +89,8 @@ func sendUpdateDesktopNotification(ai AppImage, version string, changelog string // Only act on notifications with "our" action ID // https://github.com/esiqveland/notify/issues/8#issuecomment-584881627 if action.ActionKey == "update" && &n == memory[action.ID] { - log.Println("runUpdate", ai.path) - runUpdate(ai.path) + log.Println("runUpdate", ai.Path) + runUpdate(ai.Path) } } wg.Done() diff --git a/src/appimaged/prerequisites.go b/src/appimaged/prerequisites.go index 2054f73a..7592176c 100644 --- a/src/appimaged/prerequisites.go +++ b/src/appimaged/prerequisites.go @@ -55,7 +55,7 @@ func checkPrerequisites() { _, gcEnvIsThere := os.LookupEnv("GOCACHE") if aiEnvIsThere == false { // log.Println(os.Environ()) - log.Println("Running from AppImage type", thisai.imagetype) + log.Println("Running from AppImage type", thisai.Type()) if gcEnvIsThere == false { log.Println("Not running from within an AppImage, exiting") os.Exit(1) @@ -157,9 +157,8 @@ func checkIfSystemdServiceRunning(servicenames []string) bool { if len(units) > 0 { return true - } else { - return false } + return false } @@ -489,14 +488,14 @@ func installServiceFileInHome() { } } - log.Println("thisai.path:", thisai.path) + log.Println("thisai.path:", thisai.Path) d1 := []byte(`[Unit] Description=AppImage system integration daemon After=syslog.target network.target [Service] Type=simple -ExecStart=` + thisai.path + ` +ExecStart=` + thisai.Path + ` LimitNOFILE=65536 diff --git a/src/appimaged/thumbnail.go b/src/appimaged/thumbnail.go index 2fc52925..b740f104 100644 --- a/src/appimaged/thumbnail.go +++ b/src/appimaged/thumbnail.go @@ -4,12 +4,10 @@ import ( "bufio" "image" "image/png" + "io" "io/ioutil" "log" "os" - "os/exec" - "path/filepath" - "strconv" "time" "github.com/adrg/xdg" @@ -18,7 +16,6 @@ import ( pngembed "github.com/sabhiram/png-embed" // For embedding metadata into PNG . "github.com/srwiley/oksvg" // https://github.com/niemeyer/gopkg/issues/72 . "github.com/srwiley/rasterx" - "gopkg.in/ini.v1" ) /* The thumbnail cache directory is prefixed with $XDG_CACHE_DIR/ and the leading dot removed @@ -28,7 +25,7 @@ var ThumbnailsDirNormal = xdg.CacheHome + "/thumbnails/normal/" func (ai AppImage) extractDirIconAsThumbnail() { // log.Println("thumbnail: extract DirIcon as thumbnail") - if ai.imagetype <= 0 { + if ai.Type() <= 0 { return } @@ -50,115 +47,41 @@ func (ai AppImage) extractDirIconAsThumbnail() { // } //this will try to extract the thumbnail, or goes back to command based extraction if it fails. - if ai.reader != nil { - thumbnail := ai.reader.GetFileAtPath(".DirIcon") - if thumbnail == nil { - goto fallback - } - errs := thumbnail.ExtractSymlink(thumbnailcachedir) - if len(errs) > 0 { - goto fallback - } - err := os.Rename(thumbnailcachedir+"/"+thumbnail.Name(), thumbnailcachedir+"/.DirIcon") - if err != nil { - goto fallback - } - thumbnail.Close() - return - } -fallback: - err := ai.ExtractFile(".DirIcon", thumbnailcachedir) - if err != nil { - // Too verbose - // sendErrorDesktopNotification(ai.niceName+" may be defective", "Could not read .DirIcon") - } - // What we have just extracted may well have been a symlink - // hence we try to resolve it - fileInfo, err := ioutil.ReadDir(thumbnailcachedir) - for _, file := range fileInfo { - // log.Println(file.Name()) - originFile, err := os.Readlink(thumbnailcachedir + "/" + file.Name()) - // If we could resolve the symlink, then extract its parent - // and throw the symlink away - if err == nil { - if ai.imagetype == 1 { - log.Println("TODO: Not yet implemented for type-1: We have a symlink, extract the original file") - } else if ai.imagetype == 2 { - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.offset, 10), "-d", thumbnailcachedir, ai.path, originFile) - _, err = runCommand(cmd) - if err != nil { - helpers.LogError("thumbnail", err) - } - } - err = os.RemoveAll(thumbnailcachedir + "/.DirIcon") // Remove the symlink - err = os.Rename(thumbnailcachedir+"/"+originFile, thumbnailcachedir+"/.DirIcon") // Put the real file there instead - helpers.LogError("thumbnail", err) - // TODO: Rinse and repeat: May we still have a symlink at this point? - } - } + err := ai.ExtractFile(".DirIcon", thumbnailcachedir, true) + // if err != nil { + // Too verbose + // sendErrorDesktopNotification(ai.niceName+" may be defective", "Could not read .DirIcon") + // } // Workaround for electron-builder not generating .DirIcon // We may still not have an icon. For example, AppImages made by electron-builder // are lacking .DirIcon files as of Fall 2019; here we have to parse the desktop // file, and try to extract the value of Icon= with the suffix ".png" from the AppImage - if helpers.Exists(thumbnailcachedir+"/.DirIcon") == false && ai.imagetype == 2 { + if helpers.Exists(thumbnailcachedir+"/.DirIcon") == false { if *verbosePtr == true { log.Println(".DirIcon extraction failed. Is it missing? Trying to figure out alternative") } - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.offset, 10), "-d", "/tmp", ai.path, "*.desktop") - _, err = runCommand(cmd) - if err != nil { - helpers.LogError("thumbnail", err) - } - files, _ := ioutil.ReadDir(thumbnailcachedir) - for _, file := range files { - if filepath.Ext(thumbnailcachedir+file.Name()) == ".desktop" { - log.Println("Determine iconname from desktop file:", "/tmp"+"/"+file.Name()) - cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, // Do not cripple lines hat contain ";" - thumbnailcachedir+"/"+file.Name()) - if err == nil { - section, _ := cfg.GetSection("Desktop Entry") - iconkey, _ := section.GetKey("Icon") - iconvalue := iconkey.Value() + ".png" // We are just assuming ".png" here - log.Println("iconname from desktop file:", iconvalue) - helpers.LogError("thumbnail: thumbnailcachedir", err) - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.offset, 10), "-d", thumbnailcachedir, ai.path, iconvalue) - _, err = runCommand(cmd) - if err != nil { - helpers.LogError("thumbnail", err) - } - err = os.Rename(thumbnailcachedir+"/"+iconvalue, thumbnailcachedir+"/.DirIcon") - helpers.LogError("thumbnail", err) - err = os.RemoveAll(thumbnailcachedir + "/" + file.Name()) - helpers.LogError("thumbnail", err) - } - } + var iconName string + if ai.Desktop != nil { + iconTmp, _ := ai.Desktop.Section("Desktop Entry").GetKey("Icon") + iconName = iconTmp.String() } - // Workaround for electron-builder not generating .DirIcon - // Also for the fallback: - // What we have just extracted may well have been a symlink (in the case of electron-builder, it is) - // hence we try to resolve it - fileInfo, err = ioutil.ReadDir(thumbnailcachedir) - for _, file := range fileInfo { - log.Println(file.Name()) - originFile, err := os.Readlink(thumbnailcachedir + "/" + file.Name()) - // If we could resolve the symlink, then extract its parent - // and throw the symlink away + if iconName != "" { + iconName += ".png" + os.Remove(thumbnailcachedir + "/.DirIcon") + fil, err := os.Create(thumbnailcachedir + "/.DirIcon") if err == nil { - if ai.imagetype == 1 { - log.Println("TODO: Not yet implemented for type-1: We have a symlink, extract the original file") - } else if ai.imagetype == 2 { - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.FormatInt(ai.offset, 10), "-d", thumbnailcachedir, ai.path, originFile) - _, err = runCommand(cmd) + rdr, err := ai.ExtractFileReader("iconName") + if err == nil { + _, err = io.Copy(fil, rdr) if err != nil { - helpers.LogError("thumbnail", err) + os.Remove(thumbnailcachedir + "/.DirIcon") } + rdr.Close() + } else { + os.Remove(thumbnailcachedir + "/.DirIcon") } - err = os.RemoveAll(thumbnailcachedir + "/.DirIcon") // Remove the symlink - err = os.Rename(thumbnailcachedir+"/"+originFile, thumbnailcachedir+"/.DirIcon") // Put the real file there instead - helpers.LogError("thumbnail", err) - // TODO: Rinse and repeat: May we still have a symlink at this point? } } } @@ -178,7 +101,7 @@ fallback: log.Printf("Error: %s\n", err) } if issvg.Is(buf) { - log.Println("thumbnail: .DirIcon in", ai.path, "is an SVG, this is discouraged. Costly converting it now") + log.Println("thumbnail: .DirIcon in", ai.Path, "is an SVG, this is discouraged. Costly converting it now") err = convertToPng(thumbnailcachedir + "/.DirIcon") helpers.LogError("thumbnail", err) } @@ -217,11 +140,11 @@ fallback: if *verbosePtr == true { if _, ok := content["Thumb::URI"]; ok { - log.Println("thumbnail: FIXME: Remove pre-existing Thumb::URI in", ai.path) + log.Println("thumbnail: FIXME: Remove pre-existing Thumb::URI in", ai.Path) // log.Println(content["Thumb::URI"]) } if _, ok := content["Thumb::MTime"]; ok { - log.Println("thumbnail: FIXME: Remove pre-existing Thumb::MTime", content["Thumb::MTime"], "in", ai.path) // FIXME; pngembed does not seem to overwrite pre-existing values, is it a bug there? + log.Println("thumbnail: FIXME: Remove pre-existing Thumb::MTime", content["Thumb::MTime"], "in", ai.Path) // FIXME; pngembed does not seem to overwrite pre-existing values, is it a bug there? // log.Println(content["Thumb::MTime"]) } } @@ -236,7 +159,7 @@ fallback: than the thumbnail stored mtime, we won't recognize this modification. If for some reason the thumbnail doesn't have the 'Thumb::MTime' key (although it's required) it should be recreated in any case. */ - if appImageInfo, err := os.Stat(ai.path); err == nil { + if appImageInfo, err := os.Stat(ai.Path); err == nil { _, err := pngembed.EmbedFile(thumbnailcachedir+"/.DirIcon", "Thumb::MTime", appImageInfo.ModTime()) helpers.LogError("thumbnail", err) } @@ -266,7 +189,7 @@ fallback: /* Also set mtime of the thumbnail file to the mtime of the AppImage. Quite possibly this is not needed. TODO: Perhaps we can remove it. See https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#MODIFICATIONS */ - if appImageInfo, err := os.Stat(ai.path); err == nil { + if appImageInfo, err := os.Stat(ai.Path); err == nil { err := os.Chtimes(ai.thumbnailfilepath, time.Now().Local(), appImageInfo.ModTime()) helpers.LogError("thumbnail", err) } diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index 928681d4..0a666df9 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -28,7 +28,7 @@ type AppImage struct { reader archiveReader //Desktop is the AppImage's main .desktop file parsed as an ini.File. Desktop *ini.File - path string + Path string // updateInformation string TODO: add update stuff Name string offset int64 @@ -43,7 +43,7 @@ const execLocationKey = helpers.ExecLocationKey // and for this the functions of an AppImage are needed. // Non-existing and invalid AppImages will have type -1. func NewAppImage(path string) (*AppImage, error) { - ai := AppImage{path: path, imageType: -1} + ai := AppImage{Path: path, imageType: -1} // If we got a temp file, exit immediately // E.g., ignore typical Internet browser temporary files used during download if strings.HasSuffix(path, ".temp") || @@ -60,7 +60,7 @@ func NewAppImage(path string) (*AppImage, error) { return nil, errors.New("Given path is NOT an AppImage") } if ai.imageType > 1 { - ai.offset = helpers.CalculateElfSize(ai.path) + ai.offset = helpers.CalculateElfSize(ai.Path) } err := ai.populateReader() if err == nil { @@ -80,7 +80,7 @@ func NewAppImage(path string) (*AppImage, error) { } func (ai AppImage) calculateNiceName() string { - niceName := filepath.Base(ai.path) + niceName := filepath.Base(ai.Path) niceName = strings.Replace(niceName, ".AppImage", "", -1) niceName = strings.Replace(niceName, ".appimage", "", -1) niceName = strings.Replace(niceName, "-x86_64", "", -1) @@ -95,12 +95,12 @@ func (ai AppImage) calculateNiceName() string { // Return image type, or -1 if it is not an AppImage func (ai AppImage) determineImageType() int { // log.Println("appimage: ", ai.path) - f, err := os.Open(ai.path) + f, err := os.Open(ai.Path) // printError("appimage", err) if err != nil { return -1 // If we were not able to open the file, then we report that it is not an AppImage } - info, err := os.Stat(ai.path) + info, err := os.Stat(ai.Path) if err != nil { return -1 } @@ -125,6 +125,11 @@ func (ai AppImage) determineImageType() int { return -1 } +//Type is the type of the AppImage. Should be either 1 or 2. +func (ai AppImage) Type() int { + return ai.imageType +} + //ExtractFile extracts a file from from filepath (which may contain * wildcards) in an AppImage to the destinationdirpath. // //If resolveSymlinks is true, if the filepath specified is a symlink, the actual file is extracted in it's place. @@ -134,7 +139,7 @@ func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resol return ai.reader.ExtractTo(filepath, destinationdirpath, resolveSymlinks) } if ai.imageType == 2 { - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.Itoa(int(ai.offset)), "-d", destinationdirpath, ai.path, filepath) + cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.Itoa(int(ai.offset)), "-d", destinationdirpath, ai.Path, filepath) _, err := runCommand(cmd) return err } @@ -185,7 +190,7 @@ func (ai AppImage) ModTime() time.Time { if ai.reader != nil { return ai.reader.(*type2Reader).rdr.ModTime() } - result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.offset, 10), ai.path).Output() + result, err := exec.Command("unsquashfs", "-q", "-fstime", "-o", strconv.FormatInt(ai.offset, 10), ai.Path).Output() resstr := strings.TrimSpace(string(bytes.TrimSpace(result))) if err != nil { goto fallback @@ -195,7 +200,7 @@ func (ai AppImage) ModTime() time.Time { } } fallback: - fil, err := os.Open(ai.path) + fil, err := os.Open(ai.Path) if err != nil { return time.Unix(0, 0) } diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index 1d91c19c..539e2d07 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -40,21 +40,22 @@ type archiveReader interface { func (ai *AppImage) populateReader() (err error) { if ai.imageType == 1 { - ai.reader, err = newType1Reader(ai.path) + ai.reader, err = newType1Reader(ai.Path) return err } else if ai.imageType == 2 { ai.reader, err = newType2Reader(ai) return err } - return errors.New("HIII") + return errors.New("Invalid AppImage type") } +//TODO: Implement command based fallback here. type type2Reader struct { rdr *squashfs.Reader } func newType2Reader(ai *AppImage) (*type2Reader, error) { - aiFil, err := os.Open(ai.path) + aiFil, err := os.Open(ai.Path) if err != nil { return nil, err } From 82870eff58ff7b2fb8c22d631c4e0b060b7e1742 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Tue, 22 Dec 2020 07:43:24 -0600 Subject: [PATCH 14/23] Possible fix --- internal/helpers/helpers.go | 5 ++--- src/appimaged/appimage.go | 9 --------- src/appimaged/appimaged.go | 4 ++-- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go index 8b169688..4aa712ba 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -102,7 +102,7 @@ func AddHereToPath() { // FilesWithSuffixInDirectoryRecursive returns the files in a given directory with the given filename extension, and err func FilesWithSuffixInDirectoryRecursive(directory string, extension string) []string { var foundfiles []string - err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(directory, func(path string, info os.FileInfo, _ error) error { if strings.HasSuffix(info.Name(), extension) { foundfiles = append(foundfiles, path) } @@ -158,13 +158,12 @@ func CheckIfFileExists(filepath string) bool { return true } - // CheckIfFileOrFolderExists checks if a file exists and is not a directory before we // try using it to prevent further errors. // Returns true if it does, false otherwise. func CheckIfFileOrFolderExists(filepath string) bool { _, err := os.Stat(filepath) - if os.IsNotExist(err){ + if os.IsNotExist(err) { return false } return true diff --git a/src/appimaged/appimage.go b/src/appimaged/appimage.go index 4d662bde..7c09f25f 100644 --- a/src/appimaged/appimage.go +++ b/src/appimaged/appimage.go @@ -1,11 +1,9 @@ package main import ( - "C" "bytes" "crypto/md5" "encoding/hex" - "io" "io/ioutil" "net/url" @@ -233,13 +231,6 @@ func (ai AppImage) IntegrateOrUnintegrate() { } } -func ioReader(file string) io.ReaderAt { - r, err := os.Open(file) - defer r.Close() - helpers.LogError("appimage: elf:", err) - return r -} - // ReadUpdateInformation reads updateinformation from an AppImage // Returns updateinformation string and error func (ai AppImage) ReadUpdateInformation() (string, error) { diff --git a/src/appimaged/appimaged.go b/src/appimaged/appimaged.go index 7893624b..6335356c 100644 --- a/src/appimaged/appimaged.go +++ b/src/appimaged/appimaged.go @@ -50,7 +50,7 @@ var noZeroconfPtr = flag.Bool("nz", false, "Do not announce this service on the var ToBeIntegratedOrUnintegrated []string -var thisai AppImage // A reference to myself +var thisai *AppImage // A reference to myself var MQTTclient mqtt.Client @@ -87,7 +87,7 @@ var candidateDirectories = []string{ } func main() { - thisai.Path = helpers.Args0() + thisai, _ = NewAppImage(helpers.Args0()) // As quickly as possible go there if we are invoked from the command line with a command takeCareOfCommandlineCommands() From 887bb9081a54610271347128189e98a325cfbe44 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Tue, 22 Dec 2020 08:07:55 -0600 Subject: [PATCH 15/23] Messing around to get TravisCI to work --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index df7114f3..e92f70e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ cache: directories: - $HOME/.cache/go-build # https://restic.net/blog/2018-09-02/travis-build-cache - $HOME/gopath/pkg/mod # https://restic.net/blog/2018-09-02/travis-build-cache - - $GOPATH/pkg/mod # https://evilmartians.com/chronicles/speeding-up-go-modules-for-docker-and-ci + # - $GOPATH/pkg/mod # https://evilmartians.com/chronicles/speeding-up-go-modules-for-docker-and-ci # $HOME/gopath/src # This is where my code gets downloaded to as well, so maybe not the best solution notifications: @@ -29,7 +29,6 @@ notifications: - "chat.freenode.net#AppImage" on_success: always # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always - on_start: always # options: [always|never|change] default: always template: - "%{repository} build %{build_number}: %{result} %{build_url}" use_notice: true From 8b3ac759fc865eb1094702164e749236a7cb2070 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Tue, 22 Dec 2020 08:29:19 -0600 Subject: [PATCH 16/23] Fixed broken build due to a library --- .travis.yml | 3 ++- go.mod | 2 +- go.sum | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index e92f70e5..df7114f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ cache: directories: - $HOME/.cache/go-build # https://restic.net/blog/2018-09-02/travis-build-cache - $HOME/gopath/pkg/mod # https://restic.net/blog/2018-09-02/travis-build-cache - # - $GOPATH/pkg/mod # https://evilmartians.com/chronicles/speeding-up-go-modules-for-docker-and-ci + - $GOPATH/pkg/mod # https://evilmartians.com/chronicles/speeding-up-go-modules-for-docker-and-ci # $HOME/gopath/src # This is where my code gets downloaded to as well, so maybe not the best solution notifications: @@ -29,6 +29,7 @@ notifications: - "chat.freenode.net#AppImage" on_success: always # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always + on_start: always # options: [always|never|change] default: always template: - "%{repository} build %{build_number}: %{result} %{build_url}" use_notice: true diff --git a/go.mod b/go.mod index ceb9efdf..57de7afe 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/hashicorp/go-version v1.2.1 - github.com/otiai10/copy v1.3.0 + github.com/otiai10/copy v1.1.1 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 github.com/prometheus/procfs v0.2.0 github.com/rjeczalik/notify v0.9.2 diff --git a/go.sum b/go.sum index 6b6d2798..f8bb92c7 100644 --- a/go.sum +++ b/go.sum @@ -89,14 +89,14 @@ github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.3.0 h1:Z0OIFgj8hyI18YVzgPXpp652vv0NggCGWaNKSPN9KU8= -github.com/otiai10/copy v1.3.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= +github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo= +github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= -github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA= github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= From 2af72e5faea327f2661559080867abb4ca48365b Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Tue, 22 Dec 2020 08:42:06 -0600 Subject: [PATCH 17/23] Trying to figure out why travisCI isn't building --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- scripts/build.sh | 2 +- src/appimagetool/appdirtool.go | 21 +++++++++------------ 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 57de7afe..1e65f6ad 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/hashicorp/go-version v1.2.1 - github.com/otiai10/copy v1.1.1 + github.com/otiai10/copy v1.3.0 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 github.com/prometheus/procfs v0.2.0 github.com/rjeczalik/notify v0.9.2 @@ -29,9 +29,9 @@ require ( github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 github.com/urfave/cli/v2 v2.3.0 go.lsp.dev/uri v0.3.0 - golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect - golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e + golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0 gopkg.in/ini.v1 v1.62.0 gopkg.in/src-d/go-git.v4 v4.13.1 ) diff --git a/go.sum b/go.sum index f8bb92c7..19365170 100644 --- a/go.sum +++ b/go.sum @@ -89,14 +89,14 @@ github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo= -github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.3.0 h1:Z0OIFgj8hyI18YVzgPXpp652vv0NggCGWaNKSPN9KU8= +github.com/otiai10/copy v1.3.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= +github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA= github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -155,8 +155,8 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -181,8 +181,8 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0 h1:n+DPcgTwkgWzIFpLmoimYR2K2b0Ga5+Os4kayIN0vGo= +golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/scripts/build.sh b/scripts/build.sh index 4e8b0f8e..f5091839 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -35,7 +35,7 @@ fi # Get pinned version of Go directly from upstream if [ "aarch64" == "$TRAVIS_ARCH" ] ; then export ARCH=arm64 ; fi if [ "amd64" == "$TRAVIS_ARCH" ] ; then export ARCH=amd64 ; fi -wget -c -nv https://dl.google.com/go/go1.13.4.linux-$ARCH.tar.gz +wget -c -nv https://dl.google.com/go/go1.13.15.linux-$ARCH.tar.gz sudo tar -C /usr/local -xzf go*.tar.gz export PATH=/usr/local/go/bin:$PATH diff --git a/src/appimagetool/appdirtool.go b/src/appimagetool/appdirtool.go index 93453b40..e3ae248a 100644 --- a/src/appimagetool/appdirtool.go +++ b/src/appimagetool/appdirtool.go @@ -13,16 +13,15 @@ import ( "strconv" "syscall" + "debug/elf" "os" "os/exec" "path/filepath" "strings" -) -import "debug/elf" -import "github.com/probonopd/go-appimage/internal/helpers" -import "github.com/otiai10/copy" -//go:generate go run genexclude.go + "github.com/otiai10/copy" + "github.com/probonopd/go-appimage/internal/helpers" +) type QMLImport struct { Classname string `json:"classname,omitempty"` @@ -166,9 +165,8 @@ var packagesContainingFiles = make(map[string]string) // Need to use 'make', oth /usr/lib64.) If the binary was linked with the -z nodeflib linker option, this step is skipped. */ - type DeployOptions struct { - standalone bool + standalone bool libAppRunHooks bool } @@ -176,7 +174,6 @@ type DeployOptions struct { // which need to be set before the function is called var options DeployOptions - func AppDirDeploy(path string) { appdir, err := helpers.NewAppDir(path) if err != nil { @@ -407,12 +404,12 @@ func deployInterpreter(appdir helpers.AppDir) (string, error) { // if it is not on the exclude list and it is not yet at the target location func deployElf(lib string, appdir helpers.AppDir, err error) { for _, excludePrefix := range ExcludedLibraries { - if strings.HasPrefix(filepath.Base(lib), excludePrefix) == true && ! options.standalone { + if strings.HasPrefix(filepath.Base(lib), excludePrefix) == true && !options.standalone { log.Println("Skipping", lib, "because it is on the excludelist") return } } - + log.Println("Working on", lib, "(TODO: Remove this message)") if strings.HasPrefix(lib, appdir.Path) == false { // Do not copy if it is already in the AppDir libTargetPath := appdir.Path + "/" + lib @@ -796,7 +793,7 @@ func deployGtkDirectory(appdir helpers.AppDir, gtkVersion int) { func appendLib(path string) { for _, excludedlib := range ExcludedLibraries { - if filepath.Base(path) == excludedlib && ! options.standalone { + if filepath.Base(path) == excludedlib && !options.standalone { // log.Println("Skipping", excludedlib, "because it is on the excludelist") return } @@ -1435,7 +1432,7 @@ func checkWhetherPartOfLibc(thisfile string) bool { for _, prefix := range prefixes { if strings.HasPrefix(filepath.Base(thisfile), prefix+"-") || strings.HasPrefix(filepath.Base(thisfile), prefix+".") || strings.HasPrefix(filepath.Base(thisfile), prefix+"_") { - return true + return true } } From 97f71155329320537aea796ce7cc424f3498fdf7 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Tue, 22 Dec 2020 08:48:48 -0600 Subject: [PATCH 18/23] Trying to figure out why TravisCI for otiai10/copy --- scripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index f5091839..401a0225 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -35,7 +35,7 @@ fi # Get pinned version of Go directly from upstream if [ "aarch64" == "$TRAVIS_ARCH" ] ; then export ARCH=arm64 ; fi if [ "amd64" == "$TRAVIS_ARCH" ] ; then export ARCH=amd64 ; fi -wget -c -nv https://dl.google.com/go/go1.13.15.linux-$ARCH.tar.gz +wget -c -nv https://dl.google.com/go/go1.15.6.linux-$ARCH.tar.gz sudo tar -C /usr/local -xzf go*.tar.gz export PATH=/usr/local/go/bin:$PATH From 6ad39b3fe3f5f5b413d8790851ea8fbe2666fd58 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Wed, 23 Dec 2020 05:04:30 -0600 Subject: [PATCH 19/23] Laid the foundation for command fallback for type2 Reverted some changes I made to debug TravisCI failing --- go.mod | 2 +- go.sum | 8 ++-- scripts/build.sh | 2 +- src/goappimage/appimage.go | 25 +++++------ src/goappimage/appimage_test.go | 79 ++++++++++++++++++++++----------- src/goappimage/archivereader.go | 46 +++++++++++++++---- 6 files changed, 108 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 1e65f6ad..9fc401fc 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/hashicorp/go-version v1.2.1 - github.com/otiai10/copy v1.3.0 + github.com/otiai10/copy v1.1.1 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 github.com/prometheus/procfs v0.2.0 github.com/rjeczalik/notify v0.9.2 diff --git a/go.sum b/go.sum index 19365170..9c7fbe24 100644 --- a/go.sum +++ b/go.sum @@ -89,14 +89,14 @@ github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.3.0 h1:Z0OIFgj8hyI18YVzgPXpp652vv0NggCGWaNKSPN9KU8= -github.com/otiai10/copy v1.3.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= +github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo= +github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= -github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA= github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= diff --git a/scripts/build.sh b/scripts/build.sh index 401a0225..4e8b0f8e 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -35,7 +35,7 @@ fi # Get pinned version of Go directly from upstream if [ "aarch64" == "$TRAVIS_ARCH" ] ; then export ARCH=arm64 ; fi if [ "amd64" == "$TRAVIS_ARCH" ] ; then export ARCH=amd64 ; fi -wget -c -nv https://dl.google.com/go/go1.15.6.linux-$ARCH.tar.gz +wget -c -nv https://dl.google.com/go/go1.13.4.linux-$ARCH.tar.gz sudo tar -C /usr/local -xzf go*.tar.gz export PATH=/usr/local/go/bin:$PATH diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index 0a666df9..a4148870 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -38,10 +38,8 @@ type AppImage struct { const execLocationKey = helpers.ExecLocationKey // NewAppImage creates an AppImage object from the location defined by path. -// The AppImage object will also be created if path does not exist, -// because the AppImage that used to be there may need to be removed -// and for this the functions of an AppImage are needed. -// Non-existing and invalid AppImages will have type -1. +// Returns an error if the given path is not an appimage, or is a temporary file. +// In all instances, will still return the AppImage. func NewAppImage(path string) (*AppImage, error) { ai := AppImage{Path: path, imageType: -1} // If we got a temp file, exit immediately @@ -52,25 +50,26 @@ func NewAppImage(path string) (*AppImage, error) { strings.HasSuffix(path, ".partial") || strings.HasSuffix(path, ".zs-old") || strings.HasSuffix(path, ".crdownload") { - return nil, errors.New("Given path is a temporary file") + return &ai, errors.New("Given path is a temporary file") } ai.imageType = ai.determineImageType() // Don't waste more time if the file is not actually an AppImage if ai.imageType < 0 { - return nil, errors.New("Given path is NOT an AppImage") + return &ai, errors.New("Given path is NOT an AppImage") } if ai.imageType > 1 { ai.offset = helpers.CalculateElfSize(ai.Path) } - err := ai.populateReader() + err := ai.populateReader(true, false) + if err != nil { + return &ai, err + } + //try to load up the desktop file for some information. + desktopFil, err := ai.reader.FileReader("*.desktop") if err == nil { - //try to load up the desktop file for some information. - desktopFil, err := ai.reader.FileReader("*.desktop") + ai.Desktop, err = ini.Load(desktopFil) if err == nil { - ai.Desktop, err = ini.Load(desktopFil) - if err == nil { - ai.Name = ai.Desktop.Section("Desktop Entry").Key("Name").Value() - } + ai.Name = ai.Desktop.Section("Desktop Entry").Key("Name").Value() } } if ai.Name == "" { diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go index 0976de0e..3cbc1252 100644 --- a/src/goappimage/appimage_test.go +++ b/src/goappimage/appimage_test.go @@ -2,45 +2,38 @@ package goappimage import ( "fmt" + "io" + "net/http" "os" "testing" ) +//NOTE: If you internet is a bit slow, it's not a bad idea to download it manually instead of letting the test download it +//TODO: Change to a different AppImage since Blender is a bit large... const type1TestURL = "https://bintray.com/probono/AppImages/download_file?file_path=Blender-2.78c.glibc2.17-x86_64.AppImage" const type1TestFilename = "Blender-2.78c.glibc2.17-x86_64.AppImage" +//NOTE: If you internet is a bit slow, it's not a bad idea to download it manually instead of letting the test download it +const type2TestURL = "https://github.com/subsurface/subsurface/releases/download/v4.9.4/Subsurface-4.9.4-x86_64.AppImage" +const type2TestFilename = "Subsurface-4.9.4-x86_64.AppImage" + func TestAppImageType1(t *testing.T) { - wdDir, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - //make sure we have a testing dir - _, err = os.Open(wdDir + "/testing") - if os.IsNotExist(err) { - err = os.Mkdir(wdDir+"/testing", os.ModePerm) - if err != nil { - t.Fatal(err) - } - _, err = os.Open(wdDir + "/testing") - if err != nil { - t.Fatal(err) - } - } else if err != nil { - t.Fatal(err) - } testImg := getAppImage(1, t) ai, err := NewAppImage(testImg) if err != nil { t.Fatal(err) } - fmt.Println("name", ai.Name) - if ai.imageType == -1 { - t.Fatal("Not an appimage") - } - _, err = newType1Reader(testImg) + fmt.Println("Name", ai.Name) + t.Fatal("No Problem") +} + +func TestAppImageType2(t *testing.T) { + testImg := getAppImage(2, t) + ai, err := NewAppImage(testImg) if err != nil { t.Fatal(err) } + fmt.Println("Name", ai.Name) t.Fatal("No Problem") } @@ -49,8 +42,9 @@ func getAppImage(imageType int, t *testing.T) string { var filename string switch imageType { case 1: - // url = type1TestURL filename = type1TestFilename + case 2: + filename = type2TestFilename default: t.Fatal("What are you doing here?") } @@ -58,7 +52,6 @@ func getAppImage(imageType int, t *testing.T) string { if err != nil { t.Fatal(err) } - //make sure we have a testing dir _, err = os.Open(wdDir + "/testing") if os.IsNotExist(err) { err = os.Mkdir(wdDir+"/testing", os.ModePerm) @@ -74,7 +67,7 @@ func getAppImage(imageType int, t *testing.T) string { } _, err = os.Open(wdDir + "/testing/" + filename) if os.IsNotExist(err) { - downloadTestImage(imageType, t) + downloadTestImage(imageType, wdDir+"/testing", t) _, err = os.Open(wdDir + "/testing/" + filename) if err != nil { t.Fatal(err) @@ -85,5 +78,37 @@ func getAppImage(imageType int, t *testing.T) string { return wdDir + "/testing/" + filename } -func downloadTestImage(imageType int, t *testing.T) { +func downloadTestImage(imageType int, dir string, t *testing.T) { + var filename string + var url string + switch imageType { + case 1: + url = type1TestURL + filename = type1TestFilename + case 2: + url = type2TestURL + filename = type2TestFilename + default: + t.Fatal("What are you doing here?") + } + appImage, err := os.Create(dir + "/" + filename) + if err != nil { + t.Fatal(err) + } + defer appImage.Close() + check := http.Client{ + CheckRedirect: func(r *http.Request, _ []*http.Request) error { + r.URL.Opaque = r.URL.Path + return nil + }, + } + resp, err := check.Get(url) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + _, err = io.Copy(appImage, resp.Body) + if err != nil { + t.Fatal(err) + } } diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index 539e2d07..438d11ee 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -38,12 +38,12 @@ type archiveReader interface { ExtractTo(path, destination string, resolveSymlinks bool) error } -func (ai *AppImage) populateReader() (err error) { +func (ai *AppImage) populateReader(allowFallback, forceFallback bool) (err error) { if ai.imageType == 1 { ai.reader, err = newType1Reader(ai.Path) return err } else if ai.imageType == 2 { - ai.reader, err = newType2Reader(ai) + ai.reader, err = newType2Reader(ai, allowFallback, forceFallback) return err } return errors.New("Invalid AppImage type") @@ -51,10 +51,17 @@ func (ai *AppImage) populateReader() (err error) { //TODO: Implement command based fallback here. type type2Reader struct { - rdr *squashfs.Reader + rdr *squashfs.Reader + fallbackAllowed bool + forceFallback bool } -func newType2Reader(ai *AppImage) (*type2Reader, error) { +func newType2Reader(ai *AppImage, fallbackAllowed, forceFallback bool) (*type2Reader, error) { + if forceFallback { + return &type2Reader{ + forceFallback: true, + }, nil + } aiFil, err := os.Open(ai.Path) if err != nil { return nil, err @@ -62,15 +69,29 @@ func newType2Reader(ai *AppImage) (*type2Reader, error) { stat, _ := aiFil.Stat() aiRdr := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) squashRdr, err := squashfs.NewSquashfsReader(aiRdr) - if err != nil { + if err == squashfs.ErrOptions { + //Force fallbackAllowed if there might be incompatible compressor options + return &type2Reader{ + rdr: squashRdr, + fallbackAllowed: true, + }, nil + } else if err != nil { + //If there are other errors, we can always use unsquashfs + if fallbackAllowed { + return &type2Reader{ + forceFallback: true, + }, nil + } return nil, err } return &type2Reader{ - rdr: squashRdr, + rdr: squashRdr, + fallbackAllowed: fallbackAllowed, }, nil } func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { + //TODO: command fallback fil := r.rdr.GetFileAtPath(path) if fil == nil { return nil, errors.New("Can't find file at: " + path) @@ -88,6 +109,7 @@ func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { } func (r *type2Reader) IsDir(path string) bool { + //TODO: command fallback fil := r.rdr.GetFileAtPath(path) if fil == nil { return false @@ -102,6 +124,7 @@ func (r *type2Reader) IsDir(path string) bool { } func (r *type2Reader) SymlinkPath(path string) string { + //TODO: command fallback fil := r.rdr.GetFileAtPath(path) if fil == nil { return path @@ -113,6 +136,7 @@ func (r *type2Reader) SymlinkPath(path string) string { } func (r *type2Reader) SymlinkPathRecursive(path string) string { + //TODO: command fallback fil := r.rdr.GetFileAtPath(path) if fil == nil { return path @@ -127,11 +151,13 @@ func (r *type2Reader) SymlinkPathRecursive(path string) string { } func (r *type2Reader) Contains(path string) bool { + //TODO: command fallback fil := r.rdr.GetFileAtPath(path) return fil != nil } func (r *type2Reader) ListFiles(path string) []string { + //TODO: command fallback fil := r.rdr.GetFileAtPath(path) if fil == nil { return nil @@ -157,6 +183,7 @@ func (r *type2Reader) ListFiles(path string) []string { } func (r *type2Reader) ExtractTo(path, destination string, resolveSymlinks bool) error { + //TODO: command fallback fil := r.rdr.GetFileAtPath(path) if fil == nil { return nil @@ -217,8 +244,9 @@ func newType1Reader(filepath string) (*type1Reader, error) { return &rdr, nil } -//makes sure taht the path is nice and only points to ONE file, which is needed if there are wildcards. -//If you were to search for *.desktop, you will get both blender.desktop & /usr/bin/blender.desktop. +//makes sure that the path is nice and only points to ONE file, which is needed if there are wildcards. +//If you were to search for *.desktop, you will get both blender.desktop AND /usr/bin/blender.desktop. +//This could cause issues, especially for FileReader // //Probably a bit spagetti and can be cleaned up. Maybe add a rawPaths variable to type1reader to make //it easier to find a match with wildcards. @@ -263,6 +291,8 @@ func (r *type1Reader) cleanPath(filepath string) (string, error) { } func (r *type1Reader) FileReader(filepath string) (io.ReadCloser, error) { + //TODO: check size of file and if it's large, extract to a temp directory, read that, and delete it on close. + //This would make sure a huge file isn't completely held in memory via the byte buffer. filepath, err := r.cleanPath(filepath) if err != nil { return nil, err From 8f55bb10bfec392e6b49933b947e282b3a99dfe9 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Wed, 23 Dec 2020 15:10:18 -0600 Subject: [PATCH 20/23] Added some fallback for type 2 Changed to CalebQ42/copy temporarily --- go.mod | 2 +- go.sum | 8 +-- src/appimagetool/appdirtool.go | 2 +- src/goappimage/appimage_test.go | 21 ++++++++ src/goappimage/archivereader.go | 88 +++++++++++++++++++++++++-------- 5 files changed, 95 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 9fc401fc..dab3f182 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/probonopd/go-appimage go 1.13 require ( + github.com/CalebQ42/copy v1.4.1 github.com/CalebQ42/squashfs v0.3.5 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 @@ -17,7 +18,6 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/hashicorp/go-version v1.2.1 - github.com/otiai10/copy v1.1.1 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 github.com/prometheus/procfs v0.2.0 github.com/rjeczalik/notify v0.9.2 diff --git a/go.sum b/go.sum index 9c7fbe24..2d655553 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= +github.com/CalebQ42/copy v1.4.1 h1:ve5gOqZ7nrzQfjktAWvDyqTC98cWr9bvk4b1Sx0zjMA= +github.com/CalebQ42/copy v1.4.1/go.mod h1:LxvtMRiP3tmXL7l7v76EQlCyMTWkGsBfvKUkrT7kNhk= github.com/CalebQ42/squashfs v0.3.5 h1:EniKQIvaGkPIz/6WebIZGFnEV9Viio6cyRPMJs0B+/Y= github.com/CalebQ42/squashfs v0.3.5/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= @@ -89,14 +91,12 @@ github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo= -github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= +github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pierrec/lz4/v4 v4.1.1 h1:cS6aGkNLJr4u+UwaA21yp+gbWN3WJWtKo1axmPDObMA= github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= diff --git a/src/appimagetool/appdirtool.go b/src/appimagetool/appdirtool.go index e3ae248a..17d9ad25 100644 --- a/src/appimagetool/appdirtool.go +++ b/src/appimagetool/appdirtool.go @@ -19,7 +19,7 @@ import ( "path/filepath" "strings" - "github.com/otiai10/copy" + "github.com/CalebQ42/copy" "github.com/probonopd/go-appimage/internal/helpers" ) diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go index 3cbc1252..db3b457a 100644 --- a/src/goappimage/appimage_test.go +++ b/src/goappimage/appimage_test.go @@ -33,10 +33,31 @@ func TestAppImageType2(t *testing.T) { if err != nil { t.Fatal(err) } + getCleanSquashfsFromAppImage(ai, testImg+".sfs", t) fmt.Println("Name", ai.Name) t.Fatal("No Problem") } +//this is so I can easily use unsquashfs & gui tools to double check my work +func getCleanSquashfsFromAppImage(ai *AppImage, name string, t *testing.T) { + fil, err := os.Create(name) + if os.IsExist(err) { + return + } else if err != nil { + t.Fatal(err) + } + aiFil, err := os.Open(ai.Path) + if err != nil { + t.Fatal(err) + } + stat, _ := aiFil.Stat() + rdr := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) + _, err = io.Copy(fil, rdr) + if err != nil { + t.Fatal(err) + } +} + func getAppImage(imageType int, t *testing.T) string { // var url string var filename string diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index 438d11ee..abab8ee8 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -9,6 +9,7 @@ import ( "os/exec" "path" "sort" + "strconv" "strings" "github.com/CalebQ42/squashfs" @@ -52,15 +53,23 @@ func (ai *AppImage) populateReader(allowFallback, forceFallback bool) (err error //TODO: Implement command based fallback here. type type2Reader struct { rdr *squashfs.Reader + structure map[string][]string + path string + folders []string fallbackAllowed bool forceFallback bool } func newType2Reader(ai *AppImage, fallbackAllowed, forceFallback bool) (*type2Reader, error) { if forceFallback { - return &type2Reader{ + out := &type2Reader{ forceFallback: true, - }, nil + } + err := out.setupCommandFallback(ai) + if err != nil { + return nil, err + } + return out, nil } aiFil, err := os.Open(ai.Path) if err != nil { @@ -69,18 +78,17 @@ func newType2Reader(ai *AppImage, fallbackAllowed, forceFallback bool) (*type2Re stat, _ := aiFil.Stat() aiRdr := io.NewSectionReader(aiFil, ai.offset, stat.Size()-ai.offset) squashRdr, err := squashfs.NewSquashfsReader(aiRdr) - if err == squashfs.ErrOptions { - //Force fallbackAllowed if there might be incompatible compressor options - return &type2Reader{ - rdr: squashRdr, - fallbackAllowed: true, - }, nil - } else if err != nil { - //If there are other errors, we can always use unsquashfs + if err != nil { if fallbackAllowed { - return &type2Reader{ + //If there are errors, we force the use of unsquashfs. + out := &type2Reader{ forceFallback: true, - }, nil + } + err := out.setupCommandFallback(ai) + if err != nil { + return nil, err + } + return out, nil } return nil, err } @@ -90,6 +98,40 @@ func newType2Reader(ai *AppImage, fallbackAllowed, forceFallback bool) (*type2Re }, nil } +func (r *type2Reader) setupCommandFallback(ai *AppImage) error { + r.structure = make(map[string][]string) + r.folders = make([]string, 0) + cmd := exec.Command("unsquashfs", "-o", strconv.FormatInt(ai.offset, 10), "-l", ai.Path) + out, err := runCommand(cmd) + if err != nil { + return err + } + allFiles := strings.Split(string(out.Bytes()), "\n") + for _, filepath := range allFiles { + if filepath == "" { + continue + } + filepath = strings.TrimPrefix(filepath, "squashfs-root/") + dir := path.Dir(filepath) + name := path.Base(filepath) + if dir == "." { + dir = "/" + } + if r.structure[dir] == nil { + if dir != "/" { + r.folders = append(r.folders, dir) + } + r.structure[dir] = make([]string, 0) + } + r.structure[dir] = append(r.structure[dir], name) + } + sort.Strings(r.folders) + for dir := range r.structure { + sort.Strings(r.structure[dir]) + } + return nil +} + func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { //TODO: command fallback fil := r.rdr.GetFileAtPath(path) @@ -108,19 +150,25 @@ func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { return fil, nil } -func (r *type2Reader) IsDir(path string) bool { +func (r *type2Reader) IsDir(filepath string) bool { //TODO: command fallback - fil := r.rdr.GetFileAtPath(path) - if fil == nil { - return false - } - if fil.IsSymlink() { - fil = fil.GetSymlinkFileRecursive() + if !r.forceFallback { + fil := r.rdr.GetFileAtPath(filepath) if fil == nil { + //TODO: make squashfs differenciate between a not found file, and compression extraction issues. return false } + if fil.IsSymlink() { + fil = fil.GetSymlinkFileRecursive() + if fil == nil { + return false + } + } + return fil.IsDir() } - return fil.IsDir() + // commandFallback: TODO: when i can differenciate the above errors + filepath = path.Clean(strings.TrimPrefix(filepath, "/")) + return filepath == "." || r.structure[filepath] != nil } func (r *type2Reader) SymlinkPath(path string) string { From 77dadcfea51909a1dc1a10b9274aa61418408bb0 Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Fri, 25 Dec 2020 12:24:23 -0600 Subject: [PATCH 21/23] Some work on type2 command fallback. --- go.sum | 4 +- src/goappimage/appimage_test.go | 6 +- src/goappimage/archivereader.go | 137 ++++++++++++++++++++++++++------ 3 files changed, 120 insertions(+), 27 deletions(-) diff --git a/go.sum b/go.sum index 60c46307..09663f2f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= github.com/CalebQ42/copy v1.4.1 h1:ve5gOqZ7nrzQfjktAWvDyqTC98cWr9bvk4b1Sx0zjMA= github.com/CalebQ42/copy v1.4.1/go.mod h1:LxvtMRiP3tmXL7l7v76EQlCyMTWkGsBfvKUkrT7kNhk= +github.com/CalebQ42/squashfs v0.3.5 h1:EniKQIvaGkPIz/6WebIZGFnEV9Viio6cyRPMJs0B+/Y= github.com/CalebQ42/squashfs v0.3.5/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -65,8 +67,6 @@ github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c h1:fEE5/5VNnYUoBOj github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go index db3b457a..44247b8c 100644 --- a/src/goappimage/appimage_test.go +++ b/src/goappimage/appimage_test.go @@ -34,7 +34,11 @@ func TestAppImageType2(t *testing.T) { t.Fatal(err) } getCleanSquashfsFromAppImage(ai, testImg+".sfs", t) - fmt.Println("Name", ai.Name) + typ2Rdr, err := newType2Reader(ai, true, true) + if err != nil { + t.Fatal(err) + } + typ2Rdr.SymlinkPath("AppRun") t.Fatal("No Problem") } diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index abab8ee8..e238f440 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -56,12 +56,13 @@ type type2Reader struct { structure map[string][]string path string folders []string + offset int fallbackAllowed bool forceFallback bool } func newType2Reader(ai *AppImage, fallbackAllowed, forceFallback bool) (*type2Reader, error) { - if forceFallback { + if forceFallback || ai == nil { out := &type2Reader{ forceFallback: true, } @@ -101,6 +102,8 @@ func newType2Reader(ai *AppImage, fallbackAllowed, forceFallback bool) (*type2Re func (r *type2Reader) setupCommandFallback(ai *AppImage) error { r.structure = make(map[string][]string) r.folders = make([]string, 0) + r.offset = int(ai.offset) + r.path = ai.Path cmd := exec.Command("unsquashfs", "-o", strconv.FormatInt(ai.offset, 10), "-l", ai.Path) out, err := runCommand(cmd) if err != nil { @@ -111,7 +114,10 @@ func (r *type2Reader) setupCommandFallback(ai *AppImage) error { if filepath == "" { continue } - filepath = strings.TrimPrefix(filepath, "squashfs-root/") + filepath = path.Clean(strings.TrimPrefix(filepath, "squashfs-root/")) + if filepath == "." { + continue + } dir := path.Dir(filepath) name := path.Base(filepath) if dir == "." { @@ -132,6 +138,52 @@ func (r *type2Reader) setupCommandFallback(ai *AppImage) error { return nil } +//makes sure that the path is nice and only points to ONE file, which is needed if there are wildcards. +//If you were to search for *.desktop, you will get both blender.desktop AND /usr/bin/blender.desktop. +//This could cause issues, especially for FileReader +// +//Probably a bit spagetti and can be cleaned up. Maybe add a rawPaths variable to type1reader to make +//it easier to find a match with wildcards. +func (r *type2Reader) cleanPath(filepath string) (string, error) { + filepath = strings.TrimPrefix(filepath, "/") + filepath = path.Clean(filepath) + if filepath == "." { + return "/", nil + } + filepathDir := path.Dir(filepath) + if filepathDir != "." { + for _, dir := range r.folders { + match, _ := path.Match(filepathDir, dir) + if match { + filepathDir = dir + break + } + } + } else { + filepathDir = "/" + } + if filepathDir == "" { + return "", errors.New("File not found in the archive") + } + filepathName := path.Base(filepath) + for _, fil := range r.structure[filepathDir] { + match, _ := path.Match(filepathName, fil) + if match { + filepathName = fil + break + } + } + if filepathName == "" { + return "", errors.New("File not found in the archive") + } + if filepathDir == "/" { + filepath = filepathName + } else { + filepath = filepathDir + "/" + filepathName + } + return filepath, nil +} + func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { //TODO: command fallback fil := r.rdr.GetFileAtPath(path) @@ -151,7 +203,6 @@ func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { } func (r *type2Reader) IsDir(filepath string) bool { - //TODO: command fallback if !r.forceFallback { fil := r.rdr.GetFileAtPath(filepath) if fil == nil { @@ -167,35 +218,73 @@ func (r *type2Reader) IsDir(filepath string) bool { return fil.IsDir() } // commandFallback: TODO: when i can differenciate the above errors - filepath = path.Clean(strings.TrimPrefix(filepath, "/")) - return filepath == "." || r.structure[filepath] != nil + filepath, err := r.cleanPath(filepath) + if err != nil { + return false + } + return r.structure[filepath] != nil } -func (r *type2Reader) SymlinkPath(path string) string { - //TODO: command fallback - fil := r.rdr.GetFileAtPath(path) - if fil == nil { - return path +func (r *type2Reader) SymlinkPath(filepath string) string { + if !r.forceFallback { + fil := r.rdr.GetFileAtPath(filepath) + if fil == nil { + return filepath + } + if fil.IsSymlink() { + return fil.SymlinkPath() + } + return filepath } - if fil.IsSymlink() { - return fil.SymlinkPath() + //fallingback to commands. + //TODO: add a way fro the above to fallback down here. + filepath, err := r.cleanPath(filepath) + if err != nil { + return filepath + } + cmd := exec.Command("unsquashfs", "-ll", "-o", strconv.Itoa(r.offset), r.path, filepath) + out, err := runCommand(cmd) + if err != nil { + return filepath + } + tmpOutput := strings.Split(strings.TrimSuffix(string(out.Bytes()), "\n"), "\n") + neededLine := tmpOutput[len(tmpOutput)-1] + if strings.Contains(neededLine, "->") { + neededLine = neededLine[strings.Index(neededLine, "->")+3:] } - return path + return path.Dir(filepath) + "/" + neededLine } -func (r *type2Reader) SymlinkPathRecursive(path string) string { - //TODO: command fallback - fil := r.rdr.GetFileAtPath(path) - if fil == nil { - return path - } - if fil.IsSymlink() { - tmpLoc := r.SymlinkPathRecursive(fil.Path()) - if tmpLoc != fil.Path() { - return tmpLoc +func (r *type2Reader) SymlinkPathRecursive(filepath string) string { + if !r.forceFallback { + fil := r.rdr.GetFileAtPath(filepath) + if fil == nil { + return filepath } + tmp := fil.GetSymlinkFileRecursive() + if tmp == nil { + return filepath + } + return tmp.Path() + } + //Command fallback + //TODO: allow command fallback from above + filepath, err := r.cleanPath(filepath) + if err != nil { + return filepath } - return path + symlinkedFile := r.SymlinkPath(filepath) + if symlinkedFile == filepath { + return filepath + } + if strings.HasPrefix(symlinkedFile, "/") { + return filepath //we can't help with absolute symlinks... + } + tmp := r.SymlinkPathRecursive(path.Dir(filepath) + "/" + symlinkedFile) + if tmp != path.Dir(filepath)+"/"+symlinkedFile { + return tmp + } + return filepath } func (r *type2Reader) Contains(path string) bool { From 5767e505854b65d6236d58533f9266c4811eaa7a Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sat, 26 Dec 2020 05:51:52 -0600 Subject: [PATCH 22/23] Finished command backup for type 2 All file handling is done through archivereader so things can be much cleaner. Cleaned up thumbnail getting Changed back to otiai10/copy --- go.mod | 2 +- go.sum | 4 +- src/appimaged/desktop.go | 23 ++--- src/appimaged/thumbnail.go | 47 +++------ src/appimagetool/appdirtool.go | 2 +- src/goappimage/appimage.go | 54 +++++----- src/goappimage/appimage_test.go | 6 +- src/goappimage/archivereader.go | 178 +++++++++++++++++++++++--------- 8 files changed, 186 insertions(+), 130 deletions(-) diff --git a/go.mod b/go.mod index 30e532e1..d2833dcc 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/probonopd/go-appimage go 1.13 require ( - github.com/CalebQ42/copy v1.4.1 github.com/CalebQ42/squashfs v0.3.5 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 @@ -18,6 +17,7 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/hashicorp/go-version v1.2.0 + github.com/otiai10/copy v1.4.1 github.com/probonopd/go-zsyncmake v0.0.0-20181008012426-5db478ac2be7 github.com/prometheus/procfs v0.2.0 github.com/rjeczalik/notify v0.9.2 diff --git a/go.sum b/go.sum index 09663f2f..698e9d1b 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtIGnaY= github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= -github.com/CalebQ42/copy v1.4.1 h1:ve5gOqZ7nrzQfjktAWvDyqTC98cWr9bvk4b1Sx0zjMA= -github.com/CalebQ42/copy v1.4.1/go.mod h1:LxvtMRiP3tmXL7l7v76EQlCyMTWkGsBfvKUkrT7kNhk= github.com/CalebQ42/squashfs v0.3.5 h1:EniKQIvaGkPIz/6WebIZGFnEV9Viio6cyRPMJs0B+/Y= github.com/CalebQ42/squashfs v0.3.5/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= @@ -91,6 +89,8 @@ github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/otiai10/copy v1.4.1 h1:T1Ggae54qC0G+0VA5B/3CJQorvvaNVStzDn3YLZHdLI= +github.com/otiai10/copy v1.4.1/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= diff --git a/src/appimaged/desktop.go b/src/appimaged/desktop.go index dfc455ec..6c1c8547 100644 --- a/src/appimaged/desktop.go +++ b/src/appimaged/desktop.go @@ -40,15 +40,6 @@ func writeDesktopFile(ai AppImage) { log.Printf("desktop: %v", err) } // log.Println(xdg.RuntimeDir) - - // TODO: Instead of starting with an empty file, start with reading the original one - // cfg, err := ini.Load("my.ini") - // if err != nil { - // fmt.Printf("Fail to read file: %v", err) - // os.Exit(1) - // } - // BLOCKED: To do this in a halfway decent way, we need to improve - // ai.ExtractFile() so that it resolves symlinks! var cfg *ini.File ini.PrettyFormat = false startingPoint := false //An easy way to tell if extracting the desktop file worked. @@ -62,19 +53,19 @@ func writeDesktopFile(ai AppImage) { if ai.Desktop != nil { startingPoint = true cfg = ai.Desktop + //TODO: check if the thumbnail is already present and only extract it and set it's value if it isn't } if !startingPoint { cfg = ini.Empty() cfg.Section("Desktop Entry").Key("Type").SetValue("Application") cfg.Section("Desktop Entry").Key("Name").SetValue(ai.Name) - thumbnail := ThumbnailsDirNormal + ai.md5 + ".png" - cfg.Section("Desktop Entry").Key("Icon").SetValue(thumbnail) - // Construct the Name entry based on the actual filename - // so that renaming the file in the file manager results in a changed name in the menu - // FIXME: If the thumbnail is not generated here but by another external thumbnailer, it may not be fast enough - time.Sleep(1 * time.Second) } - + thumbnail := ThumbnailsDirNormal + ai.md5 + ".png" + cfg.Section("Desktop Entry").Key("Icon").SetValue(thumbnail) + // Construct the Name entry based on the actual filename + // so that renaming the file in the file manager results in a changed name in the menu + // FIXME: If the thumbnail is not generated here but by another external thumbnailer, it may not be fast enough + time.Sleep(1 * time.Second) cfg.Section("Desktop Entry").Key("Exec").SetValue(arg0abs + " wrap \"" + ai.Path + "\"") // Resolve to a full path cfg.Section("Desktop Entry").Key(ExecLocationKey).SetValue(ai.Path) cfg.Section("Desktop Entry").Key("TryExec").SetValue(arg0abs) // Resolve to a full path diff --git a/src/appimaged/thumbnail.go b/src/appimaged/thumbnail.go index b740f104..c7bd1090 100644 --- a/src/appimaged/thumbnail.go +++ b/src/appimaged/thumbnail.go @@ -47,45 +47,22 @@ func (ai AppImage) extractDirIconAsThumbnail() { // } //this will try to extract the thumbnail, or goes back to command based extraction if it fails. - err := ai.ExtractFile(".DirIcon", thumbnailcachedir, true) + dirIconFil, _ := os.Create(thumbnailcachedir + "/.DirIcon") + dirIconRdr, err := ai.Thumbnail() + if err != nil { + dirIconRdr, _, err = ai.Icon() + if err != nil { + goto genericIcon + } + } + _, err = io.Copy(dirIconFil, dirIconRdr) + //TODO: I could probably dump it directly to the buffer below + dirIconRdr.Close() // if err != nil { // Too verbose // sendErrorDesktopNotification(ai.niceName+" may be defective", "Could not read .DirIcon") // } - - // Workaround for electron-builder not generating .DirIcon - // We may still not have an icon. For example, AppImages made by electron-builder - // are lacking .DirIcon files as of Fall 2019; here we have to parse the desktop - // file, and try to extract the value of Icon= with the suffix ".png" from the AppImage - if helpers.Exists(thumbnailcachedir+"/.DirIcon") == false { - if *verbosePtr == true { - log.Println(".DirIcon extraction failed. Is it missing? Trying to figure out alternative") - } - var iconName string - if ai.Desktop != nil { - iconTmp, _ := ai.Desktop.Section("Desktop Entry").GetKey("Icon") - iconName = iconTmp.String() - } - - if iconName != "" { - iconName += ".png" - os.Remove(thumbnailcachedir + "/.DirIcon") - fil, err := os.Create(thumbnailcachedir + "/.DirIcon") - if err == nil { - rdr, err := ai.ExtractFileReader("iconName") - if err == nil { - _, err = io.Copy(fil, rdr) - if err != nil { - os.Remove(thumbnailcachedir + "/.DirIcon") - } - rdr.Close() - } else { - os.Remove(thumbnailcachedir + "/.DirIcon") - } - } - } - } - +genericIcon: buf, err := ioutil.ReadFile(thumbnailcachedir + "/.DirIcon") if os.IsNotExist(err) { if *verbosePtr == true { diff --git a/src/appimagetool/appdirtool.go b/src/appimagetool/appdirtool.go index 17d9ad25..e3ae248a 100644 --- a/src/appimagetool/appdirtool.go +++ b/src/appimagetool/appdirtool.go @@ -19,7 +19,7 @@ import ( "path/filepath" "strings" - "github.com/CalebQ42/copy" + "github.com/otiai10/copy" "github.com/probonopd/go-appimage/internal/helpers" ) diff --git a/src/goappimage/appimage.go b/src/goappimage/appimage.go index a4148870..0fa847ae 100644 --- a/src/goappimage/appimage.go +++ b/src/goappimage/appimage.go @@ -6,6 +6,7 @@ import ( "io" "os" "os/exec" + "path" "path/filepath" "strconv" "strings" @@ -35,8 +36,6 @@ type AppImage struct { imageType int } -const execLocationKey = helpers.ExecLocationKey - // NewAppImage creates an AppImage object from the location defined by path. // Returns an error if the given path is not an appimage, or is a temporary file. // In all instances, will still return the AppImage. @@ -66,11 +65,12 @@ func NewAppImage(path string) (*AppImage, error) { } //try to load up the desktop file for some information. desktopFil, err := ai.reader.FileReader("*.desktop") + if err != nil { + return nil, err + } + ai.Desktop, err = ini.Load(desktopFil) if err == nil { - ai.Desktop, err = ini.Load(desktopFil) - if err == nil { - ai.Name = ai.Desktop.Section("Desktop Entry").Key("Name").Value() - } + ai.Name = ai.Desktop.Section("Desktop Entry").Key("Name").Value() } if ai.Name == "" { ai.Name = ai.calculateNiceName() @@ -134,34 +134,42 @@ func (ai AppImage) Type() int { //If resolveSymlinks is true, if the filepath specified is a symlink, the actual file is extracted in it's place. //resolveSymlinks will have no effect on absolute symlinks (symlinks that start at root). func (ai AppImage) ExtractFile(filepath string, destinationdirpath string, resolveSymlinks bool) error { - if ai.reader != nil { - return ai.reader.ExtractTo(filepath, destinationdirpath, resolveSymlinks) - } - if ai.imageType == 2 { - cmd := exec.Command("unsquashfs", "-f", "-n", "-o", strconv.Itoa(int(ai.offset)), "-d", destinationdirpath, ai.Path, filepath) - _, err := runCommand(cmd) - return err - } - return errors.New("Unable to extract") + return ai.reader.ExtractTo(filepath, destinationdirpath, resolveSymlinks) } //ExtractFileReader tries to get an io.ReadCloser for the file at filepath. //Returns an error if the path is pointing to a folder. If the path is pointing to a symlink, //it will try to return the file being pointed to, but only if it's within the AppImage. func (ai AppImage) ExtractFileReader(filepath string) (io.ReadCloser, error) { - if ai.reader != nil { - return ai.reader.FileReader(filepath) - } - //TODO: possible type2 command fallback, but unsquashfs can't print to Stdout from what I've seen. - return nil, errors.New("Unable to get reader for " + filepath) + return ai.reader.FileReader(filepath) } //Thumbnail tries to get the AppImage's thumbnail and returns it as a io.ReadCloser. func (ai AppImage) Thumbnail() (io.ReadCloser, error) { - if ai.reader != nil { - return ai.reader.FileReader(".DirIcon") + return ai.reader.FileReader(".DirIcon") +} + +//Icon tries to get a io.ReadCloser for the icon dictated in the AppImage's desktop file. +//Returns the ReadCloser and the file's name (which could be useful for decoding). +func (ai AppImage) Icon() (io.ReadCloser, string, error) { + if ai.Desktop == nil { + return nil, "", errors.New("Desktop file wasn't parsed") + } + icon := ai.Desktop.Section("Desktop Entry").Key("Icon").Value() + if icon == "" { + return nil, "", errors.New("Desktop file doesn't specify an icon") + } + rootFils := ai.reader.ListFiles("/") + for _, fil := range rootFils { + if match, _ := path.Match(icon+"*", fil); match { + reader, err := ai.reader.FileReader(fil) + if err != nil { + return nil, "", err + } + return reader, fil, nil + } } - return nil, errors.New("Icon couldn't be found") + return nil, "", errors.New("Cannot find the AppImage's icon: " + icon) } func runCommand(cmd *exec.Cmd) (bytes.Buffer, error) { diff --git a/src/goappimage/appimage_test.go b/src/goappimage/appimage_test.go index 44247b8c..db3b457a 100644 --- a/src/goappimage/appimage_test.go +++ b/src/goappimage/appimage_test.go @@ -34,11 +34,7 @@ func TestAppImageType2(t *testing.T) { t.Fatal(err) } getCleanSquashfsFromAppImage(ai, testImg+".sfs", t) - typ2Rdr, err := newType2Reader(ai, true, true) - if err != nil { - t.Fatal(err) - } - typ2Rdr.SymlinkPath("AppRun") + fmt.Println("Name", ai.Name) t.Fatal("No Problem") } diff --git a/src/goappimage/archivereader.go b/src/goappimage/archivereader.go index e238f440..f701d81b 100644 --- a/src/goappimage/archivereader.go +++ b/src/goappimage/archivereader.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/CalebQ42/squashfs" + ioutilextra "gopkg.in/src-d/go-git.v4/utils/ioutil" ) type archiveReader interface { @@ -184,22 +185,61 @@ func (r *type2Reader) cleanPath(filepath string) (string, error) { return filepath, nil } -func (r *type2Reader) FileReader(path string) (io.ReadCloser, error) { +type anonymousCloser struct { + close func() error +} + +func (a anonymousCloser) Close() error { + return a.close() +} + +func (r *type2Reader) FileReader(filepath string) (io.ReadCloser, error) { //TODO: command fallback - fil := r.rdr.GetFileAtPath(path) - if fil == nil { - return nil, errors.New("Can't find file at: " + path) - } - if fil.IsSymlink() { - fil = fil.GetSymlinkFileRecursive() + if !r.forceFallback { + fil := r.rdr.GetFileAtPath(filepath) if fil == nil { - return nil, errors.New("Can't resolve symlink at: " + path) + return nil, errors.New("Can't find file at: " + filepath) } + if fil.IsSymlink() { + fil = fil.GetSymlinkFileRecursive() + if fil == nil { + return nil, errors.New("Can't resolve symlink at: " + filepath) + } + } + if fil.IsDir() { + return nil, errors.New("Path is a directory: " + filepath) + } + return fil, nil + } + filepath, err := r.cleanPath(filepath) + filepath = r.SymlinkPathRecursive(filepath) + if filepath != r.SymlinkPath(filepath) { + return nil, errors.New("Can't resolve symlink at: " + filepath) + } + if r.IsDir(filepath) { + return nil, errors.New("Path is a directory: " + filepath) + } + tmpDir, err := ioutil.TempDir("", filepath) + if err != nil { + return nil, errors.New("Cannot make the temp directory") } - if fil.IsDir() { - return nil, errors.New("Path is a directory: " + path) + err = r.ExtractTo(filepath, tmpDir, true) + if err != nil { + os.RemoveAll(tmpDir) + return nil, err + } + tmpFil, err := os.Open(tmpDir + "/" + path.Base(filepath)) + if err != nil { + os.RemoveAll(tmpDir) + return nil, err } - return fil, nil + closer := anonymousCloser{ + close: func() error { + tmpFil.Close() + return os.RemoveAll(tmpDir) + }, + } + return ioutilextra.NewReadCloser(tmpFil, closer), nil } func (r *type2Reader) IsDir(filepath string) bool { @@ -251,8 +291,9 @@ func (r *type2Reader) SymlinkPath(filepath string) string { neededLine := tmpOutput[len(tmpOutput)-1] if strings.Contains(neededLine, "->") { neededLine = neededLine[strings.Index(neededLine, "->")+3:] + return path.Dir(filepath) + "/" + neededLine } - return path.Dir(filepath) + "/" + neededLine + return filepath } func (r *type2Reader) SymlinkPathRecursive(filepath string) string { @@ -288,56 +329,99 @@ func (r *type2Reader) SymlinkPathRecursive(filepath string) string { } func (r *type2Reader) Contains(path string) bool { - //TODO: command fallback - fil := r.rdr.GetFileAtPath(path) - return fil != nil + if !r.forceFallback { + fil := r.rdr.GetFileAtPath(path) + return fil != nil + } + path, err := r.cleanPath(path) + return err == nil } func (r *type2Reader) ListFiles(path string) []string { - //TODO: command fallback - fil := r.rdr.GetFileAtPath(path) - if fil == nil { - return nil - } - if fil.IsSymlink() { - fil = fil.GetSymlinkFileRecursive() + if !r.forceFallback { + fil := r.rdr.GetFileAtPath(path) if fil == nil { return nil } + if fil.IsSymlink() { + fil = fil.GetSymlinkFileRecursive() + if fil == nil { + return nil + } + } + if !fil.IsDir() { + return nil + } + children, err := fil.GetChildren() + if err != nil { + return nil + } + out := make([]string, 0) + for _, child := range children { + out = append(out, child.Name()) + } + return out } - if !fil.IsDir() { - return nil - } - children, err := fil.GetChildren() + path, err := r.cleanPath(path) if err != nil { return nil } - out := make([]string, 0) - for _, child := range children { - out = append(out, child.Name()) - } - return out + return r.structure[path] } -func (r *type2Reader) ExtractTo(path, destination string, resolveSymlinks bool) error { - //TODO: command fallback - fil := r.rdr.GetFileAtPath(path) - if fil == nil { +func (r *type2Reader) ExtractTo(filepath, destination string, resolveSymlinks bool) error { + if !r.forceFallback { + fil := r.rdr.GetFileAtPath(filepath) + if fil == nil { + return nil + } + var errs []error + if resolveSymlinks { + errs = fil.ExtractSymlink(filepath) + } else { + errs = fil.ExtractTo(destination) + } + if len(errs) > 0 { + return errs[0] + } return nil } - if fil.IsSymlink() && resolveSymlinks { - tmp := fil.GetSymlinkFileRecursive() - if tmp != nil { - errs := tmp.ExtractTo(destination) - if len(errs) > 0 { - return errs[0] - } - return nil + filepath, err := r.cleanPath(filepath) + if err != nil { + return err + } + var origName string + if resolveSymlinks { + origName = path.Base(filepath) + filepath = r.SymlinkPathRecursive(filepath) + } + var tmp string + for i := -1; ; i++ { //let's make sure we aren't going to coincidentally extracting to a directoyr that already has a temp directory in it... + if i == -1 { + tmp = destination + "/.tmp" + } else { + tmp = destination + "/.tmp" + strconv.Itoa(i) + } + _, err = os.Open(tmp) + if os.IsNotExist(err) { + break + } else if err != nil { + return err //make sure other issues aren't going to cause this loop to run forever. } } - errs := fil.ExtractTo(destination) - if len(errs) > 0 { - return errs[0] + defer os.RemoveAll(tmp) + cmd := exec.Command("unsquashfs", "-o", strconv.Itoa(r.offset), "-d", tmp, r.path, filepath) + err = cmd.Run() + if err != nil { + return err + } + name := path.Base(filepath) + if origName != "" { + name = origName + } + err = os.Rename(tmp+"/"+filepath, destination+"/"+name) + if err != nil { + return err } return nil } @@ -531,7 +615,7 @@ func (r *type1Reader) ExtractTo(filepath, destination string, resolveSymlinks bo } name := path.Base(filepath) destination = strings.TrimSuffix(destination, "/") - tmpDir := destination + "/" + ".temp" + tmpDir := destination + "/" + ".tmp" err = os.Mkdir(tmpDir, os.ModePerm) if err != nil { return err From caac1cf1a9870b7e0439b9edc4b0cf8f551d069f Mon Sep 17 00:00:00 2001 From: Caleb Gardner Date: Sun, 27 Dec 2020 03:03:58 -0600 Subject: [PATCH 23/23] Updated to newest sqashfs to fix issues. Fixed some issues with thumbnails and desktop files --- go.mod | 2 +- go.sum | 2 ++ src/appimaged/appimage.go | 2 +- src/appimaged/desktop.go | 4 ++++ src/appimaged/thumbnail.go | 9 ++++++++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d2833dcc..7d531916 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/probonopd/go-appimage go 1.13 require ( - github.com/CalebQ42/squashfs v0.3.5 + github.com/CalebQ42/squashfs v0.3.6 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 github.com/adrg/xdg v0.2.3 diff --git a/go.sum b/go.sum index 698e9d1b..8eb669a0 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/CalebQ42/GoAppImage v0.4.0 h1:aF+Y/vyo/RGhoyZEW1CMY6WyRWrZZO4ydsRFAtI github.com/CalebQ42/GoAppImage v0.4.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= github.com/CalebQ42/squashfs v0.3.5 h1:EniKQIvaGkPIz/6WebIZGFnEV9Viio6cyRPMJs0B+/Y= github.com/CalebQ42/squashfs v0.3.5/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= +github.com/CalebQ42/squashfs v0.3.6 h1:bn2w7YyH++8OHiSmiqsG7FP2p+pizuA3Qo1HJSXSGtU= +github.com/CalebQ42/squashfs v0.3.6/go.mod h1:vKz+LDiKqKGDlB6tiykL3rtHdGT+QEPicvoF80S2S0Q= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:fMi9ZZ/it4orHj3xWrM6cLkVFcCbkXQALFUiNtHtCPs= diff --git a/src/appimaged/appimage.go b/src/appimaged/appimage.go index 7c09f25f..b0083cc7 100644 --- a/src/appimaged/appimage.go +++ b/src/appimaged/appimage.go @@ -46,7 +46,7 @@ func NewAppImage(path string) (ai *AppImage, err error) { ai = new(AppImage) ai.AppImage, err = goappimage.NewAppImage(path) if err != nil { - return nil, err + return ai, err } ai.uri = strings.TrimSpace(string(uri.File(filepath.Clean(ai.Path)))) diff --git a/src/appimaged/desktop.go b/src/appimaged/desktop.go index 6c1c8547..0cf98c3d 100644 --- a/src/appimaged/desktop.go +++ b/src/appimaged/desktop.go @@ -240,7 +240,11 @@ func fixDesktopFile(path string) error { if bytes.Contains(input, []byte("=`")) { output = bytes.Replace(input, []byte("=`"), []byte("="), -1) output = bytes.Replace(output, []byte("`\n"), []byte("\n"), -1) + //some issues that appear now that we're using the original .desktop file + output = bytes.Replace(output, []byte("\n;\n"), []byte("\n"), -1) + output = bytes.Replace(output, []byte("\n; \n"), []byte("\n"), -1) } + if err = ioutil.WriteFile(path, output, 0755); err != nil { return err } diff --git a/src/appimaged/thumbnail.go b/src/appimaged/thumbnail.go index c7bd1090..58c6bc0f 100644 --- a/src/appimaged/thumbnail.go +++ b/src/appimaged/thumbnail.go @@ -34,6 +34,7 @@ func (ai AppImage) extractDirIconAsThumbnail() { // Write out the icon to a temporary location thumbnailcachedir := xdg.CacheHome + "/thumbnails/" + ai.md5 + os.MkdirAll(thumbnailcachedir, os.ModePerm) // if ai.imagetype == 1 { // err := os.MkdirAll(thumbnailcachedir, os.ModePerm) @@ -50,14 +51,20 @@ func (ai AppImage) extractDirIconAsThumbnail() { dirIconFil, _ := os.Create(thumbnailcachedir + "/.DirIcon") dirIconRdr, err := ai.Thumbnail() if err != nil { + if *verbosePtr { + log.Print("Could not find .DirIcon, trying to find the desktop file's specified icon") + } dirIconRdr, _, err = ai.Icon() if err != nil { goto genericIcon } } _, err = io.Copy(dirIconFil, dirIconRdr) - //TODO: I could probably dump it directly to the buffer below dirIconRdr.Close() + if err != nil { + helpers.LogError("thumbnail", err) + } + //TODO: I could probably dump it directly to the buffer below // if err != nil { // Too verbose // sendErrorDesktopNotification(ai.niceName+" may be defective", "Could not read .DirIcon")