Set shebang command to explicitly invoke Python 2.
[dput.git] / dput
blob6a26d838faa5936f067beee1cd0dae09a5578c5c
1 #! /usr/bin/python2
2 # -*- coding: utf-8; -*-
4 # dput — Debian package upload tool.
5 # Part of Debian ‘dput’ package.
7 # Copyright © 2015 Ben Finney <ben+debian@benfinney.id.au>
8 # Copyright © 2008–2013 Y Giridhar Appaji Nag <appaji@debian.org>
9 # Copyright © 2006–2008 Thomas Viehmann <tv@beamnet.de>
10 # Copyright © 2000–2005 Christian Kurz <shorty@debian.org>
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License along
23 # with this program; if not, write to the Free Software Foundation, Inc.,
24 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 """ dput — Debian package upload tool. """
28 import os
29 import os.path
30 import sys
31 import string
32 import re
33 import glob
34 import signal
35 import pwd
36 import stat
37 import rfc822
38 from hashlib import md5, sha1
39 import importlib
40 import pkgutil
42 # Now import our modules
43 import ConfigParser
45 app_library_path = "/usr/share/dput"
46 sys.path.insert(0, app_library_path)
47 from helper import dputhelper
50 dput_version = "dput 0.9.6"
52 config = None
53 upload_methods = {}
54 files_to_upload = []
55 simulate = unsigned_upload = delay_upload = 0
56 debug = dinstall = check_only = 0
57 config_file = ''
60 def import_upload_functions():
61     """ Import files from the /usr/share/dput and make them available. """
62     package_name = "methods"
63     modules_path = os.path.join(app_library_path, package_name)
64     modules_found = [
65             name for (__, name, ispkg) in
66             pkgutil.iter_modules([modules_path])
67             if not ispkg]
68     if debug:
69         print "D: modules_found: %r" % modules_found
70     for module_name in modules_found:
71         module = importlib.import_module("{package}.{module}".format(
72                 package=package_name, module=module_name))
73         if debug:
74             print "D: Module: %s (%r)" % (module_name, module)
75         method_name = module_name
76         if debug:
77             print "D: Method name: %s" % method_name
79         register_upload_function(method_name, module.upload)
82 def register_upload_function(name, function):
83     """ Add function to upload_methods dictionary using key of name. """
84     global upload_methods
86     upload_methods[name] = function
89 def parse_changes(chg_fd):
90     """ Parse the changes file. """
91     check = chg_fd.read(5)
92     if check != '-----':
93         chg_fd.seek(0)
94     else:
95         # found a PGP header, gonna ditch the next 3 lines
96         # eat the rest of the line
97         chg_fd.readline()
98         # Hash: SHA1
99         chg_fd.readline()
100         # empty line
101         chg_fd.readline()
102     if not chg_fd.readline().find('Format') != -1:
103         chg_fd.readline()
104     changes = rfc822.Message(chg_fd)
105     for a in changes.dict['files'].split('\n'):
106         if len(a.split()) != 5:
107             sys.stderr.write("Invalid Files line in .changes:\n  %s\n" % a)
108             sys.exit(1)
109     return changes
112 def read_configs(extra_config, debug):
113     """ Read configuration settings from config files.
115         Read configs in this order:
116         * If specified on the command line, only read `extra_config`.
117         * Otherwise, read ‘/etc/dput.cf’ then ‘~/.dput.cf’.
118         The config parser will layer values.
120         """
121     global config
123     config = ConfigParser.ConfigParser()
125     config.set('DEFAULT', 'login', 'username')
126     config.set('DEFAULT', 'method', 'scp')
127     config.set('DEFAULT', 'hash', 'md5')
128     config.set('DEFAULT', 'allow_unsigned_uploads', '0')
129     config.set('DEFAULT', 'allow_dcut', '0')
130     config.set('DEFAULT', 'distributions', '')
131     config.set('DEFAULT', 'allowed_distributions', '')
132     config.set('DEFAULT', 'run_lintian', '0')
133     config.set('DEFAULT', 'run_dinstall', '0')
134     config.set('DEFAULT', 'check_version', '0')
135     config.set('DEFAULT', 'scp_compress', '0')
136     config.set('DEFAULT', 'default_host_main', '')
137     config.set('DEFAULT', 'post_upload_command', '')
138     config.set('DEFAULT', 'pre_upload_command', '')
139     config.set('DEFAULT', 'ssh_config_options', '')
140     config.set('DEFAULT', 'passive_ftp', '1')
141     config.set('DEFAULT', 'progress_indicator', '0')
142     config.set('DEFAULT', 'delayed', '')
144     if extra_config:
145         config_files = (extra_config,)
146     else:
147         config_files = ('/etc/dput.cf', os.path.expanduser("~/.dput.cf"))
148     fd = None
149     for config_file in config_files:
150         try:
151             fd = open(config_file)
152         except IOError as e:
153             if debug:
154                 sys.stderr.write("%s: %s, skipping\n" % (e[1], config_file))
155             continue
156         if debug:
157             print "D: Parsing Configuration File %s" % config_file
158         try:
159             config.readfp(fd)
160         except ConfigParser.ParsingError as e:
161             sys.stderr.write("Error parsing config file:\n%s\n" % str(e))
162             sys.exit(1)
163         fd.close()
164     if fd is None:
165         sys.stderr.write(
166                 "Error: Could not open any configfile, tried %s\n"
167                 % (', '.join(config_files)))
168         sys.exit(1)
169     # only check for fqdn and incoming dir, rest have reasonable defaults
170     error = 0
171     for section in config.sections():
172         if config.get(section, 'method') == 'local':
173             config.set(section, 'fqdn', 'localhost')
174         if (
175                 not config.has_option(section, 'fqdn') and
176                 config.get(section, 'method') != 'local'):
177             sys.stderr.write(
178                     "Config error: %s must have a fqdn set\n" % section)
179             error = 1
180         if not config.has_option(section, 'incoming'):
181             sys.stderr.write(
182                     "Config error: %s must have an incoming directory set\n"
183                     % section)
184             error = 1
185     if error:
186         sys.exit(1)
189 hexStr = string.hexdigits
192 def hexify_string(string):
193     """ Convert a hex string into readable characters. """
194     char = ''
195     for c in string:
196         char = char + hexStr[(ord(c) >> 4) & 0xF] + hexStr[ord(c) & 0xF]
197     return char
200 def checksum_test(filename, hash):
201     """ Generate a checksum for a file.
203         Currently supports md5, sha1. ripemd may come in the future.
205         """
206     try:
207         file_to_test = open(filename, 'r')
208     except IOError:
209         print "Can't open %s" % filename
210         sys.exit(1)
212     if hash == 'md5':
213         hash_type = md5
214     else:
215         hash_type = sha1
217     check_obj = hash_type()
219     while 1:
220         data = file_to_test.read(65536)
221         if len(data) == 0:
222             break
223         check_obj.update(data)
225     file_to_test.close()
226     checksum = hexify_string(check_obj.digest())
228     return checksum
231 def check_signature(filename):
232     """ Verify the GnuPG signature on a file. """
233     if os.access(filename, os.R_OK):
234         if os.access("/usr/bin/gpg", os.X_OK):
235             stream = os.popen(
236                     "/usr/bin/gpg --status-fd 1 --verify --batch %s"
237                     % filename).read()
238             if stream.count('[GNUPG:] GOODSIG'):
239                 print "Good signature on %s." % filename
240             elif stream.count('[GNUPG:] BADSIG'):
241                 print "Bad signature on %s." % filename
242                 sys.exit(1)
243             elif stream.count('[GNUPG:] ERRSIG'):
244                 print "Error verifying signature on %s." % filename
245                 sys.exit(1)
246             elif stream.count('[GNUPG:] NODATA'):
247                 print "No signature on %s." % filename
248                 sys.exit(1)
249             else:
250                 print "Error in finding signature verification status."
251         else:
252             print "Can't verify signature on %s without GnuPG" % filename
253             print "If you are still using PGP, please read this:"
254             print "http://www.gnupg.org/gph/en/pgp2x.html"
255             sys.exit(1)
256     else:
257         print "Can't read %s" % filename
258         sys.exit(1)
261 def check_upload_variant(changes, debug):
262     """ Check if this is a binary_upload only or not. """
263     binary_upload = 0
264     if 'architecture' in changes.dict:
265         arch = changes.dict['architecture']
266         if debug:
267             print "D: Architecture: %s" % arch
268         if arch.find('source') < 0:
269             if debug:
270                 print "D: Doing a binary upload only."
271             binary_upload = 1
272     return binary_upload
275 def verify_signature(
276         host, changes_file, dsc_file, check_only, debug,
277         unsigned_upload, binary_upload):
278     """ Check the signature on the two files given via function call. """
280     if debug:
281         print "D: .changes-File: %s" % changes_file
282         print "D: .dsc-File: %s" % dsc_file
283     if ((check_only or config.getboolean(host, 'allow_unsigned_uploads') == 0)
284             and unsigned_upload == 0):
285         print "Checking signature on .changes"
286         check_signature(changes_file)
287         if not binary_upload:
288             print "Checking signature on .dsc"
289             check_signature(dsc_file)
292 def source_check(changes, debug):
293     """ Check if a source tarball has to be included in the package or not. """
294     include_orig = include_tar = 0
295     if 'version' in changes.dict:
296         version = changes.dict['version']
297         if debug:
298             print "D: Package Version: %s" % version
299         # versions with a dash in them are for non-native only
300         if version.find('-') == -1:
301             # debian native
302             include_tar = 1
303         else:
304             if version.find(':') > 0:
305                 if debug:
306                     print "D: Epoch found"
307                 epoch, version = version.split(':', 1)
308             pos = version.rfind('-')
309             upstream_version = version[0:pos]
310             debian_version = version[pos + 1:]
311             if debug:
312                 print "D: Upstream Version: %s" % upstream_version
313                 print "D: Debian Version: %s" % debian_version
314             if (
315                     debian_version == '0.1' or debian_version == '1'
316                     or debian_version == '1.1'):
317                 include_orig = 1
318             else:
319                 include_tar = 1
320     return (include_orig, include_tar)
323 def verify_files(
324         path, filename, host, check_only, check_version,
325         unsigned_upload, debug):
326     """ Run some tests on the files to verify that they are in good shape. """
328     file_seen = include_orig_tar_gz = include_tar_gz = binary_only = 0
330     name_of_file = filename
332     change_file = os.path.join(path, name_of_file)
334     if debug:
335         print "D: Validating contents of changes file %s" % change_file
336     try:
337         chg_fd = open(change_file, 'r')
338     except IOError:
339         print "Can't open %s" % change_file
340         sys.exit(1)
341     changes = parse_changes(chg_fd)
342     chg_fd.close
344     # Find out if it's a binary only upload or not
345     binary_upload = check_upload_variant(changes, debug)
347     if binary_upload:
348         dsc_file = ''
349     else:
350         dsc_file = None
351         for file in changes.dict['files'].split('\n'):
352             # filename only
353             filename = string.split(file)[4]
354             if filename.find('.dsc') != -1:
355                 if debug:
356                     print "D: dsc-File: %s" % filename
357                 dsc_file = os.path.join(path, filename)
358         if not dsc_file:
359             sys.stderr.write("Error: no dsc file found in sourceful upload\n")
360             sys.exit(1)
362     # Run the check to verify that the package has been tested.
363     try:
364         if config.getboolean(host, 'check_version') == 1 or check_version:
365             version_check(path, changes, debug)
366     except ConfigParser.NoSectionError as e:
367         sys.stderr.write("Error in config file:\n%s\n" % str(e))
368         sys.exit(1)
370     # Verify the signature of the maintainer
371     verify_signature(
372             host, change_file, dsc_file, check_only, debug,
373             unsigned_upload, binary_upload)
375     # Check the sources
376     (include_orig_tar_gz, include_tar_gz) = source_check(changes, debug)
378     # Check md5sum and the size
379     file_list = changes.dict['files'].split('\n')
380     hash_to_use = config.get('DEFAULT', 'hash')
381     for line in file_list:
382         (check_sum, size, section, priority, file) = line.split()
383         file_to_upload = os.path.join(path, file)
384         if debug:
385             print "D: File to upload: %s" % file_to_upload
386         if checksum_test(file_to_upload, hash_to_use) != check_sum:
387             if debug:
388                 print "D: Checksum from .changes: %s" % check_sum
389                 print (
390                         "D: Generated Checksum: %s" %
391                         checksum_test(file_to_upload, hash_to_use))
392             print "Checksum doesn't match for %s" % file_to_upload
393             sys.exit(1)
394         else:
395             if debug:
396                 print "D: Checksum for %s is fine" % file_to_upload
397         if os.stat(file_to_upload)[stat.ST_SIZE] != int(size):
398             if debug:
399                 print "D: size from .changes: %s" % size
400                 print (
401                         "D: calculated size: %s" %
402                         os.stat(file_to_upload)[stat.ST_SIZE])
403             print "size doesn't match for %s" % file_to_upload
405         files_to_upload.append(file_to_upload)
407     # Check filenames
408     for file in files_to_upload:
409         if file[-12:] == '.orig.tar.gz' and not include_orig_tar_gz:
410             if debug:
411                 print "D: Filename: %s" % file
412                 print "D: Suffix: %s" % file[-12:]
413             print (
414                     "Package includes an .orig.tar.gz file although"
415                     " the debian revision suggests")
416             print (
417                     "that it might not be required."
418                     " Multiple uploads of the .orig.tar.gz may be")
419             print "rejected by the upload queue management software."
420         elif (
421                 file[-7:] == '.tar.gz' and not include_tar_gz
422                 and not include_orig_tar_gz):
423             if debug:
424                 print "D: Filename: %s" % file
425                 print "D: Suffix: %s" % file[-7:]
426             print (
427                     "Package includes a .tar.gz file although"
428                     " the version suggests that it might")
429             print (
430                     "not be required."
431                     " Multiple uploads of the .tar.gz may be rejected by the")
432             print "upload queue management software."
434     distribution = changes.get('distribution')
435     allowed_distributions = config.get(host, 'allowed_distributions')
436     if distribution and allowed_distributions:
437         if debug:
438             print (
439                     "D: Checking: distribution %s matches %s"
440                     % (distribution, allowed_distributions))
441         if not re.match(allowed_distributions, distribution):
442             raise dputhelper.DputUploadFatalException(
443                     "Error: uploading files for distribution %s to %s"
444                     " not allowed."
445                     % (distribution, host))
447     if debug:
448         print "D: File to upload: %s" % change_file
449     files_to_upload.append(change_file)
452 def print_config(config, debug):
453     """ Print the configuration and exit. """
454     print
455     config.write(sys.stdout)
456     print
459 def create_upload_file(package, host, path, files_to_upload, debug):
460     """ Write a logfile of the upload and call it ‘.upload’. """
462     # only need first part
463     base = os.path.splitext(package)[0]
464     logfile_name = os.path.join(path, base + '.' + host + '.upload')
465     if config.get(host, 'method') == 'local':
466         fqdn = 'localhost'
467     else:
468         fqdn = config.get(host, 'fqdn')
469     if debug:
470         print "D: Writing logfile: %s" % logfile_name
471     try:
472         if os.access(logfile_name, os.R_OK):
473             logfile_fd = open(logfile_name, 'a')
474         else:
475             logfile_fd = open(logfile_name, 'w')
476     except IOError:
477         sys.stderr.write("Could not write %s\n" % logfile_name)
478         sys.exit(1)
480     for file in files_to_upload:
481         entry_for_logfile = (
482                 'Successfully uploaded ' + os.path.basename(file) +
483                 ' to ' + fqdn + ' for ' + host + '.\n')
484         logfile_fd.write(entry_for_logfile)
485     logfile_fd.close()
488 def run_lintian_test(changes_file):
489     """ Run lintian on the changes file and stop if it finds errors. """
491     if os.access(changes_file, os.R_OK):
492         if os.access("/usr/bin/lintian", os.R_OK):
493             old_signal = signal.signal(signal.SIGPIPE, signal.SIG_DFL)
494             print "Package is now being checked with lintian."
495             if dputhelper.spawnv(
496                     os.P_WAIT, "/usr/bin/lintian",
497                     ['lintian', '-i', changes_file]):
498                 print
499                 print (
500                         "Lintian says this package is not compliant" +
501                         " with the current policy.")
502                 print "Please check the current policy and your package."
503                 print "Also see lintian documentation about overrides."
504                 sys.exit(1)
505             else:
506                 signal.signal(signal.SIGPIPE, old_signal)
507                 return 0
508         else:
509             print "lintian is not installed, skipping package test."
510     else:
511         print "Can't read %s" % changes_file
512         sys.exit(1)
515 def guess_upload_host(path, filename):
516     """ Guess the host where the package should be uploaded to.
518         This is based on information from the changes file.
520         """
521     non_us = 0
522     distribution = ""
523     dist_re = re.compile(r'^Distribution: (.*)')
525     name_of_file = filename
526     changes_file = os.path.join(path, name_of_file)
528     try:
529         changes_file_fd = open(changes_file, 'r')
530     except IOError:
531         print "Can't open %s" % changes_file
532         sys.exit(1)
533     lines = changes_file_fd.readlines()
534     for line in lines:
535         match = dist_re.search(line)
536         if match:
537             distribution = match.group(1)
539     # Try to guess a host based on the Distribution: field
540     if distribution:
541         for section in config.sections():
542             host_dists = config.get(section, 'distributions')
543             if not host_dists:
544                 continue
545             for host_dist in host_dists.split(','):
546                 if distribution == host_dist.strip():
547                     if debug:
548                         print (
549                                 "D: guessing host %s based on distribution %s"
550                                 % (section, host_dist))
551                     return section
553     if len(config.get('DEFAULT', 'default_host_main')) != 0:
554         print "Trying to upload package to %s" % config.get(
555                 'DEFAULT', 'default_host_main')
556         return config.get('DEFAULT', 'default_host_main')
557     else:
558         print "Trying to upload package to ftp-master (ftp.upload.debian.org)"
559         return "ftp-master"
562 def dinstall_caller(filename, host, login, incoming, debug):
563     """ Run dinstall in test-mode and present the output to the user.
565         This is so the user can see if the package would be installed
566         or not.
568         """
569     command = [
570             'ssh', '%s@%s' % (login, config.get(host, 'fqdn')),
571             'cd', '%s' % incoming,
572             ';', 'dinstall', '-n', '%s' % filename]
573     if debug:
574         print "D: Logging into %s@%s:%s" % (login, host, incoming)
575         print "D: dinstall -n %s" % filename
576     if config.getboolean(host, 'run_dinstall') == 1 or dinstall:
577         if dputhelper.spawnv(os.P_WAIT, '/usr/bin/ssh', command):
578             print (
579                     "Error occured while trying to connect, or while " +
580                     "attempting to run dinstall.")
581             sys.exit(1)
584 def version_check(path, changes, debug):
585     """ Check if the caller has installed the package also on his system.
587         This is for testing purposes before uploading it. If not, we
588         reject the upload.
590         """
591     files_to_check = []
593     # Get arch
594     (dpkg_stdin, dpkg_stdout, dpkg_stderr) = os.popen3(
595             'dpkg --print-architecture')
596     dpkg_stdin.close()
597     dpkg_architecture = dpkg_stdout.read().strip()
598     dpkg_stdout.close()
599     dpkg_stderr_output = dpkg_stderr.read()
600     dpkg_stderr.close()
601     if debug and dpkg_stderr_output:
602         print "D: dpkg-architecture stderr output:", repr(dpkg_stderr_output)
603     if debug:
604         print "D: detected architecture: '%s'" % dpkg_architecture
606     # Get filenames of deb files:
607     for file in changes.dict['files'].split('\n'):
608         filename = os.path.join(path, string.split(file)[4])
609         if filename.endswith('.deb'):
610             if debug:
611                 print "D: Debian Package: %s" % filename
612             (dpkg_stdin, dpkg_stdout, dpkg_stderr) = os.popen3(
613                     'dpkg --field %s' % filename)
614             dpkg_stdin.close()
615             dpkg_output = rfc822.Message(dpkg_stdout)
616             dpkg_stdout.close()
617             dpkg_stderr_output = dpkg_stderr.read()
618             dpkg_stderr.close()
619             if debug and dpkg_stderr_output:
620                 print "D: dpkg stderr output:", repr(dpkg_stderr_output)
621             if (
622                     dpkg_architecture
623                     and dpkg_output['architecture'] not in [
624                         'all', dpkg_architecture]):
625                 if debug:
626                     print (
627                             "D: not install-checking %s due to arch mismatch"
628                             % filename)
629             else:
630                 package_name = dpkg_output['package']
631                 version_number = dpkg_output['version']
632                 if debug:
633                     print "D: Package to Check: %s" % package_name
634                 if debug:
635                     print "D: Version to Check: %s" % version_number
636                 files_to_check.append((package_name, version_number))
638     for file, version_to_check in files_to_check:
639         if debug:
640             print "D: Name of Package: %s" % file
641         dpkg_stdin, dpkg_stdout, dpkg_stderr = os.popen3('dpkg -s %s' % file)
642         dpkg_stdin.close()
643         dpkg_output = rfc822.Message(dpkg_stdout)
644         dpkg_stdout.close()
645         dpkg_stderr_output = dpkg_stderr.read()
646         dpkg_stderr.close()
647         if debug and dpkg_stderr_output:
648             print "D: dpkg stderr output:", repr(dpkg_stderr_output)
649         if 'version' in dpkg_output:
650             installed_version = dpkg_output.dict['version']
651             if debug:
652                 print "D: Installed-Version: %s" % installed_version
653             if debug:
654                 print (
655                     "D: Check-Version: %s" % version_to_check)
656             if installed_version != version_to_check:
657                 print (
658                         "Package to upload is not installed, but it appears " +
659                         "you have an older version installed.")
660         else:
661             print "Uninstalled Package. Test it before uploading it."
662             sys.exit(1)
665 def execute_command(host, debug, type):
666     """ Run a command that the user-defined in the config_file. """
668     lookup_command = type + '_upload_command'
669     if debug:
670         print "D: Command: %s" % config.get(host, lookup_command)
671     if os.system(config.get(host, lookup_command)):
672         raise dputhelper.DputUploadFatalException(
673                 "Error: %s upload command failed." % type)
676 def check_upload_logfile(
677         changes_file, host, check_only,
678         call_lintian, force_upload, debug):
679     """ Check if the user already put this package on the specified host. """
681     uploaded = 0
682     upload_logfile = changes_file[:-8] + '.' + host + '.upload'
683     if not check_only and not force_upload:
684         if not os.path.exists(upload_logfile):
685             return
686         try:
687             fd_logfile = open(upload_logfile)
688         except IOError:
689             print "Couldn't open %s" % upload_logfile
690             sys.exit(1)
691         for line in fd_logfile.readlines():
692             if config.get(host, 'method') == 'local':
693                 fqdn = 'localhost'
694             else:
695                 fqdn = config.get(host, 'fqdn')
696             if line.find(fqdn) != -1:
697                 uploaded = 1
698         if uploaded:
699             print (
700                     "Package has already been uploaded to %s on %s"
701                     % (host, fqdn))
702             print "Nothing more to do for %s" % changes_file
703             sys.exit(0)
705 # Help Message to print
706 USAGE = """Usage: dput [options] [host] <package(s).changes>
707  Supported options (see man page for long forms):
708    -c: Config file to parse.
709    -d: Enable debug messages.
710    -D: Run dinstall after upload.
711    -e: Upload to a delayed queue. Takes an argument from 0 to 15.
712    -f: Force an upload.
713    -h: Display this help message.
714    -H: Display a list of hosts from the config file.
715    -l: Run lintian before upload.
716    -U: Do not write a .upload file after uploading.
717    -o: Only check the package.
718    -p: Print the configuration.
719    -P: Use passive mode for ftp uploads.
720    -s: Simulate the upload only.
721    -u: Don't check GnuPG signature.
722    -v: Display version information.
723    -V: Check the package version and then upload it.
727 def main():
728     """ Main function, no further comment needed. :) """
730     global simulate
731     global debug
732     global check_only
733     global dinstall
734     global unsigned_upload
735     global config_file
736     global delay_upload
738     check_version = config_print = force_upload = 0
739     call_lintian = no_upload_log = config_host_list = 0
740     ftp_passive_mode = 0
741     preferred_host = ''
743     # Parse Command Line Options.
744     (opts, args) = dputhelper.getopt(
745             sys.argv[1:],
746             'c:dDe:fhHlUopPsuvV', [
747                 'debug', 'dinstall', 'check-only',
748                 'check-version', 'config=', 'force', 'help',
749                 'host-list', 'lintian', 'no-upload-log',
750                 'passive', 'print', 'simulate', 'unchecked',
751                 'delayed=', 'version'])
752     for option, arg in opts:
753         if option in ('-h', '--help'):
754             print USAGE
755             return
756         elif option in ('-v', '--version'):
757             print dput_version
758             return
759         elif option in ('-d', '--debug'):
760             debug = 1
761         elif option in ('-D', '--dinstall'):
762             dinstall = 1
763         elif option in ('-c', '--config'):
764             config_file = arg
765         elif option in ('-f', '--force'):
766             force_upload = 1
767         elif option in ('-H', '--host-list'):
768             config_host_list = 1
769         elif option in ('-l', '--lintian'):
770             call_lintian = 1
771         elif option in ('-U', '--no-upload-log'):
772             no_upload_log = 1
773         elif option in ('-o', '--check-only'):
774             check_only = 1
775         elif option in ('-p', '--print'):
776             config_print = 1
777         elif option in ('-P', '--passive'):
778             ftp_passive_mode = 1
779         elif option in ('-s', '--simulate'):
780             simulate = 1
781         elif option in ('-u', '--unchecked'):
782             unsigned_upload = 1
783         elif option in ('-e', '--delayed'):
784             if arg in map(str, range(16)):
785                 delay_upload = arg
786             else:
787                 print (
788                         "Incorrect delayed argument,"
789                         " dput only understands 0 to 15.")
790                 sys.exit(1)
791         elif option in ('-V', '--check_version'):
792             check_version = 1
794     # Always print the version number in the debug output
795     # so that in case of bugreports, we know which version
796     # the user has installed
797     if debug:
798         print "D: %s" % dput_version
800     # Try to get the login from the enviroment
801     if 'USER' in os.environ:
802         login = os.environ['USER']
803         if debug:
804             print "D: Login: %s" % login
805     else:
806         print "$USER not set, will use login information."
807         # Else use the current username
808         login = pwd.getpwuid(os.getuid())[0]
809         if debug:
810             print "D: User-ID: %s" % os.getuid()
811             print "D: Login: %s" % login
813     # Start Config File Parsing.
814     read_configs(config_file, debug)
816     if config_print:
817         print_config(config, debug)
818         sys.exit(0)
820     if config_host_list:
821         print
822         print "Default Method: %s" % config.get('DEFAULT', 'method')
823         print
824         for section in config.sections():
825             distributions = ""
826             if config.get(section, 'distributions'):
827                 distributions = (
828                         ", distributions: %s" %
829                         config.get(section, 'distributions'))
830             print "%s => %s  (Upload method: %s%s)" % (
831                     section,
832                     config.get(section, 'fqdn'), config.get(section, 'method'),
833                     distributions)
834         print
835         sys.exit(0)
837     # Process further command line options.
838     if len(args) == 0:
839         print "No package or host has been provided, see dput -h"
840         sys.exit(0)
841     elif len(args) == 1 and not check_only:
842         package_to_upload = args[0:]
843     else:
844         if not check_only:
845             if debug:
846                 print "D: Checking if a host was named on the command line."
847             if config.has_section(args[0]):
848                 if debug:
849                     print "D: Host %s found in config" % args[0]
850                 # Host was also named, so only the rest will be a list
851                 # of packages to upload.
852                 preferred_host = args[0]
853                 package_to_upload = args[1:]
854             elif (
855                     not config.has_section(args[0])
856                     and not args[0].endswith('.changes')):
857                 sys.stderr.write("No host %s found in config\n" % args[0])
858                 if args[0] == 'gluck_delayed':
859                     sys.stderr.write("""
860     The delayed upload queue has been moved back to
861     ftp-master (aka ftp.upload.debian.org).
862     """)
863                 sys.exit(1)
864             else:
865                 if debug:
866                     print "D: No host named on command line."
867                 # Only packages have been named on the command line.
868                 preferred_host = ''
869                 package_to_upload = args[0:]
870         else:
871             if debug:
872                 print "D: Checking for the package name."
873             if config.has_section(args[0]):
874                 print "D: Host %s found in config." % args[0]
875                 preferred_host = args[0]
876                 package_to_upload = args[1:]
877             elif not config.has_section(args[0]):
878                 print "D: No host %s found in config" % args[0]
879                 package_to_upload = args[0:]
881     # Now Import the Upload functions
882     import_upload_functions()
884     # Run the same checks for all packages that have been given on
885     # the command line
886     for package_name in package_to_upload:
887         # Check that a .changes file was given on the command line
888         # and no matching .upload file exists.
889         if package_name[-8:] != '.changes':
890             print "Not a .changes file."
891             print "Please select a .changes file to upload."
892             print "Tried to upload: %s" % package_name
893             sys.exit(1)
894         files_to_upload[:] = []
896         # Construct the package name for further usage.
897         path, name_of_package = os.path.split(package_name)
898         if path == '':
899             path = os.getcwd()
901         # Define the host to upload to.
902         if preferred_host == '':
903             host = guess_upload_host(path, name_of_package)
904         else:
905             host = preferred_host
907         # Check if we already did this upload or not
908         check_upload_logfile(
909                 package_name, host, check_only,
910                 call_lintian, force_upload, debug)
912         # Run the change file tests.
913         verify_files(
914                 path, name_of_package, host, check_only, check_version,
915                 unsigned_upload, debug)
917         # Run the lintian test if the user asked us to do so.
918         if (
919                 call_lintian or
920                 config.getboolean(host, 'run_lintian') == 1):
921             run_lintian_test(os.path.join(path, name_of_package))
922         elif check_only:
923             print "Warning: The option -o does not automatically include "
924             print "a lintian run any more. Please use the option -ol if "
925             print "you want to include running lintian in your checking."
927         # don't upload, skip to the next item
928         if check_only:
929             print "Package checked by dput."
930             continue
932         # Pre-Upload Commands
933         if len(config.get(host, 'pre_upload_command')) != 0:
934             type = 'pre'
935             execute_command(host, debug, type)
937         # Check the upload methods that we have as default and per host
938         if debug:
939             print (
940                     "D: Default Method: %s" %
941                     config.get('DEFAULT', 'method'))
942         if config.get('DEFAULT', 'method') not in upload_methods:
943             print (
944                     "Unknown upload method: %s" %
945                     config.get('DEFAULT', 'method'))
946             sys.exit(1)
947         if debug:
948             print "D: Host Method: %s" % config.get(host, 'method')
949         if config.get(host, 'method') not in upload_methods:
950             print "Unknown upload method: %s" % config.get(host, 'method')
951             sys.exit(1)
953         # Inspect the Config and set appropriate upload method
954         if not config.get(host, 'method'):
955             method = config.get('DEFAULT', 'method')
956         else:
957             method = config.get(host, 'method')
959         # Check now the login and redefine it if needed
960         if (
961                 len(config.get(host, 'login')) != 0 and
962                 config.get(host, 'login') != 'username'):
963             login = config.get(host, 'login')
964             if debug:
965                 print "D: Login %s from section %s used" % (login, host)
966         elif (
967                 len(config.get('DEFAULT', 'login')) != 0 and
968                 config.get('DEFAULT', 'login') != 'username'):
969             login = config.get('DEFAULT', 'login')
970             if debug:
971                 print "D: Default login %s used" % login
972         else:
973             if debug:
974                 print (
975                         "D: Neither host %s nor default login used. Using %s"
976                         % (host, login))
978         # Messy, yes. But it isn't referenced by the upload method anyway.
979         if config.get(host, 'method') == 'local':
980             fqdn = 'localhost'
981         else:
982             fqdn = config.get(host, 'fqdn')
983         incoming = config.get(host, 'incoming')
985         # if delay_upload wasn't passed via -e/--delayed
986         if not delay_upload:
987             delay_upload = config.get(host, 'delayed')
988             if not delay_upload:
989                 delay_upload = config.get('DEFAULT', 'delayed')
991         if delay_upload:
992             if int(delay_upload) == 0:
993                 print "Uploading to DELAYED/0-day."
994             if incoming[-1] == '/':
995                 first_char = ''
996             else:
997                 first_char = '/'
998             incoming += first_char + 'DELAYED/' + delay_upload + '-day'
999             delayed = ' [DELAYED/' + delay_upload + ']'
1000         else:
1001             delayed = ''
1003         # Do the actual upload
1004         if not simulate:
1005             print (
1006                     "Uploading to %s%s (via %s to %s):"
1007                     % (host, delayed, method, fqdn))
1008             if debug:
1009                 print "D: FQDN: %s" % fqdn
1010                 print "D: Login: %s" % login
1011                 print "D: Incoming: %s" % incoming
1012             progress = config.getint(host, 'progress_indicator')
1013             if not os.isatty(1):
1014                 progress = 0
1015             if method == 'ftp':
1016                 if ':' in fqdn:
1017                     fqdn, port = fqdn.rsplit(":", 1)
1018                 else:
1019                     port = 21
1020                 ftp_mode = config.getboolean(host, 'passive_ftp')
1021                 if ftp_passive_mode == 1:
1022                     ftp_mode = 1
1023                 if debug:
1024                     print "D: FTP port: %s" % port
1025                     if ftp_mode == 1:
1026                         print "D: Using passive ftp"
1027                     else:
1028                         print "D: Using active ftp"
1029                 upload_methods[method](
1030                         fqdn, login, incoming,
1031                         files_to_upload, debug, ftp_mode,
1032                         progress=progress, port=port)
1033             elif method == 'scp':
1034                 if debug and config.getboolean(host, 'scp_compress'):
1035                     print "D: Setting compression for scp"
1036                 scp_compress = config.getboolean(host, 'scp_compress')
1037                 ssh_config_options = [
1038                         y for y in (
1039                             x.strip() for x in
1040                             config.get(host, 'ssh_config_options').split('\n'))
1041                         if y]
1042                 if debug:
1043                     print (
1044                             "D: ssh config options:\n  "
1045                             + '\n  '.join(ssh_config_options))
1046                 upload_methods[method](
1047                         fqdn, login, incoming,
1048                         files_to_upload, debug, scp_compress,
1049                         ssh_config_options)
1050             else:
1051                 upload_methods[method](
1052                         fqdn, login, incoming,
1053                         files_to_upload, debug, 0, progress=progress)
1054         # Or just simulate it.
1055         else:
1056             for file in files_to_upload:
1057                 print 'Uploading with %s: %s to %s:%s' % (
1058                         method, file, fqdn, incoming)
1060         # Create the logfile after the package has
1061         # been put into the archive.
1062         if not simulate:
1063             if not no_upload_log:
1064                 create_upload_file(
1065                         name_of_package, host, path,
1066                         files_to_upload, debug)
1067             print "Successfully uploaded packages."
1068         else:
1069             print "Simulated upload."
1071         # Run dinstall if the user asked us to do so.
1072         if debug:
1073             print "D: dinstall: %s" % dinstall
1074             print (
1075                     "D: Host Config: %s"
1076                     % config.getboolean(host, 'run_dinstall'))
1077         if config.getboolean(host, 'run_dinstall') == 1 or dinstall:
1078             if not simulate:
1079                 dinstall_caller(name_of_package, host, login, incoming, debug)
1080             else:
1081                 print "Will run dinstall now."
1083         # Post-Upload Command
1084         if len(config.get(host, 'post_upload_command')) != 0:
1085             type = 'post'
1086             execute_command(host, debug, type)
1088     return
1091 if __name__ == '__main__':
1092     try:
1093         main()
1094     except KeyboardInterrupt:
1095         print "Exiting due to user interrupt."
1096         sys.exit(1)
1097     except dputhelper.DputException as e:
1098         sys.stderr.write("%s\n" % e)
1099         sys.exit(1)
1102 # Local variables:
1103 # coding: utf-8
1104 # mode: python
1105 # End:
1106 # vim: fileencoding=utf-8 filetype=python :