# General Utility Functions used in our Git scripts # # Copyright (C) 2008 Owen Taylor # Copyright (C) 2009 Red Hat, Inc # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, If not, see # http://www.gnu.org/licenses/. import os import sys import smtplib from subprocess import Popen import tempfile import time import email def die(message): print >>sys.stderr, message sys.exit(1) # This cleans up our generation code by allowing us to use the same indentation # for the first line and subsequent line of a multi-line string def strip_string(str): start = 0 end = len(str) if len(str) > 0 and str[0] == '\n': start += 1 if len(str) > 1 and str[end - 1] == '\n': end -= 1 return str[start:end] # How long to wait between mails (in seconds); the idea of waiting # is to try to make the sequence of mails we send out in order # actually get delivered in order. The waiting is done in a forked # subprocess and doesn't stall completion of the main script. EMAIL_DELAY = 0.02 # Some line that can never appear in any email we send out EMAIL_BOUNDARY="---@@@--- labs-git-email ---@@@---\n" def tomail(x): if '<' in x: x = x[x.index('<')+1:-1] return x # Run in subprocess def _do_send_emails(email_in): email_files = [] current_file = None last_line = None # Read emails from the input pipe and write each to a file for line in email_in: if current_file is None: current_file, filename = tempfile.mkstemp(suffix=".mail", prefix="labs-post-receive-email-") email_files.append(filename) if line == EMAIL_BOUNDARY: # Strip the last line if blank; see comment when writing # the email boundary for rationale if last_line.strip() != "": os.write(current_file, last_line) last_line = None os.close(current_file) current_file = None else: if last_line is not None: os.write(current_file, last_line) last_line = line if current_file is not None: if last_line is not None: os.write(current_file, last_line) os.close(current_file) # We're done interacting with the parent process, the rest happens # asynchronously; send out the emails one by one and remove the # temporary files server = smtplib.SMTP('localhost') for i, filename in enumerate(email_files): if i != 0: time.sleep(EMAIL_DELAY) f = open(filename) msgstr = f.read() message = email.message_from_string(msgstr) fromaddr = tomail(message['From']) toaddrs = [tomail(x.strip()) for x in message['To'].split(',')] server.sendmail(fromaddr, toaddrs, msgstr) os.remove(filename) f.close() server.quit() email_file = None # Start a new outgoing email; returns a file object that the # email should be written to. Call end_email() when done def start_email(): global email_file if email_file is None: email_pipe = os.pipe() pid = os.fork() if pid == 0: # The child os.close(email_pipe[1]) email_in = os.fdopen(email_pipe[0]) # Redirect stdin/stdout/stderr to/from /dev/null devnullin = os.open("/dev/null", os.O_RDONLY) os.close(0) os.dup2(devnullin, 0) if False: devnullout = os.open("/dev/null", os.O_WRONLY) os.close(1) os.dup2(devnullout, 1) os.close(2) os.dup2(devnullout, 2) os.close(devnullout) # Fork again to daemonize if os.fork() > 0: sys.exit(0) try: _do_send_emails(email_in) except Exception: raise import syslog import traceback syslog.openlog(os.path.basename(sys.argv[0])) syslog.syslog(syslog.LOG_ERR, "Unexpected exception sending mail") for line in traceback.format_exc().strip().split("\n"): syslog.syslog(syslog.LOG_ERR, line) sys.exit(0) email_file = os.fdopen(email_pipe[1], "w") else: # The email might not end with a newline, so add one. We'll # strip the last line, if blank, when emails, so the net effect # is to add a newline to messages without one email_file.write("\n") email_file.write(EMAIL_BOUNDARY) return email_file # Finish an email started with start_email def end_email(): global email_file email_file.flush()