2 # -*- coding: utf-8; -*-
5 # Part of ‘dput’, a Debian package upload toolkit.
7 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
8 # Copyright © 2008–2013 Y Giridhar Appaji Nag <appaji@debian.org>
9 # Copyright © 2004–2009 Thomas Viehmann <tv@beamnet.de>
10 # Copyright © 2000–2004 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 """ dcut — Debian command upload tool. """
38 from .helper
import dputhelper
42 validcommands
= ('rm', 'cancel', 'reschedule')
45 def make_usage_message():
46 """ Make the program usage help message. """
47 text
= textwrap
.dedent("""\
48 Usage: %s [options] [host] command [, command]
49 Supported options (see man page for long forms):
50 -c file Config file to parse.
51 -d Enable debug messages.
52 -h Display this help message.
53 -s Simulate the commands file creation only.
54 -v Display version information.
56 Use maintainer information in "Uploader:" field.
58 Use this keyid for signing.
59 -O file Write commands to file.
60 -U file Upload specified commands file (presently no checks).
62 Upload a commands file to remove files listed in .changes.
63 Supported commands: mv, rm
64 (No paths or command-line options allowed on ftp-master.)
65 """) % (dputhelper
.get_progname(sys
.argv
))
72 'debug': 0, 'simulate': 0, 'config': None, 'host': None,
73 'uploader': None, 'keyid': None, 'passive': 0,
74 'filetocreate': None, 'filetoupload': None, 'changes': None}
75 progname
= dputhelper
.get_progname(sys
.argv
)
76 version
= dputhelper
.get_distribution_version()
78 # enable debugging very early
79 if ('-d' in sys
.argv
[1:] or '--debug' in sys
.argv
[1:]):
81 sys
.stdout
.write("D: %s %s\n" % (progname
, version
))
83 # check environment for maintainer
86 "D: trying to get maintainer email from environment\n")
88 if 'DEBEMAIL' in os
.environ
:
89 if os
.environ
['DEBEMAIL'].find('<') < 0:
90 options
['uploader'] = os
.environ
.get("DEBFULLNAME", '')
91 if options
['uploader']:
92 options
['uploader'] += ' '
93 options
['uploader'] += '<%s>' % (os
.environ
['DEBEMAIL'])
95 options
['uploader'] = os
.environ
['DEBEMAIL']
98 "D: Uploader from env: %s\n" % (options
['uploader']))
99 elif 'EMAIL' in os
.environ
:
100 if os
.environ
['EMAIL'].find('<') < 0:
101 options
['uploader'] = os
.environ
.get("DEBFULLNAME", '')
102 if options
['uploader']:
103 options
['uploader'] += ' '
104 options
['uploader'] += '<%s>' % (os
.environ
['EMAIL'])
106 options
['uploader'] = os
.environ
['EMAIL']
109 "D: Uploader from env: %s\n" % (options
['uploader']))
112 sys
.stdout
.write("D: Guessing uploader\n")
113 pwrec
= pwd
.getpwuid(os
.getuid())
115 fullname
= pwrec
[4].split(',')[0]
117 hostname
= open('/etc/mailname').read().strip()
123 "D: Guessing uploader: /etc/mailname was a failure\n")
124 hostname_subprocess
= subprocess
.Popen(
125 "/bin/hostname --fqdn",
126 shell
=True, stdout
=subprocess
.PIPE
)
127 hostname_stdout
= dputhelper
.make_text_stream(
128 hostname_subprocess
.stdout
)
129 hostname
= hostname_stdout
.read().strip()
131 options
['uploader'] = (
132 "%s <%s@%s>" % (fullname
, username
, hostname
))
135 "D: Guessed uploader: %s\n" % (options
['uploader']))
138 sys
.stdout
.write("D: Couldn't guess uploader\n")
139 # parse command line arguments
140 (opts
, arguments
) = dputhelper
.getopt(
142 'c:dDhsvm:k:PU:O:i:', [
144 'help', 'simulate', 'version', 'host=',
145 'maintainteraddress=', 'keyid=',
146 'passive', 'upload=', 'output=', 'input='
149 for (option
, arg
) in opts
:
152 'D: processing arg "%s", option "%s"\n' % (option
, arg
))
153 if option
in ('-h', '--help'):
154 sys
.stdout
.write(make_usage_message())
156 elif option
in ('-v', '--version'):
157 sys
.stdout
.write("%s %s\n" % (progname
, version
))
159 elif option
in ('-d', '--debug'):
161 elif option
in ('-c', '--config'):
162 options
['config'] = arg
163 elif option
in ('-m', '--maintaineraddress'):
164 options
['uploader'] = arg
165 elif option
in ('-k', '--keyid'):
166 options
['keyid'] = arg
167 elif option
in ('-s', '--simulate'):
168 options
['simulate'] = 1
169 elif option
in ('-P', '--passive'):
170 options
['passive'] = 1
171 elif option
in ('-U', '--upload'):
172 options
['filetoupload'] = arg
173 elif option
in ('-O', '--output'):
174 options
['filetocreate'] = arg
175 elif option
== '--host':
176 options
['host'] = arg
177 elif option
in ('-i', '--input'):
178 options
['changes'] = arg
181 "%s internal error: Option %s, argument %s unknown\n"
182 % (progname
, option
, arg
))
185 if not options
['host'] and arguments
and arguments
[0] not in validcommands
:
186 options
['host'] = arguments
[0]
189 'D: first argument "%s" treated as host\n'
193 # we don't create command files without uploader
195 not options
['uploader']
196 and (options
['filetoupload'] or options
['changes'])):
198 "%s error: command file cannot be created"
199 " without maintainer email\n"
202 '%s please set $DEBEMAIL, $EMAIL'
203 ' or use the "-m" option\n'
204 % (len(progname
) * ' '))
207 return options
, arguments
210 def parse_queuecommands(arguments
, options
, config
):
212 # want to consume a copy of arguments
213 arguments
= arguments
[:]
217 if arguments
[0] in validcommands
:
218 curarg
= [arguments
[0]]
219 if arguments
[0] == 'rm':
220 if len(arguments
) > 1 and arguments
[1] == '--nosearchdirs':
223 curarg
.append('--searchdirs')
225 if not curarg
and arguments
[0] != 0:
227 'Error: Could not parse commands at "%s"\n'
230 if str(arguments
[0])[-1] in (',', ';', 0):
231 curarg
.append(arguments
[0][0:-1])
233 if arguments
[0] in (',', ';', 0) and curarg
:
234 # TV-TODO: syntax check for #args etc.
237 'D: Successfully parsed command "%s"\n'
238 % (' '.join(curarg
)))
239 commands
.append(' '.join(curarg
))
242 # TV-TODO: maybe syntax check the arguments here
243 curarg
.append(arguments
[0])
246 sys
.stderr
.write("Error: no arguments given, see dcut -h\n")
251 def write_commands(commands
, options
, config
, tempdir
):
252 """ Write a file of commands for the upload queue daemon.
254 :param commands: Commands to write, as a sequence of text
256 :param options: Program configuration, as a mapping of options
258 :param config: `ConfigParser` instance for this application.
259 :param tempdir: Filesystem path to directory for temporary files.
260 :return: Filesystem path of file which was written.
262 Write the specified sequence of commands to a file, in the
263 format required for the Debian upload queue management daemon.
265 Once writing is finished, the file is signed using the
268 If not specified in the configuration option 'filetocreate', a
269 default filename is generated. In either case, the resulting
270 filename is returned.
273 progname
= dputhelper
.get_progname(sys
.argv
)
274 if options
['filetocreate']:
275 filename
= options
['filetocreate']
278 str('').join(map(chr, range(256)))
279 + string
.ascii_letters
+ string
.digits
)
280 translationdest
= 256 * '_' + string
.ascii_letters
+ string
.digits
281 translationmap
= string
.maketrans(translationorig
, translationdest
)
282 uploadpartforname
= options
['uploader'].translate(translationmap
)
284 progname
+ '.%s.%d.%d.commands' %
285 (uploadpartforname
, int(time
.time()), os
.getpid()))
287 filename
= os
.path
.join(tempdir
, filename
)
288 f
= open(filename
, "w")
289 f
.write("Uploader: %s\n" % options
['uploader'])
290 f
.write("Commands:\n %s\n\n" % ('\n '.join(commands
)))
292 debsign_cmdline
= ['debsign']
293 debsign_cmdline
.append('-m%s' % options
['uploader'])
295 debsign_cmdline
.append('-k%s' % options
['keyid'])
296 debsign_cmdline
.append('%s' % filename
)
298 sys
.stdout
.write("D: calling debsign: %s\n" % debsign_cmdline
)
300 subprocess
.check_call(debsign_cmdline
)
301 except subprocess
.CalledProcessError
:
302 sys
.stderr
.write("Error: debsign failed.\n")
307 def upload_stolen_from_dput_main(
308 host
, upload_methods
, config
, debug
, simulate
,
309 files_to_upload
, ftp_passive_mode
):
310 # Messy, yes. But it isn't referenced by the upload method anyway.
311 if config
.get(host
, 'method') == 'local':
314 fqdn
= config
.get(host
, 'fqdn')
316 # Check the upload methods that we have as default and per host
319 "D: Default Method: %s\n" % config
.get('DEFAULT', 'method'))
320 if config
.get('DEFAULT', 'method') not in upload_methods
:
322 "Unknown upload method: %s\n"
323 % config
.get('DEFAULT', 'method'))
326 sys
.stdout
.write("D: Host Method: %s\n" % config
.get(host
, 'method'))
327 if config
.get(host
, 'method') not in upload_methods
:
329 "Unknown upload method: %s\n" % config
.get(host
, 'method'))
332 # Inspect the Config and set appropriate upload method
333 if not config
.get(host
, 'method'):
334 method
= config
.get('DEFAULT', 'method')
336 method
= config
.get(host
, 'method')
338 # Check now the login and redefine it if needed
340 config
.has_option(host
, 'login')
341 and config
.get(host
, 'login') != 'username'):
342 login
= config
.get(host
, 'login')
344 config
.has_option('DEFAULT', 'login')
345 and config
.get('DEFAULT', 'login') != 'username'):
346 login
= config
.get('DEFAULT', 'login')
348 # Try to get the login from the enviroment
349 if 'USER' in os
.environ
:
350 login
= os
.environ
['USER']
352 sys
.stdout
.write("$USER not set, will use login information.\n")
353 # Else use the current username
354 login
= pwd
.getpwuid(os
.getuid())[0]
356 sys
.stdout
.write("D: User-ID: %s\n" % os
.getuid())
359 "D: Neither host %s nor default login used. Using %s\n"
362 sys
.stdout
.write("D: Login to use: %s\n" % login
)
364 incoming
= config
.get(host
, 'incoming')
365 # Do the actual upload
368 sys
.stdout
.write("D: FQDN: %s\n" % fqdn
)
369 sys
.stdout
.write("D: Login: %s\n" % login
)
370 sys
.stdout
.write("D: Incoming: %s\n" % incoming
)
372 ftp_mode
= config
.getboolean(host
, 'passive_ftp')
373 if ftp_passive_mode
== 1:
377 if ftp_passive_mode
== 1:
378 sys
.stdout
.write("D: Using passive ftp\n")
380 sys
.stdout
.write("D: Using active ftp\n")
381 upload_methods
[method
](
382 fqdn
, login
, incoming
,
383 files_to_upload
, debug
, ftp_mode
)
384 elif method
== 'scp':
385 if debug
and config
.getboolean(host
, 'scp_compress'):
386 sys
.stdout
.write("D: Setting compression for scp\n")
387 scp_compress
= config
.getboolean(host
, 'scp_compress')
388 ssh_config_options
= [
391 config
.get(host
, 'ssh_config_options').split('\n'))
393 upload_methods
[method
](
394 fqdn
, login
, incoming
,
395 files_to_upload
, debug
, scp_compress
, ssh_config_options
)
397 upload_methods
[method
](
398 fqdn
, login
, incoming
,
399 files_to_upload
, debug
, 0)
400 # Or just simulate it.
402 for file in files_to_upload
:
404 "Uploading with %s: %s to %s:%s\n"
405 % (method
, file, fqdn
, incoming
))
406 subprocess
.call("cat %s" % file, shell
=True)
410 options
, arguments
= getoptions()
412 sys
.stdout
.write('D: calling dput.read_configs\n')
413 config
= dput
.read_configs(options
['config'], options
['debug'])
416 and config
.has_option('DEFAULT', 'default_host_main')):
417 options
['host'] = config
.get('DEFAULT', 'default_host_main')
420 'D: Using host "%s" (default_host_main)\n'
422 if not options
['host']:
423 options
['host'] = 'ftp-master'
426 'D: Using host "%s" (hardcoded)\n'
430 progname
= dputhelper
.get_progname(sys
.argv
)
432 if not (options
['filetoupload'] or options
['filetocreate']):
433 tempdir
= tempfile
.mkdtemp(prefix
=progname
+ '.')
434 if not options
['filetocreate']:
435 if not options
['host']:
437 "Error: No host specified"
438 " and no default found in config\n")
440 if not config
.has_section(options
['host']):
442 "No host %s found in config\n" % (options
['host']))
445 if config
.has_option(options
['host'], 'allow_dcut'):
446 dcut_allowed
= config
.getboolean(
447 options
['host'], 'allow_dcut')
449 dcut_allowed
= config
.getboolean('DEFAULT', 'allow_dcut')
452 'Error: dcut is not supported'
453 ' for this upload queue.\n')
455 if options
['filetoupload']:
458 'Error: cannot take commands'
459 ' when uploading existing file,\n'
460 ' "%s" found\n' % (' '.join(arguments
)))
463 filename
= options
['filetoupload']
464 if not filename
.endswith(".commands"):
466 'Error: I\'m insisting on the .commands extension,'
468 ' "%s" doesnt seem to have.\n' % filename
)
469 # TV-TODO: check file to be readable?
470 elif options
['changes']:
471 parse_changes
= dput
.parse_changes
472 removecommands
= create_commands(options
, config
, parse_changes
)
473 filename
= write_commands(removecommands
, options
, config
, tempdir
)
475 commands
= parse_queuecommands(arguments
, options
, config
)
476 filename
= write_commands(commands
, options
, config
, tempdir
)
477 if not options
['filetocreate']:
478 dput
.import_upload_functions()
479 upload_methods
= dput
.import_upload_functions()
480 upload_stolen_from_dput_main(
481 options
['host'], upload_methods
, config
,
482 options
['debug'], options
['simulate'],
483 [filename
], options
['passive'])
485 # we use sys.exit, so we need to clean up here
487 shutil
.rmtree(tempdir
)
490 def create_commands(options
, config
, parse_changes
):
491 """ Get the removal commands from a package changes file.
493 Parse the specified ‘foo.changes’ file and returns commands to
494 remove files named in it.
497 changes_file
= options
['changes']
500 "D: Parsing changes file (%s) for files to remove\n"
503 chg_fd
= open(changes_file
, 'r')
505 sys
.stdout
.write("Can't open changes file: %s\n" % changes_file
)
507 the_changes
= parse_changes(chg_fd
)
509 removecommands
= ['rm --searchdirs ' + os
.path
.basename(changes_file
)]
510 for file in the_changes
['files'].strip().split('\n'):
513 rm
= 'rm --searchdirs ' + fn
515 sys
.stdout
.write("D: Will remove %s with '%s'\n" % (fn
, rm
))
516 removecommands
.append(rm
)
517 return removecommands
520 if __name__
== "__main__":
523 except dputhelper
.DputException
as e
:
524 sys
.stderr
.write("%s\n" % e
)
532 # vim: fileencoding=utf-8 filetype=python :