2 # -*- coding: utf-8; -*-
5 # Part of ‘dput’, a Debian package upload toolkit.
7 # Copyright © 2015 Ben Finney <ben+debian@benfinney.id.au>
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
= hostname_subprocess
.stdout
.read().strip()
129 options
['uploader'] = (
130 "%s <%s@%s>" % (fullname
, username
, hostname
))
133 "D: Guessed uploader: %s\n" % (options
['uploader']))
136 sys
.stdout
.write("D: Couldn't guess uploader\n")
137 # parse command line arguments
138 (opts
, arguments
) = dputhelper
.getopt(
140 'c:dDhsvm:k:PU:O:i:', [
142 'help', 'simulate', 'version', 'host=',
143 'maintainteraddress=', 'keyid=',
144 'passive', 'upload=', 'output=', 'input='
147 for (option
, arg
) in opts
:
150 'D: processing arg "%s", option "%s"\n' % (option
, arg
))
151 if option
in ('-h', '--help'):
152 sys
.stdout
.write(make_usage_message())
154 elif option
in ('-v', '--version'):
155 sys
.stdout
.write("%s %s\n" % (progname
, version
))
157 elif option
in ('-d', '--debug'):
159 elif option
in ('-c', '--config'):
160 options
['config'] = arg
161 elif option
in ('-m', '--maintaineraddress'):
162 options
['uploader'] = arg
163 elif option
in ('-k', '--keyid'):
164 options
['keyid'] = arg
165 elif option
in ('-s', '--simulate'):
166 options
['simulate'] = 1
167 elif option
in ('-P', '--passive'):
168 options
['passive'] = 1
169 elif option
in ('-U', '--upload'):
170 options
['filetoupload'] = arg
171 elif option
in ('-O', '--output'):
172 options
['filetocreate'] = arg
173 elif option
== '--host':
174 options
['host'] = arg
175 elif option
in ('-i', '--input'):
176 options
['changes'] = arg
179 "%s internal error: Option %s, argument %s unknown\n"
180 % (progname
, option
, arg
))
183 if not options
['host'] and arguments
and arguments
[0] not in validcommands
:
184 options
['host'] = arguments
[0]
187 'D: first argument "%s" treated as host\n'
191 # we don't create command files without uploader
193 not options
['uploader']
194 and (options
['filetoupload'] or options
['changes'])):
196 "%s error: command file cannot be created"
197 " without maintainer email\n"
200 '%s please set $DEBEMAIL, $EMAIL'
201 ' or use the "-m" option\n'
202 % (len(progname
) * ' '))
205 return options
, arguments
208 def parse_queuecommands(arguments
, options
, config
):
210 # want to consume a copy of arguments
211 arguments
= arguments
[:]
215 if arguments
[0] in validcommands
:
216 curarg
= [arguments
[0]]
217 if arguments
[0] == 'rm':
218 if len(arguments
) > 1 and arguments
[1] == '--nosearchdirs':
221 curarg
.append('--searchdirs')
223 if not curarg
and arguments
[0] != 0:
225 'Error: Could not parse commands at "%s"\n'
228 if str(arguments
[0])[-1] in (',', ';', 0):
229 curarg
.append(arguments
[0][0:-1])
231 if arguments
[0] in (',', ';', 0) and curarg
:
232 # TV-TODO: syntax check for #args etc.
235 'D: Successfully parsed command "%s"\n'
236 % (' '.join(curarg
)))
237 commands
.append(' '.join(curarg
))
240 # TV-TODO: maybe syntax check the arguments here
241 curarg
.append(arguments
[0])
244 sys
.stderr
.write("Error: no arguments given, see dcut -h\n")
249 def write_commands(commands
, options
, config
, tempdir
):
250 progname
= dputhelper
.get_progname(sys
.argv
)
251 if options
['filetocreate']:
252 filename
= options
['filetocreate']
255 str('').join(map(chr, range(256)))
256 + string
.ascii_letters
+ string
.digits
)
257 translationdest
= 256 * '_' + string
.ascii_letters
+ string
.digits
259 ord(orig_char
): ord(dest_char
)
260 for (orig_char
, dest_char
)
261 in zip(translationorig
, translationdest
)}
262 uploadpartforname
= options
['uploader'].translate(translationmap
)
264 progname
+ '.%s.%d.%d.commands' %
265 (uploadpartforname
, int(time
.time()), os
.getpid()))
267 filename
= os
.path
.join(tempdir
, filename
)
268 f
= open(filename
, "w")
269 f
.write("Uploader: %s\n" % options
['uploader'])
270 f
.write("Commands:\n %s\n\n" % ('\n '.join(commands
)))
272 debsign_cmdline
= ['debsign']
273 debsign_cmdline
.append('-m%s' % options
['uploader'])
275 debsign_cmdline
.append('-k%s' % options
['keyid'])
276 debsign_cmdline
.append('%s' % filename
)
278 sys
.stdout
.write("D: calling debsign: %s\n" % debsign_cmdline
)
280 subprocess
.check_call(debsign_cmdline
)
281 except subprocess
.CalledProcessError
:
282 sys
.stderr
.write("Error: debsign failed.\n")
287 def upload_stolen_from_dput_main(
288 host
, upload_methods
, config
, debug
, simulate
,
289 files_to_upload
, ftp_passive_mode
):
290 # Messy, yes. But it isn't referenced by the upload method anyway.
291 if config
.get(host
, 'method') == 'local':
294 fqdn
= config
.get(host
, 'fqdn')
296 # Check the upload methods that we have as default and per host
299 "D: Default Method: %s\n" % config
.get('DEFAULT', 'method'))
300 if config
.get('DEFAULT', 'method') not in upload_methods
:
302 "Unknown upload method: %s\n"
303 % config
.get('DEFAULT', 'method'))
306 sys
.stdout
.write("D: Host Method: %s\n" % config
.get(host
, 'method'))
307 if config
.get(host
, 'method') not in upload_methods
:
309 "Unknown upload method: %s\n" % config
.get(host
, 'method'))
312 # Inspect the Config and set appropriate upload method
313 if not config
.get(host
, 'method'):
314 method
= config
.get('DEFAULT', 'method')
316 method
= config
.get(host
, 'method')
318 # Check now the login and redefine it if needed
320 config
.has_option(host
, 'login')
321 and config
.get(host
, 'login') != 'username'):
322 login
= config
.get(host
, 'login')
324 config
.has_option('DEFAULT', 'login')
325 and config
.get('DEFAULT', 'login') != 'username'):
326 login
= config
.get('DEFAULT', 'login')
328 # Try to get the login from the enviroment
329 if 'USER' in os
.environ
:
330 login
= os
.environ
['USER']
332 sys
.stdout
.write("$USER not set, will use login information.\n")
333 # Else use the current username
334 login
= pwd
.getpwuid(os
.getuid())[0]
336 sys
.stdout
.write("D: User-ID: %s\n" % os
.getuid())
339 "D: Neither host %s nor default login used. Using %s\n"
342 sys
.stdout
.write("D: Login to use: %s\n" % login
)
344 incoming
= config
.get(host
, 'incoming')
345 # Do the actual upload
348 sys
.stdout
.write("D: FQDN: %s\n" % fqdn
)
349 sys
.stdout
.write("D: Login: %s\n" % login
)
350 sys
.stdout
.write("D: Incoming: %s\n" % incoming
)
352 ftp_mode
= config
.getboolean(host
, 'passive_ftp')
353 if ftp_passive_mode
== 1:
357 if ftp_passive_mode
== 1:
358 sys
.stdout
.write("D: Using passive ftp\n")
360 sys
.stdout
.write("D: Using active ftp\n")
361 upload_methods
[method
](
362 fqdn
, login
, incoming
,
363 files_to_upload
, debug
, ftp_mode
)
364 elif method
== 'scp':
365 if debug
and config
.getboolean(host
, 'scp_compress'):
366 sys
.stdout
.write("D: Setting compression for scp\n")
367 scp_compress
= config
.getboolean(host
, 'scp_compress')
368 ssh_config_options
= [
371 config
.get(host
, 'ssh_config_options').split('\n'))
373 upload_methods
[method
](
374 fqdn
, login
, incoming
,
375 files_to_upload
, debug
, scp_compress
, ssh_config_options
)
377 upload_methods
[method
](
378 fqdn
, login
, incoming
,
379 files_to_upload
, debug
, 0)
380 # Or just simulate it.
382 for file in files_to_upload
:
384 "Uploading with %s: %s to %s:%s\n"
385 % (method
, file, fqdn
, incoming
))
386 subprocess
.call("cat %s" % file, shell
=True)
390 options
, arguments
= getoptions()
392 sys
.stdout
.write('D: calling dput.read_configs\n')
393 config
= dput
.read_configs(options
['config'], options
['debug'])
396 and config
.has_option('DEFAULT', 'default_host_main')):
397 options
['host'] = config
.get('DEFAULT', 'default_host_main')
400 'D: Using host "%s" (default_host_main)\n'
402 if not options
['host']:
403 options
['host'] = 'ftp-master'
406 'D: Using host "%s" (hardcoded)\n'
410 progname
= dputhelper
.get_progname(sys
.argv
)
412 if not (options
['filetoupload'] or options
['filetocreate']):
413 tempdir
= tempfile
.mkdtemp(prefix
=progname
+ '.')
414 if not options
['filetocreate']:
415 if not options
['host']:
417 "Error: No host specified"
418 " and no default found in config\n")
420 if not config
.has_section(options
['host']):
422 "No host %s found in config\n" % (options
['host']))
425 if config
.has_option(options
['host'], 'allow_dcut'):
426 dcut_allowed
= config
.getboolean(
427 options
['host'], 'allow_dcut')
429 dcut_allowed
= config
.getboolean('DEFAULT', 'allow_dcut')
432 'Error: dcut is not supported'
433 ' for this upload queue.\n')
435 if options
['filetoupload']:
438 'Error: cannot take commands'
439 ' when uploading existing file,\n'
440 ' "%s" found\n' % (' '.join(arguments
)))
443 filename
= options
['filetoupload']
444 if not filename
.endswith(".commands"):
446 'Error: I\'m insisting on the .commands extension,'
448 ' "%s" doesnt seem to have.\n' % filename
)
449 # TV-TODO: check file to be readable?
450 elif options
['changes']:
451 parse_changes
= dput
.parse_changes
452 removecommands
= create_commands(options
, config
, parse_changes
)
453 filename
= write_commands(removecommands
, options
, config
, tempdir
)
455 commands
= parse_queuecommands(arguments
, options
, config
)
456 filename
= write_commands(commands
, options
, config
, tempdir
)
457 if not options
['filetocreate']:
458 dput
.import_upload_functions()
459 upload_methods
= dput
.import_upload_functions()
460 upload_stolen_from_dput_main(
461 options
['host'], upload_methods
, config
,
462 options
['debug'], options
['simulate'],
463 [filename
], options
['passive'])
465 # we use sys.exit, so we need to clean up here
467 shutil
.rmtree(tempdir
)
470 def create_commands(options
, config
, parse_changes
):
471 """ Get the removal commands from a package changes file.
473 Parse the specified ‘foo.changes’ file and returns commands to
474 remove files named in it.
477 changes_file
= options
['changes']
480 "D: Parsing changes file (%s) for files to remove\n"
483 chg_fd
= open(changes_file
, 'r')
485 sys
.stdout
.write("Can't open changes file: %s\n" % changes_file
)
487 the_changes
= parse_changes(chg_fd
)
489 removecommands
= ['rm --searchdirs ' + os
.path
.basename(changes_file
)]
490 for file in the_changes
['files'].strip().split('\n'):
493 rm
= 'rm --searchdirs ' + fn
495 sys
.stdout
.write("D: Will remove %s with '%s'\n" % (fn
, rm
))
496 removecommands
.append(rm
)
497 return removecommands
500 if __name__
== "__main__":
503 except dputhelper
.DputException
as e
:
504 sys
.stderr
.write("%s\n" % e
)
512 # vim: fileencoding=utf-8 filetype=python :