2 # -*- coding: utf-8; -*-
5 # Part of ‘dput’, a Debian package upload toolkit.
7 # This is free software, and you are welcome to redistribute it under
8 # certain conditions; see the end of this file for copyright
9 # information, grant of license, and disclaimer of warranty.
11 """ dcut — Debian command upload tool. """
23 from .helper
import dputhelper
27 validcommands
= ('rm', 'cancel', 'reschedule')
30 def make_usage_message():
31 """ Make the program usage help message. """
32 text
= textwrap
.dedent("""\
33 Usage: %s [options] [host] command [, command]
34 Supported options (see man page for long forms):
35 -c file Config file to parse.
36 -d Enable debug messages.
37 -h Display this help message.
38 -s Simulate the commands file creation only.
39 -v Display version information.
41 Use maintainer information in "Uploader:" field.
43 Use this keyid for signing.
44 -O file Write commands to file.
45 -U file Upload specified commands file (presently no checks).
47 Upload a commands file to remove files listed in .changes.
48 Supported commands: mv, rm
49 (No paths or command-line options allowed on ftp-master.)
50 """) % (dputhelper
.get_progname(sys
.argv
))
57 'debug': 0, 'simulate': 0, 'config': None, 'host': None,
58 'uploader': None, 'keyid': None, 'passive': 0,
59 'filetocreate': None, 'filetoupload': None, 'changes': None}
60 progname
= dputhelper
.get_progname(sys
.argv
)
61 version
= dputhelper
.get_distribution_version()
63 # enable debugging very early
64 if ('-d' in sys
.argv
[1:] or '--debug' in sys
.argv
[1:]):
66 sys
.stdout
.write("D: %s %s\n" % (progname
, version
))
68 # check environment for maintainer
71 "D: trying to get maintainer email from environment\n")
73 if 'DEBEMAIL' in os
.environ
:
74 if os
.environ
['DEBEMAIL'].find('<') < 0:
75 options
['uploader'] = os
.environ
.get("DEBFULLNAME", '')
76 if options
['uploader']:
77 options
['uploader'] += ' '
78 options
['uploader'] += '<%s>' % (os
.environ
['DEBEMAIL'])
80 options
['uploader'] = os
.environ
['DEBEMAIL']
83 "D: Uploader from env: %s\n" % (options
['uploader']))
84 elif 'EMAIL' in os
.environ
:
85 if os
.environ
['EMAIL'].find('<') < 0:
86 options
['uploader'] = os
.environ
.get("DEBFULLNAME", '')
87 if options
['uploader']:
88 options
['uploader'] += ' '
89 options
['uploader'] += '<%s>' % (os
.environ
['EMAIL'])
91 options
['uploader'] = os
.environ
['EMAIL']
94 "D: Uploader from env: %s\n" % (options
['uploader']))
97 sys
.stdout
.write("D: Guessing uploader\n")
98 pwrec
= pwd
.getpwuid(os
.getuid())
100 fullname
= pwrec
[4].split(',')[0]
102 hostname
= open('/etc/mailname').read().strip()
108 "D: Guessing uploader: /etc/mailname was a failure\n")
109 hostname_subprocess
= subprocess
.Popen(
110 "/bin/hostname --fqdn",
111 shell
=True, stdout
=subprocess
.PIPE
)
112 hostname_stdout
= dputhelper
.make_text_stream(
113 hostname_subprocess
.stdout
)
114 hostname
= hostname_stdout
.read().strip()
116 options
['uploader'] = (
117 "%s <%s@%s>" % (fullname
, username
, hostname
))
120 "D: Guessed uploader: %s\n" % (options
['uploader']))
123 sys
.stdout
.write("D: Couldn't guess uploader\n")
124 # parse command line arguments
125 (opts
, arguments
) = dputhelper
.getopt(
127 'c:dDhsvm:k:PU:O:i:', [
129 'help', 'simulate', 'version', 'host=',
130 'maintainteraddress=', 'keyid=',
131 'passive', 'upload=', 'output=', 'input='
134 for (option
, arg
) in opts
:
137 'D: processing arg "%s", option "%s"\n' % (option
, arg
))
138 if option
in ('-h', '--help'):
139 sys
.stdout
.write(make_usage_message())
141 elif option
in ('-v', '--version'):
142 sys
.stdout
.write("%s %s\n" % (progname
, version
))
144 elif option
in ('-d', '--debug'):
146 elif option
in ('-c', '--config'):
147 options
['config'] = arg
148 elif option
in ('-m', '--maintaineraddress'):
149 options
['uploader'] = arg
150 elif option
in ('-k', '--keyid'):
151 options
['keyid'] = arg
152 elif option
in ('-s', '--simulate'):
153 options
['simulate'] = 1
154 elif option
in ('-P', '--passive'):
155 options
['passive'] = 1
156 elif option
in ('-U', '--upload'):
157 options
['filetoupload'] = arg
158 elif option
in ('-O', '--output'):
159 options
['filetocreate'] = arg
160 elif option
== '--host':
161 options
['host'] = arg
162 elif option
in ('-i', '--input'):
163 options
['changes'] = arg
166 "%s internal error: Option %s, argument %s unknown\n"
167 % (progname
, option
, arg
))
170 if not options
['host'] and arguments
and arguments
[0] not in validcommands
:
171 options
['host'] = arguments
[0]
174 'D: first argument "%s" treated as host\n'
178 # we don't create command files without uploader
180 not options
['uploader']
181 and (options
['filetoupload'] or options
['changes'])):
183 "%s error: command file cannot be created"
184 " without maintainer email\n"
187 '%s please set $DEBEMAIL, $EMAIL'
188 ' or use the "-m" option\n'
189 % (len(progname
) * ' '))
192 return options
, arguments
195 def parse_queuecommands(arguments
, options
, config
):
197 # want to consume a copy of arguments
198 arguments
= arguments
[:]
202 if arguments
[0] in validcommands
:
203 curarg
= [arguments
[0]]
204 if arguments
[0] == 'rm':
205 if len(arguments
) > 1 and arguments
[1] == '--nosearchdirs':
208 curarg
.append('--searchdirs')
210 if not curarg
and arguments
[0] != 0:
212 'Error: Could not parse commands at "%s"\n'
215 if str(arguments
[0])[-1] in (',', ';', 0):
216 curarg
.append(arguments
[0][0:-1])
218 if arguments
[0] in (',', ';', 0) and curarg
:
219 # TV-TODO: syntax check for #args etc.
222 'D: Successfully parsed command "%s"\n'
223 % (' '.join(curarg
)))
224 commands
.append(' '.join(curarg
))
227 # TV-TODO: maybe syntax check the arguments here
228 curarg
.append(arguments
[0])
231 sys
.stderr
.write("Error: no arguments given, see dcut -h\n")
236 def write_commands(commands
, options
, config
, tempdir
):
237 """ Write a file of commands for the upload queue daemon.
239 :param commands: Commands to write, as a sequence of text
241 :param options: Program configuration, as a mapping of options
243 :param config: `ConfigParser` instance for this application.
244 :param tempdir: Filesystem path to directory for temporary files.
245 :return: Filesystem path of file which was written.
247 Write the specified sequence of commands to a file, in the
248 format required for the Debian upload queue management daemon.
250 Once writing is finished, the file is signed using the
253 If not specified in the configuration option 'filetocreate', a
254 default filename is generated. In either case, the resulting
255 filename is returned.
258 progname
= dputhelper
.get_progname(sys
.argv
)
259 if options
['filetocreate']:
260 filename
= options
['filetocreate']
263 str('').join(map(chr, range(256)))
264 + string
.ascii_letters
+ string
.digits
)
265 translationdest
= 256 * '_' + string
.ascii_letters
+ string
.digits
266 translationmap
= string
.maketrans(translationorig
, translationdest
)
267 uploadpartforname
= options
['uploader'].translate(translationmap
)
269 progname
+ '.%s.%d.%d.commands' %
270 (uploadpartforname
, int(time
.time()), os
.getpid()))
272 filename
= os
.path
.join(tempdir
, filename
)
273 f
= open(filename
, "w")
274 f
.write("Uploader: %s\n" % options
['uploader'])
275 f
.write("Commands:\n %s\n\n" % ('\n '.join(commands
)))
277 debsign_cmdline
= ['debsign']
278 debsign_cmdline
.append('-m%s' % options
['uploader'])
280 debsign_cmdline
.append('-k%s' % options
['keyid'])
281 debsign_cmdline
.append('%s' % filename
)
283 sys
.stdout
.write("D: calling debsign: %s\n" % debsign_cmdline
)
285 subprocess
.check_call(debsign_cmdline
)
286 except subprocess
.CalledProcessError
:
287 sys
.stderr
.write("Error: debsign failed.\n")
292 def upload_stolen_from_dput_main(
293 host
, upload_methods
, config
, debug
, simulate
,
294 files_to_upload
, ftp_passive_mode
):
295 # Messy, yes. But it isn't referenced by the upload method anyway.
296 if config
.get(host
, 'method') == 'local':
299 fqdn
= config
.get(host
, 'fqdn')
301 # Check the upload methods that we have as default and per host
304 "D: Default Method: %s\n" % config
.get('DEFAULT', 'method'))
305 if config
.get('DEFAULT', 'method') not in upload_methods
:
307 "Unknown upload method: %s\n"
308 % config
.get('DEFAULT', 'method'))
311 sys
.stdout
.write("D: Host Method: %s\n" % config
.get(host
, 'method'))
312 if config
.get(host
, 'method') not in upload_methods
:
314 "Unknown upload method: %s\n" % config
.get(host
, 'method'))
317 # Inspect the Config and set appropriate upload method
318 if not config
.get(host
, 'method'):
319 method
= config
.get('DEFAULT', 'method')
321 method
= config
.get(host
, 'method')
323 # Check now the login and redefine it if needed
325 config
.has_option(host
, 'login')
326 and config
.get(host
, 'login') != 'username'):
327 login
= config
.get(host
, 'login')
329 config
.has_option('DEFAULT', 'login')
330 and config
.get('DEFAULT', 'login') != 'username'):
331 login
= config
.get('DEFAULT', 'login')
333 # Try to get the login from the enviroment
334 if 'USER' in os
.environ
:
335 login
= os
.environ
['USER']
337 sys
.stdout
.write("$USER not set, will use login information.\n")
338 # Else use the current username
339 login
= pwd
.getpwuid(os
.getuid())[0]
341 sys
.stdout
.write("D: User-ID: %s\n" % os
.getuid())
344 "D: Neither host %s nor default login used. Using %s\n"
347 sys
.stdout
.write("D: Login to use: %s\n" % login
)
349 incoming
= config
.get(host
, 'incoming')
350 # Do the actual upload
353 sys
.stdout
.write("D: FQDN: %s\n" % fqdn
)
354 sys
.stdout
.write("D: Login: %s\n" % login
)
355 sys
.stdout
.write("D: Incoming: %s\n" % incoming
)
357 ftp_mode
= config
.getboolean(host
, 'passive_ftp')
358 if ftp_passive_mode
== 1:
362 if ftp_passive_mode
== 1:
363 sys
.stdout
.write("D: Using passive ftp\n")
365 sys
.stdout
.write("D: Using active ftp\n")
366 upload_methods
[method
](
367 fqdn
, login
, incoming
,
368 files_to_upload
, debug
, ftp_mode
)
369 elif method
== 'scp':
370 if debug
and config
.getboolean(host
, 'scp_compress'):
371 sys
.stdout
.write("D: Setting compression for scp\n")
372 scp_compress
= config
.getboolean(host
, 'scp_compress')
373 ssh_config_options
= [
376 config
.get(host
, 'ssh_config_options').split('\n'))
378 upload_methods
[method
](
379 fqdn
, login
, incoming
,
380 files_to_upload
, debug
, scp_compress
, ssh_config_options
)
382 upload_methods
[method
](
383 fqdn
, login
, incoming
,
384 files_to_upload
, debug
, 0)
385 # Or just simulate it.
387 for file in files_to_upload
:
389 "Uploading with %s: %s to %s:%s\n"
390 % (method
, file, fqdn
, incoming
))
391 subprocess
.call("cat %s" % file, shell
=True)
395 options
, arguments
= getoptions()
397 sys
.stdout
.write('D: calling dput.read_configs\n')
398 config
= dput
.read_configs(options
['config'], options
['debug'])
401 and config
.has_option('DEFAULT', 'default_host_main')):
402 options
['host'] = config
.get('DEFAULT', 'default_host_main')
405 'D: Using host "%s" (default_host_main)\n'
407 if not options
['host']:
408 options
['host'] = 'ftp-master'
411 'D: Using host "%s" (hardcoded)\n'
415 progname
= dputhelper
.get_progname(sys
.argv
)
417 if not (options
['filetoupload'] or options
['filetocreate']):
418 tempdir
= tempfile
.mkdtemp(prefix
=progname
+ '.')
419 if not options
['filetocreate']:
420 if not options
['host']:
422 "Error: No host specified"
423 " and no default found in config\n")
425 if not config
.has_section(options
['host']):
427 "No host %s found in config\n" % (options
['host']))
430 if config
.has_option(options
['host'], 'allow_dcut'):
431 dcut_allowed
= config
.getboolean(
432 options
['host'], 'allow_dcut')
434 dcut_allowed
= config
.getboolean('DEFAULT', 'allow_dcut')
437 'Error: dcut is not supported'
438 ' for this upload queue.\n')
440 if options
['filetoupload']:
443 'Error: cannot take commands'
444 ' when uploading existing file,\n'
445 ' "%s" found\n' % (' '.join(arguments
)))
448 filename
= options
['filetoupload']
449 if not filename
.endswith(".commands"):
451 'Error: I\'m insisting on the .commands extension,'
453 ' "%s" doesnt seem to have.\n' % filename
)
454 # TV-TODO: check file to be readable?
455 elif options
['changes']:
456 parse_changes
= dput
.parse_changes
457 removecommands
= create_commands(options
, config
, parse_changes
)
458 filename
= write_commands(removecommands
, options
, config
, tempdir
)
460 commands
= parse_queuecommands(arguments
, options
, config
)
461 filename
= write_commands(commands
, options
, config
, tempdir
)
462 if not options
['filetocreate']:
463 dput
.import_upload_functions()
464 upload_methods
= dput
.import_upload_functions()
465 upload_stolen_from_dput_main(
466 options
['host'], upload_methods
, config
,
467 options
['debug'], options
['simulate'],
468 [filename
], options
['passive'])
470 # we use sys.exit, so we need to clean up here
472 shutil
.rmtree(tempdir
)
475 def create_commands(options
, config
, parse_changes
):
476 """ Get the removal commands from a package changes file.
478 Parse the specified ‘foo.changes’ file and returns commands to
479 remove files named in it.
482 changes_file
= options
['changes']
485 "D: Parsing changes file (%s) for files to remove\n"
488 chg_fd
= open(changes_file
, 'r')
490 sys
.stdout
.write("Can't open changes file: %s\n" % changes_file
)
492 the_changes
= parse_changes(chg_fd
)
494 removecommands
= ['rm --searchdirs ' + os
.path
.basename(changes_file
)]
495 for file in the_changes
['files'].strip().split('\n'):
498 rm
= 'rm --searchdirs ' + fn
500 sys
.stdout
.write("D: Will remove %s with '%s'\n" % (fn
, rm
))
501 removecommands
.append(rm
)
502 return removecommands
505 if __name__
== "__main__":
508 except dputhelper
.DputException
as e
:
509 sys
.stderr
.write("%s\n" % e
)
513 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
514 # Copyright © 2008–2013 Y Giridhar Appaji Nag <appaji@debian.org>
515 # Copyright © 2004–2009 Thomas Viehmann <tv@beamnet.de>
516 # Copyright © 2000–2004 Christian Kurz <shorty@debian.org>
518 # This program is free software; you can redistribute it and/or modify
519 # it under the terms of the GNU General Public License as published by
520 # the Free Software Foundation; either version 2 of the License, or
521 # (at your option) any later version.
523 # This program is distributed in the hope that it will be useful,
524 # but WITHOUT ANY WARRANTY; without even the implied warranty of
525 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
526 # GNU General Public License for more details.
528 # You should have received a copy of the GNU General Public License along
529 # with this program; if not, write to the Free Software Foundation, Inc.,
530 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
537 # vim: fileencoding=utf-8 filetype=python :