tpkg 2.2.4 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/ruby -w
2
2
  ##############################################################################
3
3
  # tpkg package management system
4
- # Copyright 2009, 2010, 2011 AT&T Interactive
5
4
  # License: MIT (http://www.opensource.org/licenses/mit-license.php)
6
5
  ##############################################################################
7
6
 
8
- # Ensure we can find tpkg.rb
9
- $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
7
+ # When run from the source repository or from an unpacked copy of the
8
+ # distribution we want to find the local library, even if there's a copy
9
+ # installed on the system. The installed copy is likely older, and the API
10
+ # may be out of sync with this executable.
11
+ $:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
10
12
 
11
13
  # This script expects one argument, which is the tpkg.xml file
12
14
  # that you want to convert to yml format.
@@ -1,6 +1,5 @@
1
1
  ##############################################################################
2
2
  # tpkg package management system
3
- # Copyright 2009, 2010, 2011 AT&T Interactive
4
3
  # License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
4
  ##############################################################################
6
5
 
@@ -20,27 +19,30 @@ Silently.silently do
20
19
  require 'facter'
21
20
  end
22
21
  require 'digest/sha2' # Digest::SHA256#hexdigest, etc.
23
- require 'uri' # URI
24
- require 'net/http' # Net::HTTP
25
- require 'net/https' # Net::HTTP#use_ssl, etc.
26
- require 'time' # Time#httpdate
27
- require 'rexml/document' # REXML::Document
22
+ require 'etc' # Etc.getpwnam, getgrnam
28
23
  require 'fileutils' # FileUtils.cp, rm, etc.
29
- require 'tempfile' # Tempfile
30
24
  require 'find' # Find
31
- require 'etc' # Etc.getpwnam, getgrnam
25
+ require 'net/http' # Net::HTTP
26
+ require 'net/https' # Net::HTTP#use_ssl, etc.
32
27
  require 'openssl' # OpenSSL
33
28
  require 'open3' # Open3
34
29
  require 'set' # Enumerable#to_set
30
+ require 'rexml/document' # REXML::Document
31
+ require 'stringio' # StringIO
32
+ require 'tempfile' # Tempfile
33
+ require 'time' # Time#httpdate
34
+ require 'uri' # URI
35
35
  require 'yaml' # YAML
36
36
  end
37
- require 'tpkg/versiontype' # Version
38
37
  require 'tpkg/deployer'
39
38
  require 'tpkg/metadata'
39
+ require 'tpkg/versiontype' # Version
40
+
41
+ OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
40
42
 
41
43
  class Tpkg
42
44
 
43
- VERSION = '2.2.4'
45
+ VERSION = '2.3.0'
44
46
 
45
47
  GENERIC_ERR = 1
46
48
  POSTINSTALL_ERR = 2
@@ -82,17 +84,40 @@ class Tpkg
82
84
  if !ENV['PATH']
83
85
  raise "tpkg cannot run because the PATH env variable is not set."
84
86
  end
85
- ENV['PATH'].split(':').each do |path|
87
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
86
88
  TARNAMES.each do |tarname|
87
- if File.executable?(File.join(path, tarname))
88
- IO.popen("#{File.join(path, tarname)} --version 2>/dev/null") do |pipe|
89
- pipe.each_line do |line|
89
+ tarpath = nil
90
+ if RUBY_PLATFORM == 'i386-mingw32'
91
+ # Turns out that File.join is fairly pointless, at least as far
92
+ # as Windows compatibility. Ruby always uses '/' as the path
93
+ # separator, even on Windows. I.e. File::SEPARATOR is '/' on
94
+ # Windows (yes, really!). File::ALT_SEPARATOR is '\' but
95
+ # File.join and its ilk will never use it. The forward slash
96
+ # works fine for API-level file operations, Windows will accept
97
+ # either forward slashes or backslashes at the API level. But
98
+ # you can't execute a path with forward slashes, apparently due
99
+ # to some backwards-compatibility thing with cmd.exe. Sigh.
100
+ tarpath = path.gsub('/', '\\') + '\\' + tarname + '.exe'
101
+ else
102
+ tarpath = File.join(path, tarname)
103
+ end
104
+ if File.executable?(tarpath)
105
+ # Particularly on Windows it is possible that the path contains
106
+ # spaces. I.e. C:\Program Files (x86)\GnuWin32\bin\bsdtar.exe
107
+ # It looks like that needs to be wrapped in quotes to execute
108
+ # properly.
109
+ if tarpath.include?(' ')
110
+ tarpath = '"' + tarpath + '"'
111
+ end
112
+ Open3.popen3("#{tarpath} --version") do |stdin, stdout, stderr|
113
+ stdin.close
114
+ stdout.each_line do |line|
90
115
  if line.include?('GNU tar')
91
116
  @@tarinfo[:type] = 'gnu'
92
- @@tar = File.join(path, tarname)
117
+ @@tar = tarpath
93
118
  elsif line.include?('bsdtar')
94
119
  @@tarinfo[:type] = 'bsd'
95
- @@tar = File.join(path, tarname)
120
+ @@tar = tarpath
96
121
  end
97
122
  if line =~ /(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)/
98
123
  @@tarinfo[:version] = [$1, $2, $3].compact.join(".")
@@ -103,7 +128,6 @@ class Tpkg
103
128
  end
104
129
  end
105
130
  end
106
- # Raise an exception if we didn't find a suitable tar
107
131
  raise "Unable to find GNU tar or bsdtar in PATH"
108
132
  end
109
133
  end
@@ -163,10 +187,13 @@ class Tpkg
163
187
  File.chown(st.uid, st.gid, tmpfile.path)
164
188
  rescue Errno::EPERM
165
189
  raise if Process.euid == 0
190
+ rescue Errno::EINVAL
191
+ raise if RUBY_PLATFORM != 'i386-cygwin'
166
192
  end
167
193
  tmpfile.write(MAGIC)
168
194
  tmpfile.write(salt)
169
- tmpfile.write(c.update(IO.read(filename)) + c.final)
195
+ content = IO.read(filename)
196
+ tmpfile.write(c.update(content) + c.final) unless content.empty?
170
197
  tmpfile.close
171
198
  File.rename(tmpfile.path, filename)
172
199
  end
@@ -207,8 +234,11 @@ class Tpkg
207
234
  File.chown(st.uid, st.gid, tmpfile.path)
208
235
  rescue Errno::EPERM
209
236
  raise if Process.euid == 0
237
+ rescue Errno::EINVAL
238
+ raise if RUBY_PLATFORM != 'i386-cygwin'
210
239
  end
211
- tmpfile.write(c.update(file.read) + c.final)
240
+ content = file.read
241
+ tmpfile.write(c.update(content) + c.final) unless content.empty?
212
242
  tmpfile.close
213
243
  File.rename(tmpfile.path, filename)
214
244
  end
@@ -433,22 +463,12 @@ class Tpkg
433
463
  # blocks) and see if tar succeeds in listing a file.
434
464
  1.upto(10) do |numblocks|
435
465
  tarblocks = File.read(package_file, 512*numblocks)
436
- # Open3.popen3("#{find_tar} -tf - #{@@taroptions}") do |stdin, stdout, stderr|
437
- # stdin.write(tarblocks)
438
- # stdin.close
439
- # toplevel = stdout.read
440
- # end
441
- # Unfortunately popen3 doesn't provide a mechanism for determining the
442
- # success or failure of the command until ruby 1.9. ($? is never
443
- # accurately set for popen3, the mechanism in ruby 1.9 for getting the
444
- # exit status for popen3 is unique to popen3.) So we're left with this,
445
- # which it rather Unix-specific.
446
- IO.popen("#{find_tar} -tf - #{@@taroptions} 2> /dev/null", 'r+') do |pipe|
447
- pipe.write(tarblocks)
448
- pipe.close_write
449
- toplevel = pipe.read
450
- end
451
- if $?.success?
466
+ Open3.popen3("#{find_tar} -tf - #{@@taroptions}") do |stdin, stdout, stderr|
467
+ stdin.write(tarblocks)
468
+ stdin.close
469
+ toplevel = stdout.read
470
+ end
471
+ if !toplevel.empty?
452
472
  break
453
473
  else
454
474
  toplevel = nil
@@ -711,6 +731,13 @@ class Tpkg
711
731
  # Extract 7 from 7.1-RELEASE, for example
712
732
  fbver = Facter['operatingsystemrelease'].value
713
733
  osver = fbver.split('.').first
734
+ elsif Facter['operatingsystem'] &&
735
+ Facter['operatingsystem'].value == 'windows'
736
+ # Extract 6.1 from 6.1.7601, for example
737
+ # That seems like the right level to split at
738
+ # based on http://en.wikipedia.org/wiki/Ver_(command)
739
+ winver = Facter['operatingsystemrelease'].value
740
+ osver = winver.split('.')[0,2].join('.')
714
741
  elsif Facter['operatingsystemrelease'] &&
715
742
  Facter['operatingsystemrelease'].value &&
716
743
  !Facter['operatingsystemrelease'].value.empty?
@@ -772,6 +799,14 @@ class Tpkg
772
799
  same_min_ver_req = true
773
800
  end
774
801
  end
802
+ if req[:version_greater_than]
803
+ pkgver = Version.new(metadata[:version])
804
+ reqver = Version.new(req[:version_greater_than])
805
+ if pkgver <= reqver
806
+ puts "Package fails version_greater_than (#{pkgver} <= #{reqver})" if @@debug
807
+ result = false
808
+ end
809
+ end
775
810
  if req[:maximum_version]
776
811
  pkgver = Version.new(metadata[:version])
777
812
  reqver = Version.new(req[:maximum_version])
@@ -782,6 +817,14 @@ class Tpkg
782
817
  same_max_ver_req = true
783
818
  end
784
819
  end
820
+ if req[:version_less_than]
821
+ pkgver = Version.new(metadata[:version])
822
+ reqver = Version.new(req[:version_less_than])
823
+ if pkgver >= reqver
824
+ puts "Package fails version_less_than (#{pkgver} >= #{reqver})" if @@debug
825
+ result = false
826
+ end
827
+ end
785
828
  if same_min_ver_req && req[:minimum_package_version]
786
829
  pkgver = Version.new(metadata[:package_version])
787
830
  reqver = Version.new(req[:minimum_package_version])
@@ -790,6 +833,14 @@ class Tpkg
790
833
  result = false
791
834
  end
792
835
  end
836
+ if same_min_ver_req && req[:package_version_greater_than]
837
+ pkgver = Version.new(metadata[:package_version])
838
+ reqver = Version.new(req[:package_version_greater_than])
839
+ if pkgver <= reqver
840
+ puts "Package fails package_version_greater_than (#{pkgver} <= #{reqver})" if @@debug
841
+ result = false
842
+ end
843
+ end
793
844
  if same_max_ver_req && req[:maximum_package_version]
794
845
  pkgver = Version.new(metadata[:package_version])
795
846
  reqver = Version.new(req[:maximum_package_version])
@@ -798,6 +849,14 @@ class Tpkg
798
849
  result = false
799
850
  end
800
851
  end
852
+ if same_max_ver_req && req[:package_version_less_than]
853
+ pkgver = Version.new(metadata[:package_version])
854
+ reqver = Version.new(req[:package_version_less_than])
855
+ if pkgver >= reqver
856
+ puts "Package fails package_version_less_than (#{pkgver} >= #{reqver})" if @@debug
857
+ result = false
858
+ end
859
+ end
801
860
  # The empty? check ensures that a package with no operatingsystem
802
861
  # field matches all clients.
803
862
  if metadata[:operatingsystem] &&
@@ -1005,37 +1064,100 @@ class Tpkg
1005
1064
  # foo
1006
1065
  # foo=1.0
1007
1066
  # foo=1.0=1
1008
- # foo-1.0-1.tpkg
1009
- def self.parse_request(request, installed_dir = nil)
1010
- # FIXME: Add support for <, <=, >, >=
1067
+ # foo>1.0
1068
+ # foo<=1.0=2
1069
+ # foo<=1.0>=3
1070
+ # foo=1.0<=6
1071
+ # foo-1.0-1.tpkg
1072
+ def self.parse_request(request)
1011
1073
  req = {}
1012
- parts = request.split('=')
1013
-
1074
+ # Note that the ordering in the regex is important. <= and >= have to
1075
+ # appear before others so that they match rather than two separate matches
1076
+ # for the '>' and '=' characters. I.e. '1>=2'.split(/(>=|>|=)/) ==
1077
+ # ['1', '>=', '2'] but '1>=2'.split(/(>|=|>=)/) == ['1', '>', '=', '2']
1078
+ parts = request.split(/(<=|>=|<|>|=)/)
1079
+
1014
1080
  # upgrade/remove/query options could take package filenames
1015
1081
  # We're assuming that the filename ends in .tpkg and has a version number that starts
1016
1082
  # with a digit. For example: foo-1.0.tpkg, foo-bar-1.0-1.tpkg
1017
1083
  if request =~ /\.tpkg$/
1018
1084
  req = {:filename => request, :name => request.split(/-\d/)[0]}
1019
- elsif parts.length > 2 && parts[-2] =~ /^[\d\.]/ && parts[-1] =~ /^[\d\.]/
1020
- package_version = parts.pop
1021
- version = parts.pop
1022
- req[:name] = parts.join('-')
1023
- req[:minimum_version] = version
1024
- req[:maximum_version] = version
1025
- req[:minimum_package_version] = package_version
1026
- req[:maximum_package_version] = package_version
1027
- elsif parts.length > 1 && parts[-1] =~ /^[\d\.]/
1028
- version = parts.pop
1029
- req[:name] = parts.join('-')
1030
- req[:minimum_version] = version
1031
- req[:maximum_version] = version
1032
1085
  else
1033
- req[:name] = parts.join('-')
1086
+ if parts.length > 4 && parts[-3] =~ /^[\d\.]/ && parts[-1] =~ /^[\d\.]/
1087
+ package_version = parts.pop
1088
+ package_version_sign = parts.pop
1089
+ version = parts.pop
1090
+ version_sign = parts.pop
1091
+
1092
+ case version_sign
1093
+ when '<'
1094
+ # E.g. foo<1.0
1095
+ req[:version_less_than] = version
1096
+ when '<='
1097
+ # E.g. foo<=1.0
1098
+ req[:maximum_version] = version
1099
+ when '='
1100
+ # E.g. foo=1.0
1101
+ req[:minimum_version] = version
1102
+ req[:maximum_version] = version
1103
+ when '>'
1104
+ # E.g. foo>1.0
1105
+ req[:version_greater_than] = version
1106
+ when '>='
1107
+ # E.g. foo>=1.0
1108
+ req[:minimum_version] = version
1109
+ end
1110
+
1111
+ case package_version_sign
1112
+ when '<'
1113
+ # E.g. foo=1.0<2.0
1114
+ req[:package_version_less_than] = package_version
1115
+ when '<='
1116
+ # E.g. foo=1.0<=2.0
1117
+ req[:maximum_package_version] = package_version
1118
+ when '='
1119
+ # E.g. foo=1.0=2.0
1120
+ req[:minimum_package_version] = package_version
1121
+ req[:maximum_package_version] = package_version
1122
+ when '>'
1123
+ # E.g. foo=1.0>2.0
1124
+ req[:package_version_greater_than] = package_version
1125
+ when '>='
1126
+ # E.g. foo=1.0>=2.0
1127
+ req[:minimum_package_version] = package_version
1128
+ end
1129
+ elsif parts.length > 1 && parts[-1] =~ /^[\d\.]/
1130
+ version = parts.pop
1131
+ version_sign = parts.pop
1132
+ if version_sign == '=' && version.include?('*')
1133
+ req[:allowed_versions] = version
1134
+ else
1135
+ case version_sign
1136
+ when '<'
1137
+ # E.g. foo<1.0
1138
+ req[:version_less_than] = version
1139
+ when '<='
1140
+ # E.g. foo<=1.0
1141
+ req[:maximum_version] = version
1142
+ when '='
1143
+ # E.g. foo=1.0
1144
+ req[:minimum_version] = version
1145
+ req[:maximum_version] = version
1146
+ when '>'
1147
+ # E.g. foo>1.0
1148
+ req[:version_greater_than] = version
1149
+ when '>='
1150
+ # E.g. foo>=1.0
1151
+ req[:minimum_version] = version
1152
+ end
1153
+ end
1154
+ end
1155
+ req[:name] = parts.join('')
1034
1156
  end
1035
1157
  req[:type] = :tpkg
1036
1158
  req
1037
1159
  end
1038
-
1160
+
1039
1161
  # deploy_options is used for configuration the deployer. It is a map of option_names => option_values. Possible
1040
1162
  # options are: use-ssh-key, deploy-as, worker-count, abort-on-fail
1041
1163
  #
@@ -1355,17 +1477,25 @@ class Tpkg
1355
1477
  name = metadata_yml[:name]
1356
1478
  metadata[name] = [] if !metadata[name]
1357
1479
  metadata[name] << metadata_yml
1358
- elsif File.directory?(source)
1359
- if !File.exists?(File.join(source, 'metadata.yml'))
1360
- warn "Warning: the source directory #{source} has no metadata.yml file. Try running tpkg -x #{source} first."
1361
- next
1480
+ elsif source[0,1] == File::SEPARATOR || File.directory?(source)
1481
+ if File.directory?(source)
1482
+ if !File.exists?(File.join(source, 'metadata.yml'))
1483
+ warn "Source directory #{source} has no metadata.yml file. Try running tpkg -x #{source} first."
1484
+ next
1485
+ end
1486
+ metadata_contents = File.read(File.join(source, 'metadata.yml'))
1487
+ Metadata::get_pkgs_metadata_from_yml_doc(metadata_contents, metadata, source)
1488
+ else
1489
+ warn "Source directory #{source} does not exist, skipping."
1362
1490
  end
1363
-
1364
- metadata_contents = File.read(File.join(source, 'metadata.yml'))
1365
- Metadata::get_pkgs_metadata_from_yml_doc(metadata_contents, metadata, source)
1366
1491
  else
1367
1492
  uri = http = localdate = remotedate = localdir = localpath = nil
1368
1493
 
1494
+ if !URI.parse(source).absolute?
1495
+ warn "Source #{source} is not a file, directory, or absolute URI, skipping"
1496
+ next
1497
+ end
1498
+
1369
1499
  uri = URI.join(source, 'metadata.yml')
1370
1500
  http = gethttp(uri)
1371
1501
 
@@ -1880,6 +2010,10 @@ class Tpkg
1880
2010
  end
1881
2011
  pkgs
1882
2012
  end
2013
+
2014
+ # Returns an array (possibly empty) of the packages that meet the given
2015
+ # requirement. If the given requirement is nil or not specified then all
2016
+ # installed packages are returned.
1883
2017
  def installed_packages_that_meet_requirement(req=nil)
1884
2018
  pkgs = []
1885
2019
  if req && req[:type] == :native
@@ -2334,6 +2468,7 @@ class Tpkg
2334
2468
  File.chmod(0644, tmpfile.path)
2335
2469
  File.rename(tmpfile.path, localpath)
2336
2470
  rescue
2471
+ # FIXME: should include original exception message to help user debug
2337
2472
  raise "Unable to download and/or verify the package."
2338
2473
  end
2339
2474
 
@@ -2620,6 +2755,8 @@ class Tpkg
2620
2755
  end
2621
2756
  rescue Errno::EPERM
2622
2757
  raise if Process.euid == 0
2758
+ rescue Errno::EINVAL
2759
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2623
2760
  end
2624
2761
  if File.symlink?(f)
2625
2762
  if default_perms
@@ -2644,7 +2781,13 @@ class Tpkg
2644
2781
  stat = info[:stat]
2645
2782
  file_path = info[:normalized]
2646
2783
  File.chmod(stat.mode, file_path)
2647
- File.chown(stat.uid, stat.gid, file_path)
2784
+ begin
2785
+ File.chown(stat.uid, stat.gid, file_path)
2786
+ rescue Errno::EPERM
2787
+ raise if Process.euid == 0
2788
+ rescue Errno::EINVAL
2789
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2790
+ end
2648
2791
  end
2649
2792
 
2650
2793
  # Handle any decryption, ownership/permissions, and other issues for specific files
@@ -2681,6 +2824,8 @@ class Tpkg
2681
2824
  end
2682
2825
  rescue Errno::EPERM
2683
2826
  raise if Process.euid == 0
2827
+ rescue Errno::EINVAL
2828
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2684
2829
  end
2685
2830
  end
2686
2831
  if tpkgfile[:posix][:perms]
@@ -2710,7 +2855,7 @@ class Tpkg
2710
2855
  begin
2711
2856
  Tpkg::decrypt(metadata[:name], working_path, options[:passphrase], *([tpkgfile[:encrypt][:algorithm]].compact))
2712
2857
  break
2713
- rescue OpenSSL::CipherError
2858
+ rescue OpenSSLCipherError
2714
2859
  @@passphrase = nil
2715
2860
  if i == 3
2716
2861
  raise "Incorrect passphrase."
@@ -2937,6 +3082,8 @@ class Tpkg
2937
3082
  else
2938
3083
  raise e
2939
3084
  end
3085
+ rescue Errno::EINVAL
3086
+ raise if RUBY_PLATFORM != 'i386-cygwin'
2940
3087
  end
2941
3088
  # Insert the contents of the current crontab file
2942
3089
  File.open(destination[:file]) { |file| tmpfile.write(file.read) }
@@ -3029,6 +3176,8 @@ class Tpkg
3029
3176
  else
3030
3177
  raise
3031
3178
  end
3179
+ rescue Errno::EINVAL
3180
+ raise if RUBY_PLATFORM != 'i386-cygwin'
3032
3181
  end
3033
3182
  # Remove section associated with this package
3034
3183
  skip = false
@@ -3267,7 +3416,8 @@ class Tpkg
3267
3416
  # requirements << { :name => 'bar' }, packages['bar'] = { :source => 'http://server/pkgs/bar-2.3.pkg' }
3268
3417
  # requirements << { :name => 'blat', :minimum_version => '0.5', :maximum_version => '0.5' }, packages['blat'] populated with available packages meeting that requirement
3269
3418
  # Note: the requirements and packages arguments are modified by this method
3270
- def parse_requests(requests, requirements, packages)
3419
+ # FIXME: This method has a terrible API, can we fix it?
3420
+ def parse_requests(requests, requirements, packages, options = {})
3271
3421
  newreqs = []
3272
3422
 
3273
3423
  requests.each do |request|
@@ -3313,14 +3463,16 @@ class Tpkg
3313
3463
  else # basic package specs ('foo' or 'foo=1.0')
3314
3464
  puts "parse_requests request looks like package spec" if @@debug
3315
3465
 
3316
- # Tpkg::parse_request is a class method and doesn't know where packages are installed.
3317
- # So we have to tell it ourselves.
3318
- req = Tpkg::parse_request(request, @installed_directory)
3466
+ req = Tpkg::parse_request(request)
3319
3467
  newreqs << req
3320
3468
 
3321
3469
  puts "Initializing the list of possible packages for this req" if @@debug
3322
3470
  if !packages[req[:name]]
3323
- packages[req[:name]] = available_packages_that_meet_requirement(req)
3471
+ if !options[:installed_only]
3472
+ packages[req[:name]] = available_packages_that_meet_requirement(req)
3473
+ else
3474
+ packages[req[:name]] = installed_packages_that_meet_requirement(req)
3475
+ end
3324
3476
  end
3325
3477
  end
3326
3478
  end
@@ -3657,7 +3809,7 @@ class Tpkg
3657
3809
  puts "Running '/opt/local/bin/port install #{pkgname}' to install native package" if @@debug
3658
3810
  system("/opt/local/bin/port install #{pkgname}")
3659
3811
  else
3660
- # Fink support would be nice
3812
+ # Fink, Homebrew support would be nice
3661
3813
  raise "No supported native package tool available on #{Tpkg::get_os}"
3662
3814
  end
3663
3815
  else
@@ -3900,8 +4052,8 @@ class Tpkg
3900
4052
  end
3901
4053
 
3902
4054
  if prompt_for_conflicting_files(pkgfile, CHECK_UPGRADE)
3903
- # If the old and new packages have overlapping externals flag them
3904
- # to be skipped so that the external isn't removed and then
4055
+ # If the old and new packages have overlapping externals then flag
4056
+ # them to be skipped so that the external isn't removed and then
3905
4057
  # immediately re-added
3906
4058
  oldpkgs = installed_packages_that_meet_requirement({:name => pkg[:metadata][:name], :type => :tpkg})
3907
4059
  externals_to_skip = []
@@ -3909,7 +4061,7 @@ class Tpkg
3909
4061
  if oldpkgs.all? {|oldpkg| oldpkg[:metadata][:externals] && oldpkg[:metadata][:externals].include?(external)}
3910
4062
  externals_to_skip << external
3911
4063
  end
3912
- end if pkg[:metadata][:externals]
4064
+ end if pkg[:metadata][:externals] && !oldpkgs.empty?
3913
4065
 
3914
4066
  # Remove the old package if we haven't done so
3915
4067
  unless oldpkgs.nil? or oldpkgs.empty? or removed_pkgs.include?(pkg[:metadata][:name])
@@ -3969,7 +4121,7 @@ class Tpkg
3969
4121
  requests.uniq! if requests.is_a?(Array)
3970
4122
  packages_to_remove = []
3971
4123
  requests.each do |request|
3972
- req = Tpkg::parse_request(request, @installed_directory)
4124
+ req = Tpkg::parse_request(request)
3973
4125
  packages_to_remove.concat(installed_packages_that_meet_requirement(req))
3974
4126
  end
3975
4127
  else
@@ -4063,7 +4215,7 @@ class Tpkg
4063
4215
  end
4064
4216
 
4065
4217
  # Stop the services if there's init script
4066
- if !options[:upgrade]
4218
+ if !options[:upgrade] && !options[:skip_remove_stop]
4067
4219
  packages_to_remove.each do |pkg|
4068
4220
  init_scripts_metadata = init_scripts(pkg[:metadata])
4069
4221
  if init_scripts_metadata && !init_scripts_metadata.empty?
@@ -4539,6 +4691,8 @@ class Tpkg
4539
4691
  original_dir = Dir.pwd
4540
4692
 
4541
4693
  workdir = Tpkg::tempdir("tpkg_download")
4694
+ # FIXME: should use begin/ensure to make sure we chdir back when done
4695
+ # But I also wonder why we have to chdir at all...
4542
4696
  Dir.chdir(workdir)
4543
4697
  parse_requests(requests, requirements, packages)
4544
4698
  packages = packages.values.flatten