Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 1 | From 8ce9fbab2990609bdace457e146160334e931c89 Mon Sep 17 00:00:00 2001 |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 2 | From: Alexander Kanavin <alex.kanavin@gmail.com> |
| 3 | Date: Thu, 8 Jun 2017 17:08:09 +0300 |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 4 | Subject: [PATCH 14/15] build/pack.c: remove static local variables from |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 5 | buildHost() and getBuildTime() |
| 6 | |
| 7 | Their use is causing difficult to diagnoze data races when building multiple |
| 8 | packages in parallel, and is a bad idea in general, as it also makes it more |
| 9 | difficult to reason about code. |
| 10 | |
| 11 | Upstream-Status: Submitted [https://github.com/rpm-software-management/rpm/pull/226] |
| 12 | Signed-off-by: Alexander Kanavin <alex.kanavin@gmail.com> |
| 13 | |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 14 | Signed-off-by: Alexander Kanavin <alex.kanavin@gmail.com> |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 15 | |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 16 | --- |
| 17 | build/build.c | 54 ++++++++++++++++++++++++++++-- |
| 18 | build/pack.c | 84 +++++++++-------------------------------------- |
| 19 | build/rpmbuild_internal.h | 8 +++-- |
| 20 | 3 files changed, 74 insertions(+), 72 deletions(-) |
| 21 | |
| 22 | diff --git a/build/build.c b/build/build.c |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 23 | index 81152e53e..6001f9e52 100644 |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 24 | --- a/build/build.c |
| 25 | +++ b/build/build.c |
| 26 | @@ -6,6 +6,8 @@ |
| 27 | #include "system.h" |
| 28 | |
| 29 | #include <errno.h> |
| 30 | +#include <netdb.h> |
| 31 | +#include <time.h> |
| 32 | #include <sys/wait.h> |
| 33 | |
| 34 | #include <rpm/rpmlog.h> |
| 35 | @@ -16,6 +18,50 @@ |
| 36 | |
| 37 | #include "debug.h" |
| 38 | |
| 39 | +static rpm_time_t getBuildTime(void) |
| 40 | +{ |
| 41 | + rpm_time_t buildTime = 0; |
| 42 | + char *srcdate; |
| 43 | + time_t epoch; |
| 44 | + char *endptr; |
| 45 | + |
| 46 | + srcdate = getenv("SOURCE_DATE_EPOCH"); |
| 47 | + if (srcdate) { |
| 48 | + errno = 0; |
| 49 | + epoch = strtol(srcdate, &endptr, 10); |
| 50 | + if (srcdate == endptr || *endptr || errno != 0) |
| 51 | + rpmlog(RPMLOG_ERR, _("unable to parse SOURCE_DATE_EPOCH\n")); |
| 52 | + else |
| 53 | + buildTime = (int32_t) epoch; |
| 54 | + } else |
| 55 | + buildTime = (int32_t) time(NULL); |
| 56 | + |
| 57 | + return buildTime; |
| 58 | +} |
| 59 | + |
| 60 | +static char * buildHost(void) |
| 61 | +{ |
| 62 | + char* hostname; |
| 63 | + struct hostent *hbn; |
| 64 | + char *bhMacro; |
| 65 | + |
| 66 | + bhMacro = rpmExpand("%{?_buildhost}", NULL); |
| 67 | + if (strcmp(bhMacro, "") != 0) { |
| 68 | + rasprintf(&hostname, "%s", bhMacro); |
| 69 | + } else { |
| 70 | + hostname = rcalloc(1024, sizeof(*hostname)); |
| 71 | + (void) gethostname(hostname, 1024); |
| 72 | + hbn = gethostbyname(hostname); |
| 73 | + if (hbn) |
| 74 | + strcpy(hostname, hbn->h_name); |
| 75 | + else |
| 76 | + rpmlog(RPMLOG_WARNING, |
| 77 | + _("Could not canonicalize hostname: %s\n"), hostname); |
| 78 | + } |
| 79 | + free(bhMacro); |
| 80 | + return(hostname); |
| 81 | +} |
| 82 | + |
| 83 | /** |
| 84 | */ |
| 85 | static rpmRC doRmSource(rpmSpec spec) |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 86 | @@ -201,6 +247,9 @@ static rpmRC buildSpec(BTA_t buildArgs, rpmSpec spec, int what) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 87 | rpmRC rc = RPMRC_OK; |
| 88 | int test = (what & RPMBUILD_NOBUILD); |
| 89 | char *cookie = buildArgs->cookie ? xstrdup(buildArgs->cookie) : NULL; |
| 90 | + const char* host = buildHost(); |
| 91 | + rpm_time_t buildTime = getBuildTime(); |
| 92 | + |
| 93 | |
| 94 | if (rpmExpandNumeric("%{?source_date_epoch_from_changelog}") && |
| 95 | getenv("SOURCE_DATE_EPOCH") == NULL) { |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 96 | @@ -269,11 +318,11 @@ static rpmRC buildSpec(BTA_t buildArgs, rpmSpec spec, int what) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 97 | goto exit; |
| 98 | |
| 99 | if (((what & RPMBUILD_PACKAGESOURCE) && !test) && |
| 100 | - (rc = packageSources(spec, &cookie))) |
| 101 | + (rc = packageSources(spec, &cookie, buildTime, host))) |
| 102 | return rc; |
| 103 | |
| 104 | if (((what & RPMBUILD_PACKAGEBINARY) && !test) && |
| 105 | - (rc = packageBinaries(spec, cookie, (didBuild == 0)))) |
| 106 | + (rc = packageBinaries(spec, cookie, (didBuild == 0), buildTime, host))) |
| 107 | goto exit; |
| 108 | |
| 109 | if ((what & RPMBUILD_CLEAN) && |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 110 | @@ -293,6 +342,7 @@ static rpmRC buildSpec(BTA_t buildArgs, rpmSpec spec, int what) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 111 | (void) unlink(spec->specFile); |
| 112 | |
| 113 | exit: |
| 114 | + free(host); |
| 115 | free(cookie); |
| 116 | spec->rootDir = NULL; |
| 117 | if (rc != RPMRC_OK && rpmlogGetNrecs() > 0) { |
| 118 | diff --git a/build/pack.c b/build/pack.c |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 119 | index df15876ff..17a4b0905 100644 |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 120 | --- a/build/pack.c |
| 121 | +++ b/build/pack.c |
| 122 | @@ -6,8 +6,6 @@ |
| 123 | #include "system.h" |
| 124 | |
| 125 | #include <errno.h> |
| 126 | -#include <netdb.h> |
| 127 | -#include <time.h> |
| 128 | #include <sys/wait.h> |
| 129 | |
| 130 | #include <rpm/rpmlib.h> /* RPMSIGTAG*, rpmReadPackageFile */ |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 131 | @@ -152,57 +150,6 @@ exit: |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 132 | return rc; |
| 133 | } |
| 134 | |
| 135 | -static rpm_time_t * getBuildTime(void) |
| 136 | -{ |
| 137 | - static rpm_time_t buildTime[1]; |
| 138 | - char *srcdate; |
| 139 | - time_t epoch; |
| 140 | - char *endptr; |
| 141 | - |
| 142 | - if (buildTime[0] == 0) { |
| 143 | - srcdate = getenv("SOURCE_DATE_EPOCH"); |
| 144 | - if (srcdate) { |
| 145 | - errno = 0; |
| 146 | - epoch = strtol(srcdate, &endptr, 10); |
| 147 | - if (srcdate == endptr || *endptr || errno != 0) |
| 148 | - rpmlog(RPMLOG_ERR, _("unable to parse SOURCE_DATE_EPOCH\n")); |
| 149 | - else |
| 150 | - buildTime[0] = (int32_t) epoch; |
| 151 | - } else |
| 152 | - buildTime[0] = (int32_t) time(NULL); |
| 153 | - } |
| 154 | - |
| 155 | - return buildTime; |
| 156 | -} |
| 157 | - |
| 158 | -static const char * buildHost(void) |
| 159 | -{ |
| 160 | - static char hostname[1024]; |
| 161 | - static int oneshot = 0; |
| 162 | - struct hostent *hbn; |
| 163 | - char *bhMacro; |
| 164 | - |
| 165 | - if (! oneshot) { |
| 166 | - bhMacro = rpmExpand("%{?_buildhost}", NULL); |
| 167 | - if (strcmp(bhMacro, "") != 0 && strlen(bhMacro) < 1024) { |
| 168 | - strcpy(hostname, bhMacro); |
| 169 | - } else { |
| 170 | - if (strcmp(bhMacro, "") != 0) |
| 171 | - rpmlog(RPMLOG_WARNING, _("The _buildhost macro is too long\n")); |
| 172 | - (void) gethostname(hostname, sizeof(hostname)); |
| 173 | - hbn = gethostbyname(hostname); |
| 174 | - if (hbn) |
| 175 | - strcpy(hostname, hbn->h_name); |
| 176 | - else |
| 177 | - rpmlog(RPMLOG_WARNING, |
| 178 | - _("Could not canonicalize hostname: %s\n"), hostname); |
| 179 | - } |
| 180 | - free(bhMacro); |
| 181 | - oneshot = 1; |
| 182 | - } |
| 183 | - return(hostname); |
| 184 | -} |
| 185 | - |
| 186 | static rpmRC processScriptFiles(rpmSpec spec, Package pkg) |
| 187 | { |
| 188 | struct TriggerFileEntry *p; |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 189 | @@ -476,7 +423,8 @@ exit: |
| 190 | * order to how the RPM format is laid on disk. |
| 191 | */ |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 192 | static rpmRC writeRPM(Package pkg, unsigned char ** pkgidp, |
| 193 | - const char *fileName, char **cookie) |
| 194 | + const char *fileName, char **cookie, |
| 195 | + rpm_time_t buildTime, const char* buildHost) |
| 196 | { |
| 197 | FD_t fd = NULL; |
| 198 | char * rpmio_flags = NULL; |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 199 | @@ -500,7 +448,7 @@ static rpmRC writeRPM(Package pkg, unsigned char ** pkgidp, |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 200 | |
| 201 | /* Create and add the cookie */ |
| 202 | if (cookie) { |
| 203 | - rasprintf(cookie, "%s %d", buildHost(), (int) (*getBuildTime())); |
| 204 | + rasprintf(cookie, "%s %d", buildHost, buildTime); |
| 205 | headerPutString(pkg->header, RPMTAG_COOKIE, *cookie); |
| 206 | } |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 207 | |
| 208 | @@ -641,7 +589,7 @@ static rpmRC checkPackages(char *pkgcheck) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 209 | return RPMRC_OK; |
| 210 | } |
| 211 | |
| 212 | -static rpmRC packageBinary(rpmSpec spec, Package pkg, const char *cookie, int cheating, char** filename) |
| 213 | +static rpmRC packageBinary(rpmSpec spec, Package pkg, const char *cookie, int cheating, char** filename, rpm_time_t buildTime, const char* buildHost) |
| 214 | { |
| 215 | const char *errorString; |
| 216 | rpmRC rc = RPMRC_OK; |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 217 | @@ -660,8 +608,8 @@ static rpmRC packageBinary(rpmSpec spec, Package pkg, const char *cookie, int ch |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 218 | headerCopyTags(spec->packages->header, pkg->header, copyTags); |
| 219 | |
| 220 | headerPutString(pkg->header, RPMTAG_RPMVERSION, VERSION); |
| 221 | - headerPutString(pkg->header, RPMTAG_BUILDHOST, buildHost()); |
| 222 | - headerPutUint32(pkg->header, RPMTAG_BUILDTIME, getBuildTime(), 1); |
| 223 | + headerPutString(pkg->header, RPMTAG_BUILDHOST, buildHost); |
| 224 | + headerPutUint32(pkg->header, RPMTAG_BUILDTIME, &buildTime, 1); |
| 225 | |
| 226 | if (spec->sourcePkgId != NULL) { |
| 227 | headerPutBin(pkg->header, RPMTAG_SOURCEPKGID, spec->sourcePkgId,16); |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 228 | @@ -699,7 +647,7 @@ static rpmRC packageBinary(rpmSpec spec, Package pkg, const char *cookie, int ch |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 229 | free(binRpm); |
| 230 | } |
| 231 | |
| 232 | - rc = writeRPM(pkg, NULL, *filename, NULL); |
| 233 | + rc = writeRPM(pkg, NULL, *filename, NULL, buildTime, buildHost); |
| 234 | if (rc == RPMRC_OK) { |
| 235 | /* Do check each written package if enabled */ |
| 236 | char *pkgcheck = rpmExpand("%{?_build_pkgcheck} ", *filename, NULL); |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 237 | @@ -719,7 +667,7 @@ struct binaryPackageTaskData |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 238 | struct binaryPackageTaskData *next; |
| 239 | }; |
| 240 | |
| 241 | -static struct binaryPackageTaskData* runBinaryPackageTasks(rpmSpec spec, const char *cookie, int cheating) |
| 242 | +static struct binaryPackageTaskData* runBinaryPackageTasks(rpmSpec spec, const char *cookie, int cheating, rpm_time_t buildTime, char* buildHost) |
| 243 | { |
| 244 | struct binaryPackageTaskData *tasks = NULL; |
| 245 | struct binaryPackageTaskData *task = NULL; |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 246 | @@ -731,7 +679,7 @@ static struct binaryPackageTaskData* runBinaryPackageTasks(rpmSpec spec, const c |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 247 | if (pkg == spec->packages) { |
| 248 | // the first package needs to be processed ahead of others, as they copy |
| 249 | // changelog data from it, and so otherwise data races would happen |
| 250 | - task->result = packageBinary(spec, pkg, cookie, cheating, &(task->filename)); |
| 251 | + task->result = packageBinary(spec, pkg, cookie, cheating, &(task->filename), buildTime, buildHost); |
| 252 | rpmlog(RPMLOG_NOTICE, _("Finished binary package job, result %d, filename %s\n"), task->result, task->filename); |
| 253 | tasks = task; |
| 254 | } |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 255 | @@ -748,7 +696,7 @@ static struct binaryPackageTaskData* runBinaryPackageTasks(rpmSpec spec, const c |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 256 | if (task != tasks) |
| 257 | #pragma omp task |
| 258 | { |
| 259 | - task->result = packageBinary(spec, task->pkg, cookie, cheating, &(task->filename)); |
| 260 | + task->result = packageBinary(spec, task->pkg, cookie, cheating, &(task->filename), buildTime, buildHost); |
| 261 | rpmlog(RPMLOG_NOTICE, _("Finished binary package job, result %d, filename %s\n"), task->result, task->filename); |
| 262 | } |
| 263 | } |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 264 | @@ -766,11 +714,11 @@ static void freeBinaryPackageTasks(struct binaryPackageTaskData* tasks) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 265 | } |
| 266 | } |
| 267 | |
| 268 | -rpmRC packageBinaries(rpmSpec spec, const char *cookie, int cheating) |
| 269 | +rpmRC packageBinaries(rpmSpec spec, const char *cookie, int cheating, rpm_time_t buildTime, char* buildHost) |
| 270 | { |
| 271 | char *pkglist = NULL; |
| 272 | |
| 273 | - struct binaryPackageTaskData *tasks = runBinaryPackageTasks(spec, cookie, cheating); |
| 274 | + struct binaryPackageTaskData *tasks = runBinaryPackageTasks(spec, cookie, cheating, buildTime, buildHost); |
| 275 | |
| 276 | for (struct binaryPackageTaskData *task = tasks; task != NULL; task = task->next) { |
| 277 | if (task->result == RPMRC_OK) { |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 278 | @@ -797,7 +745,7 @@ rpmRC packageBinaries(rpmSpec spec, const char *cookie, int cheating) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 279 | return RPMRC_OK; |
| 280 | } |
| 281 | |
| 282 | -rpmRC packageSources(rpmSpec spec, char **cookie) |
| 283 | +rpmRC packageSources(rpmSpec spec, char **cookie, rpm_time_t buildTime, char* buildHost) |
| 284 | { |
| 285 | Package sourcePkg = spec->sourcePackage; |
| 286 | rpmRC rc; |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 287 | @@ -805,8 +753,8 @@ rpmRC packageSources(rpmSpec spec, char **cookie) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 288 | |
| 289 | /* Add some cruft */ |
| 290 | headerPutString(sourcePkg->header, RPMTAG_RPMVERSION, VERSION); |
| 291 | - headerPutString(sourcePkg->header, RPMTAG_BUILDHOST, buildHost()); |
| 292 | - headerPutUint32(sourcePkg->header, RPMTAG_BUILDTIME, getBuildTime(), 1); |
| 293 | + headerPutString(sourcePkg->header, RPMTAG_BUILDHOST, buildHost); |
| 294 | + headerPutUint32(sourcePkg->header, RPMTAG_BUILDTIME, &buildTime, 1); |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 295 | headerPutUint32(sourcePkg->header, RPMTAG_SOURCEPACKAGE, &one, 1); |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 296 | |
| 297 | /* XXX this should be %_srpmdir */ |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 298 | @@ -814,7 +762,7 @@ rpmRC packageSources(rpmSpec spec, char **cookie) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 299 | char *pkgcheck = rpmExpand("%{?_build_pkgcheck_srpm} ", fn, NULL); |
| 300 | |
| 301 | spec->sourcePkgId = NULL; |
| 302 | - rc = writeRPM(sourcePkg, &spec->sourcePkgId, fn, cookie); |
| 303 | + rc = writeRPM(sourcePkg, &spec->sourcePkgId, fn, cookie, buildTime, buildHost); |
| 304 | |
| 305 | /* Do check SRPM package if enabled */ |
| 306 | if (rc == RPMRC_OK && pkgcheck[0] != ' ') { |
| 307 | diff --git a/build/rpmbuild_internal.h b/build/rpmbuild_internal.h |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 308 | index 439b7d3b5..07e8338ad 100644 |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 309 | --- a/build/rpmbuild_internal.h |
| 310 | +++ b/build/rpmbuild_internal.h |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 311 | @@ -427,19 +427,23 @@ rpmRC processSourceFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags); |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 312 | * @param spec spec file control structure |
| 313 | * @param cookie build identifier "cookie" or NULL |
| 314 | * @param cheating was build shortcircuited? |
| 315 | + * @param buildTime the build timestamp that goes into packages |
| 316 | + * @param buildHost the hostname where the build is happening |
| 317 | * @return RPMRC_OK on success |
| 318 | */ |
| 319 | RPM_GNUC_INTERNAL |
| 320 | -rpmRC packageBinaries(rpmSpec spec, const char *cookie, int cheating); |
| 321 | +rpmRC packageBinaries(rpmSpec spec, const char *cookie, int cheating, rpm_time_t buildTime, char* buildHost); |
| 322 | |
| 323 | /** \ingroup rpmbuild |
| 324 | * Generate source package. |
| 325 | * @param spec spec file control structure |
| 326 | * @retval cookie build identifier "cookie" or NULL |
| 327 | + * @param buildTime the build timestamp that goes into packages |
| 328 | + * @param buildHost the hostname where the build is happening |
| 329 | * @return RPMRC_OK on success |
| 330 | */ |
| 331 | RPM_GNUC_INTERNAL |
| 332 | -rpmRC packageSources(rpmSpec spec, char **cookie); |
| 333 | +rpmRC packageSources(rpmSpec spec, char **cookie, rpm_time_t buildTime, char* buildHost); |
| 334 | |
| 335 | RPM_GNUC_INTERNAL |
| 336 | int addLangTag(rpmSpec spec, Header h, rpmTagVal tag, |
| 337 | -- |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 338 | 2.14.2 |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 339 | |