/* $NetBSD: strftime.c,v 1.8 1999/02/07 17:33:30 augustss Exp $ */ /* * Copyright (c) 1989 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if defined(LIBC_SCCS) && !defined(lint) #if 0 static char *sccsid = "@(#)strftime.c 5.11 (Berkeley) 2/24/91"; #else __RCSID("$NetBSD: strftime.c,v 1.8 1999/02/07 17:33:30 augustss Exp $"); #endif #endif /* LIBC_SCCS and not lint */ #include #include /* begin krb5 hack - replace stuff that would come from netbsd libc */ #undef _CurrentTimeLocale #define _CurrentTimeLocale (&dummy_locale_info) struct dummy_locale_info_t { char d_t_fmt[15]; char t_fmt_ampm[12]; char t_fmt[9]; char d_fmt[9]; char day[7][10]; char abday[7][4]; char mon[12][10]; char abmon[12][4]; char am_pm[2][3]; }; static const struct dummy_locale_info_t dummy_locale_info = { "%a %b %d %X %Y", /* %c */ "%I:%M:%S %p", /* %r */ "%H:%M:%S", /* %X */ "%m/%d/%y", /* %x */ { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }, { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }, { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }, { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }, { "AM", "PM" }, }; #undef TM_YEAR_BASE #define TM_YEAR_BASE 1900 #undef DAYSPERLYEAR #define DAYSPERLYEAR 366 #undef DAYSPERNYEAR #define DAYSPERNYEAR 365 #undef DAYSPERWEEK #define DAYSPERWEEK 7 #undef isleap #define isleap(N) ((N % 4) == 0 && (N % 100 != 0 || N % 400 == 0)) #undef tzname #define tzname my_tzname static const char *const tzname[2] = { 0, 0 }; #undef tzset #define tzset() #undef __P #define __P(X) X /* we already require ansi c in this tree */ /* end krb5 hack */ static int _add __P((const char *, char **, const char *)); static int _conv __P((int, int, int, char **, const char *)); static int _secs __P((const struct tm *, char **, const char *)); static size_t _fmt __P((const char *, const struct tm *, char **, const char *)); size_t strftime(s, maxsize, format, t) char *s; size_t maxsize; const char *format; const struct tm *t; { char *pt; tzset(); if (maxsize < 1) return (0); pt = s; if (_fmt(format, t, &pt, s + maxsize)) { *pt = '\0'; return (pt - s); } else return (0); } #define SUN_WEEK(t) (((t)->tm_yday + 7 - \ ((t)->tm_wday)) / 7) #define MON_WEEK(t) (((t)->tm_yday + 7 - \ ((t)->tm_wday ? (t)->tm_wday - 1 : 6)) / 7) static size_t _fmt(format, t, pt, ptlim) const char *format; const struct tm *t; char **pt; const char * const ptlim; { for (; *format; ++format) { if (*format == '%') { ++format; if (*format == 'E') { /* Alternate Era */ ++format; } else if (*format == 'O') { /* Alternate numeric symbols */ ++format; } switch (*format) { case '\0': --format; break; case 'A': if (t->tm_wday < 0 || t->tm_wday > 6) return (0); if (!_add(_CurrentTimeLocale->day[t->tm_wday], pt, ptlim)) return (0); continue; case 'a': if (t->tm_wday < 0 || t->tm_wday > 6) return (0); if (!_add(_CurrentTimeLocale->abday[t->tm_wday], pt, ptlim)) return (0); continue; case 'B': if (t->tm_mon < 0 || t->tm_mon > 11) return (0); if (!_add(_CurrentTimeLocale->mon[t->tm_mon], pt, ptlim)) return (0); continue; case 'b': case 'h': if (t->tm_mon < 0 || t->tm_mon > 11) return (0); if (!_add(_CurrentTimeLocale->abmon[t->tm_mon], pt, ptlim)) return (0); continue; case 'C': if (!_conv((t->tm_year + TM_YEAR_BASE) / 100, 2, '0', pt, ptlim)) return (0); continue; case 'c': if (!_fmt(_CurrentTimeLocale->d_t_fmt, t, pt, ptlim)) return (0); continue; case 'D': if (!_fmt("%m/%d/%y", t, pt, ptlim)) return (0); continue; case 'd': if (!_conv(t->tm_mday, 2, '0', pt, ptlim)) return (0); continue; case 'e': if (!_conv(t->tm_mday, 2, ' ', pt, ptlim)) return (0); continue; case 'H': if (!_conv(t->tm_hour, 2, '0', pt, ptlim)) return (0); continue; case 'I': if (!_conv(t->tm_hour % 12 ? t->tm_hour % 12 : 12, 2, '0', pt, ptlim)) return (0); continue; case 'j': if (!_conv(t->tm_yday + 1, 3, '0', pt, ptlim)) return (0); continue; case 'k': if (!_conv(t->tm_hour, 2, ' ', pt, ptlim)) return (0); continue; case 'l': if (!_conv(t->tm_hour % 12 ? t->tm_hour % 12: 12, 2, ' ', pt, ptlim)) return (0); continue; case 'M': if (!_conv(t->tm_min, 2, '0', pt, ptlim)) return (0); continue; case 'm': if (!_conv(t->tm_mon + 1, 2, '0', pt, ptlim)) return (0); continue; case 'n': if (!_add("\n", pt, ptlim)) return (0); continue; case 'p': if (!_add(_CurrentTimeLocale->am_pm[t->tm_hour >= 12], pt, ptlim)) return (0); continue; case 'R': if (!_fmt("%H:%M", t, pt, ptlim)) return (0); continue; case 'r': if (!_fmt(_CurrentTimeLocale->t_fmt_ampm, t, pt, ptlim)) return (0); continue; case 'S': if (!_conv(t->tm_sec, 2, '0', pt, ptlim)) return (0); continue; case 's': if (!_secs(t, pt, ptlim)) return (0); continue; case 'T': if (!_fmt("%H:%M:%S", t, pt, ptlim)) return (0); continue; case 't': if (!_add("\t", pt, ptlim)) return (0); continue; case 'U': if (!_conv(SUN_WEEK(t), 2, '0', pt, ptlim)) return (0); continue; case 'u': if (!_conv(t->tm_wday ? t->tm_wday : 7, 1, '0', pt, ptlim)) return (0); continue; case 'V': /* ISO 8601 week number */ case 'G': /* ISO 8601 year (four digits) */ case 'g': /* ISO 8601 year (two digits) */ /* ** From Arnold Robbins' strftime version 3.0: "the week number of the ** year (the first Monday as the first day of week 1) as a decimal number ** (01-53)." ** (ado, 1993-05-24) ** ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: ** "Week 01 of a year is per definition the first week which has the ** Thursday in this year, which is equivalent to the week which contains ** the fourth day of January. In other words, the first week of a new year ** is the week which has the majority of its days in the new year. Week 01 ** might also contain days from the previous year and the week before week ** 01 of a year is the last week (52 or 53) of the previous year even if ** it contains days from the new year. A week starts with Monday (day 1) ** and ends with Sunday (day 7). For example, the first week of the year ** 1997 lasts from 1996-12-30 to 1997-01-05..." ** (ado, 1996-01-02) */ { int year; int yday; int wday; int w; year = t->tm_year + TM_YEAR_BASE; yday = t->tm_yday; wday = t->tm_wday; for ( ; ; ) { int len; int bot; int top; len = isleap(year) ? DAYSPERLYEAR : DAYSPERNYEAR; /* ** What yday (-3 ... 3) does ** the ISO year begin on? */ bot = ((yday + 11 - wday) % DAYSPERWEEK) - 3; /* ** What yday does the NEXT ** ISO year begin on? */ top = bot - (len % DAYSPERWEEK); if (top < -3) top += DAYSPERWEEK; top += len; if (yday >= top) { ++year; w = 1; break; } if (yday >= bot) { w = 1 + ((yday - bot) / DAYSPERWEEK); break; } --year; yday += isleap(year) ? DAYSPERLYEAR : DAYSPERNYEAR; } #ifdef XPG4_1994_04_09 if ((w == 52 && t->tm_mon == TM_JANUARY) || (w == 1 && t->tm_mon == TM_DECEMBER)) w = 53; #endif /* defined XPG4_1994_04_09 */ if (*format == 'V') { if (!_conv(w, 2, '0', pt, ptlim)) return (0); } else if (*format == 'g') { if (!_conv(year % 100, 2, '0', pt, ptlim)) return (0); } else if (!_conv(year, 4, '0', pt, ptlim)) return (0); } continue; case 'W': if (!_conv(MON_WEEK(t), 2, '0', pt, ptlim)) return (0); continue; case 'w': if (!_conv(t->tm_wday, 1, '0', pt, ptlim)) return (0); continue; case 'x': if (!_fmt(_CurrentTimeLocale->d_fmt, t, pt, ptlim)) return (0); continue; case 'X': if (!_fmt(_CurrentTimeLocale->t_fmt, t, pt, ptlim)) return (0); continue; case 'y': if (!_conv((t->tm_year + TM_YEAR_BASE) % 100, 2, '0', pt, ptlim)) return (0); continue; case 'Y': if (!_conv((t->tm_year + TM_YEAR_BASE), 4, '0', pt, ptlim)) return (0); continue; case 'Z': if (tzname[t->tm_isdst ? 1 : 0] && !_add(tzname[t->tm_isdst ? 1 : 0], pt, ptlim)) return (0); continue; case '%': /* * X311J/88-090 (4.12.3.5): if conversion char is * undefined, behavior is undefined. Print out the * character itself as printf(3) does. */ default: break; } } if (*pt == ptlim) return (0); *(*pt)++ = *format; } return (ptlim - *pt); } static int _secs(t, pt, ptlim) const struct tm *t; char **pt; const char * const ptlim; { char buf[15]; time_t s; char *p; struct tm tmp; buf[sizeof (buf) - 1] = '\0'; /* Make a copy, mktime(3) modifies the tm struct. */ tmp = *t; s = mktime(&tmp); for (p = buf + sizeof(buf) - 2; s > 0 && p > buf; s /= 10) *p-- = (char)(s % 10 + '0'); return (_add(++p, pt, ptlim)); } static int _conv(n, digits, pad, pt, ptlim) int n, digits; int pad; char **pt; const char * const ptlim; { char buf[10]; char *p; buf[sizeof (buf) - 1] = '\0'; for (p = buf + sizeof(buf) - 2; n > 0 && p > buf; n /= 10, --digits) *p-- = n % 10 + '0'; while (p > buf && digits-- > 0) *p-- = pad; return (_add(++p, pt, ptlim)); } static int _add(str, pt, ptlim) const char *str; char **pt; const char * const ptlim; { for (;; ++(*pt)) { if (*pt == ptlim) return (0); if ((**pt = *str++) == '\0') return (1); } } n261'>261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
 /*
   Unix SMB/CIFS implementation.

   trivial database library

   Copyright (C) Andrew Tridgell              1999-2005
   Copyright (C) Paul `Rusty' Russell		   2000
   Copyright (C) Jeremy Allison			   2000-2003
   Copyright (C) Rusty Russell			   2010

     ** NOTE! The following LGPL license applies to the tdb
     ** library. This does NOT imply that all of Samba is released
     ** under the LGPL

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "private.h"
#include <assert.h>
#include <ccan/likely/likely.h>

void tdb_munmap(struct tdb_file *file)
{
	if (file->fd == -1)
		return;

	if (file->map_ptr) {
		munmap(file->map_ptr, file->map_size);
		file->map_ptr = NULL;
	}
}

enum TDB_ERROR tdb_mmap(struct tdb_context *tdb)
{
	int mmap_flags;

	if (tdb->flags & TDB_INTERNAL)
		return TDB_SUCCESS;

#ifndef HAVE_INCOHERENT_MMAP
	if (tdb->flags & TDB_NOMMAP)
		return TDB_SUCCESS;
#endif

	if ((tdb->open_flags & O_ACCMODE) == O_RDONLY)
		mmap_flags = PROT_READ;
	else
		mmap_flags = PROT_READ | PROT_WRITE;

	/* size_t can be smaller than off_t. */
	if ((size_t)tdb->file->map_size == tdb->file->map_size) {
		tdb->file->map_ptr = mmap(NULL, tdb->file->map_size,
					  mmap_flags,
					  MAP_SHARED, tdb->file->fd, 0);
	} else
		tdb->file->map_ptr = MAP_FAILED;

	/*
	 * NB. When mmap fails it returns MAP_FAILED *NOT* NULL !!!!
	 */
	if (tdb->file->map_ptr == MAP_FAILED) {
		tdb->file->map_ptr = NULL;
#ifdef HAVE_INCOHERENT_MMAP
		/* Incoherent mmap means everyone must mmap! */
		return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
				  "tdb_mmap failed for size %lld (%s)",
				  (long long)tdb->file->map_size,
				  strerror(errno));
#else
		tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING,
			   "tdb_mmap failed for size %lld (%s)",
			   (long long)tdb->file->map_size, strerror(errno));
#endif
	}
	return TDB_SUCCESS;
}

/* check for an out of bounds access - if it is out of bounds then
   see if the database has been expanded by someone else and expand
   if necessary
   note that "len" is the minimum length needed for the db.

   If probe is true, len being too large isn't a failure.
*/
static enum TDB_ERROR tdb_oob(struct tdb_context *tdb,
			      tdb_off_t off, tdb_len_t len, bool probe)
{
	struct stat st;
	enum TDB_ERROR ecode;

	/* We can't hold pointers during this: we could unmap! */
	assert(!tdb->tdb2.direct_access
	       || (tdb->flags & TDB_NOLOCK)
	       || tdb_has_expansion_lock(tdb));

	if (len + off < len) {
		if (probe)
			return TDB_SUCCESS;

		return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
				  "tdb_oob off %llu len %llu wrap\n",
				  (long long)off, (long long)len);
	}

	if (len + off <= tdb->file->map_size)
		return TDB_SUCCESS;
	if (tdb->flags & TDB_INTERNAL) {
		if (probe)
			return TDB_SUCCESS;

		tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
			   "tdb_oob len %lld beyond internal"
			   " malloc size %lld",
			   (long long)(off + len),
			   (long long)tdb->file->map_size);
		return TDB_ERR_IO;
	}

	ecode = tdb_lock_expand(tdb, F_RDLCK);
	if (ecode != TDB_SUCCESS) {
		return ecode;
	}

	if (fstat(tdb->file->fd, &st) != 0) {
		tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
			   "Failed to fstat file: %s", strerror(errno));
		tdb_unlock_expand(tdb, F_RDLCK);
		return TDB_ERR_IO;
	}

	tdb_unlock_expand(tdb, F_RDLCK);

	if (st.st_size < off + len) {
		if (probe)
			return TDB_SUCCESS;

		tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
			   "tdb_oob len %llu beyond eof at %llu",
			   (long long)(off + len), (long long)st.st_size);
		return TDB_ERR_IO;
	}

	/* Unmap, update size, remap */
	tdb_munmap(tdb->file);

	tdb->file->map_size = st.st_size;
	return tdb_mmap(tdb);
}

/* Endian conversion: we only ever deal with 8 byte quantities */
void *tdb_convert(const struct tdb_context *tdb, void *buf, tdb_len_t size)
{
	assert(size % 8 == 0);
	if (unlikely((tdb->flags & TDB_CONVERT)) && buf) {
		uint64_t i, *p = (uint64_t *)buf;
		for (i = 0; i < size / 8; i++)
			p[i] = bswap_64(p[i]);
	}
	return buf;
}

/* Return first non-zero offset in offset array, or end, or -ve error. */
/* FIXME: Return the off? */
uint64_t tdb_find_nonzero_off(struct tdb_context *tdb,
			      tdb_off_t base, uint64_t start, uint64_t end)
{
	uint64_t i;
	const uint64_t *val;

	/* Zero vs non-zero is the same unconverted: minor optimization. */
	val = tdb_access_read(tdb, base + start * sizeof(tdb_off_t),
			      (end - start) * sizeof(tdb_off_t), false);
	if (TDB_PTR_IS_ERR(val)) {
		return TDB_ERR_TO_OFF(TDB_PTR_ERR(val));
	}

	for (i = 0; i < (end - start); i++) {
		if (val[i])
			break;
	}
	tdb_access_release(tdb, val);
	return start + i;
}

/* Return first zero offset in num offset array, or num, or -ve error. */
uint64_t tdb_find_zero_off(struct tdb_context *tdb, tdb_off_t off,
			   uint64_t num)
{
	uint64_t i;
	const uint64_t *val;

	/* Zero vs non-zero is the same unconverted: minor optimization. */
	val = tdb_access_read(tdb, off, num * sizeof(tdb_off_t), false);
	if (TDB_PTR_IS_ERR(val)) {
		return TDB_ERR_TO_OFF(TDB_PTR_ERR(val));
	}

	for (i = 0; i < num; i++) {
		if (!val[i])
			break;
	}
	tdb_access_release(tdb, val);
	return i;
}

enum TDB_ERROR zero_out(struct tdb_context *tdb, tdb_off_t off, tdb_len_t len)
{
	char buf[8192] = { 0 };
	void *p = tdb->tdb2.io->direct(tdb, off, len, true);
	enum TDB_ERROR ecode = TDB_SUCCESS;

	assert(!(tdb->flags & TDB_RDONLY));
	if (TDB_PTR_IS_ERR(p)) {
		return TDB_PTR_ERR(p);
	}
	if (p) {
		memset(p, 0, len);
		return ecode;
	}
	while (len) {
		unsigned todo = len < sizeof(buf) ? len : sizeof(buf);
		ecode = tdb->tdb2.io->twrite(tdb, off, buf, todo);
		if (ecode != TDB_SUCCESS) {
			break;
		}
		len -= todo;
		off += todo;
	}
	return ecode;
}

tdb_off_t tdb_read_off(struct tdb_context *tdb, tdb_off_t off)
{
	tdb_off_t ret;
	enum TDB_ERROR ecode;

	if (likely(!(tdb->flags & TDB_CONVERT))) {
		tdb_off_t *p = tdb->tdb2.io->direct(tdb, off, sizeof(*p),
						    false);
		if (TDB_PTR_IS_ERR(p)) {
			return TDB_ERR_TO_OFF(TDB_PTR_ERR(p));
		}
		if (p)
			return *p;
	}

	ecode = tdb_read_convert(tdb, off, &ret, sizeof(ret));
	if (ecode != TDB_SUCCESS) {
		return TDB_ERR_TO_OFF(ecode);
	}
	return ret;
}

/* write a lump of data at a specified offset */
static enum TDB_ERROR tdb_write(struct tdb_context *tdb, tdb_off_t off,
				const void *buf, tdb_len_t len)
{
	enum TDB_ERROR ecode;

	if (tdb->flags & TDB_RDONLY) {
		return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR,
				  "Write to read-only database");
	}

	ecode = tdb->tdb2.io->oob(tdb, off, len, false);
	if (ecode != TDB_SUCCESS) {
		return ecode;
	}

	if (tdb->file->map_ptr) {
		memcpy(off + (char *)tdb->file->map_ptr, buf, len);
	} else {
#ifdef HAVE_INCOHERENT_MMAP
		return TDB_ERR_IO;
#else
		ssize_t ret;
		ret = pwrite(tdb->file->fd, buf, len, off);
		if (ret != len) {
			/* This shouldn't happen: we avoid sparse files. */
			if (ret >= 0)
				errno = ENOSPC;

			return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
					  "tdb_write: %zi at %zu len=%zu (%s)",
					  ret, (size_t)off, (size_t)len,
					  strerror(errno));
		}
#endif
	}
	return TDB_SUCCESS;
}

/* read a lump of data at a specified offset */
static enum TDB_ERROR tdb_read(struct tdb_context *tdb, tdb_off_t off,
			       void *buf, tdb_len_t len)
{
	enum TDB_ERROR ecode;

	ecode = tdb->tdb2.io->oob(tdb, off, len, false);
	if (ecode != TDB_SUCCESS) {
		return ecode;
	}

	if (tdb->file->map_ptr) {
		memcpy(buf, off + (char *)tdb->file->map_ptr, len);
	} else {
#ifdef HAVE_INCOHERENT_MMAP
		return TDB_ERR_IO;
#else
		ssize_t r = pread(tdb->file->fd, buf, len, off);
		if (r != len) {
			return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
					  "tdb_read failed with %zi at %zu "
					  "len=%zu (%s) map_size=%zu",
					  r, (size_t)off, (size_t)len,
					  strerror(errno),
					  (size_t)tdb->file->map_size);
		}
#endif
	}
	return TDB_SUCCESS;
}

enum TDB_ERROR tdb_write_convert(struct tdb_context *tdb, tdb_off_t off,
				 const void *rec, size_t len)
{
	enum TDB_ERROR ecode;

	if (unlikely((tdb->flags & TDB_CONVERT))) {
		void *conv = malloc(len);
		if (!conv) {
			return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR,
					  "tdb_write: no memory converting"
					  " %zu bytes", len);
		}
		memcpy(conv, rec, len);
		ecode = tdb->tdb2.io->twrite(tdb, off,
					   tdb_convert(tdb, conv, len), len);
		free(conv);
	} else {
		ecode = tdb->tdb2.io->twrite(tdb, off, rec, len);
	}
	return ecode;
}

enum TDB_ERROR tdb_read_convert(struct tdb_context *tdb, tdb_off_t off,
				void *rec, size_t len)
{
	enum TDB_ERROR ecode = tdb->tdb2.io->tread(tdb, off, rec, len);
	tdb_convert(tdb, rec, len);
	return ecode;
}

enum TDB_ERROR tdb_write_off(struct tdb_context *tdb,
			     tdb_off_t off, tdb_off_t val)
{
	if (tdb->flags & TDB_RDONLY) {
		return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR,
				  "Write to read-only database");
	}

	if (likely(!(tdb->flags & TDB_CONVERT))) {
		tdb_off_t *p = tdb->tdb2.io->direct(tdb, off, sizeof(*p),
						    true);
		if (TDB_PTR_IS_ERR(p)) {
			return TDB_PTR_ERR(p);
		}
		if (p) {
			*p = val;
			return TDB_SUCCESS;
		}
	}
	return tdb_write_convert(tdb, off, &val, sizeof(val));
}

static void *_tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset,
			     tdb_len_t len, unsigned int prefix)
{
	unsigned char *buf;
	enum TDB_ERROR ecode;

	/* some systems don't like zero length malloc */
	buf = malloc(prefix + len ? prefix + len : 1);
	if (!buf) {
		tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_USE_ERROR,
			   "tdb_alloc_read malloc failed len=%zu",
			   (size_t)(prefix + len));
		return TDB_ERR_PTR(TDB_ERR_OOM);
	} else {
		ecode = tdb->tdb2.io->tread(tdb, offset, buf+prefix, len);
		if (unlikely(ecode != TDB_SUCCESS)) {
			free(buf);
			return TDB_ERR_PTR(ecode);
		}
	}
	return buf;
}

/* read a lump of data, allocating the space for it */
void *tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t len)
{
	return _tdb_alloc_read(tdb, offset, len, 0);
}

static enum TDB_ERROR fill(struct tdb_context *tdb,
			   const void *buf, size_t size,
			   tdb_off_t off, tdb_len_t len)
{
	while (len) {
		size_t n = len > size ? size : len;
		ssize_t ret = pwrite(tdb->file->fd, buf, n, off);
		if (ret != n) {
			if (ret >= 0)
				errno = ENOSPC;

			return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR,
					  "fill failed:"
					  " %zi at %zu len=%zu (%s)",
					  ret, (size_t)off, (size_t)len,
					  strerror(errno));
		}
		len -= n;
		off += n;
	}
	return TDB_SUCCESS;
}

/* expand a file.  we prefer to use ftruncate, as that is what posix
  says to use for mmap expansion */
static enum TDB_ERROR tdb_expand_file(struct tdb_context *tdb,
				      tdb_len_t addition)
{
	char buf[8192];
	enum TDB_ERROR ecode;

	if (tdb->flags & TDB_RDONLY) {
		return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR,
				  "Expand on read-only database");
	}

	if (tdb->flags & TDB_INTERNAL) {
		char *new = realloc(tdb->file->map_ptr,
				    tdb->file->map_size + addition);
		if (!new) {
			return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR,
					  "No memory to expand database");
		}
		tdb->file->map_ptr = new;
		tdb->file->map_size += addition;
		return TDB_SUCCESS;
	} else {
		/* Unmap before trying to write; old TDB claimed OpenBSD had
		 * problem with this otherwise. */
		tdb_munmap(tdb->file);

		/* If this fails, we try to fill anyway. */
		if (ftruncate(tdb->file->fd, tdb->file->map_size + addition))
			;

		/* now fill the file with something. This ensures that the
		   file isn't sparse, which would be very bad if we ran out of
		   disk. This must be done with write, not via mmap */
		memset(buf, 0x43, sizeof(buf));
		ecode = fill(tdb, buf, sizeof(buf), tdb->file->map_size,
			     addition);
		if (ecode != TDB_SUCCESS)
			return ecode;
		tdb->file->map_size += addition;
		return tdb_mmap(tdb);
	}
}

const void *tdb_access_read(struct tdb_context *tdb,
			    tdb_off_t off, tdb_len_t len, bool convert)
{
	void *ret = NULL;

	if (likely(!(tdb->flags & TDB_CONVERT))) {
		ret = tdb->tdb2.io->direct(tdb, off, len, false);

		if (TDB_PTR_IS_ERR(ret)) {
			return ret;
		}
	}
	if (!ret) {
		struct tdb_access_hdr *hdr;
		hdr = _tdb_alloc_read(tdb, off, len, sizeof(*hdr));
		if (TDB_PTR_IS_ERR(hdr)) {
			return hdr;
		}
		hdr->next = tdb->tdb2.access;
		tdb->tdb2.access = hdr;
		ret = hdr + 1;
		if (convert) {
			tdb_convert(tdb, (void *)ret, len);
		}
	} else
		tdb->tdb2.direct_access++;

	return ret;
}

void *tdb_access_write(struct tdb_context *tdb,
		       tdb_off_t off, tdb_len_t len, bool convert)
{
	void *ret = NULL;

	if (tdb->flags & TDB_RDONLY) {
		tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR,
			   "Write to read-only database");
		return TDB_ERR_PTR(TDB_ERR_RDONLY);
	}

	if (likely(!(tdb->flags & TDB_CONVERT))) {
		ret = tdb->tdb2.io->direct(tdb, off, len, true);

		if (TDB_PTR_IS_ERR(ret)) {
			return ret;
		}
	}

	if (!ret) {
		struct tdb_access_hdr *hdr;
		hdr = _tdb_alloc_read(tdb, off, len, sizeof(*hdr));
		if (TDB_PTR_IS_ERR(hdr)) {
			return hdr;
		}
		hdr->next = tdb->tdb2.access;
		tdb->tdb2.access = hdr;
		hdr->off = off;
		hdr->len = len;
		hdr->convert = convert;
		ret = hdr + 1;
		if (convert)
			tdb_convert(tdb, (void *)ret, len);
	} else
		tdb->tdb2.direct_access++;

	return ret;
}

static struct tdb_access_hdr **find_hdr(struct tdb_context *tdb, const void *p)
{
	struct tdb_access_hdr **hp;

	for (hp = &tdb->tdb2.access; *hp; hp = &(*hp)->next) {
		if (*hp + 1 == p)
			return hp;
	}
	return NULL;
}

void tdb_access_release(struct tdb_context *tdb, const void *p)
{
	struct tdb_access_hdr *hdr, **hp = find_hdr(tdb, p);

	if (hp) {
		hdr = *hp;
		*hp = hdr->next;
		free(hdr);
	} else
		tdb->tdb2.direct_access--;
}

enum TDB_ERROR tdb_access_commit(struct tdb_context *tdb, void *p)
{
	struct tdb_access_hdr *hdr, **hp = find_hdr(tdb, p);
	enum TDB_ERROR ecode;

	if (hp) {
		hdr = *hp;
		if (hdr->convert)
			ecode = tdb_write_convert(tdb, hdr->off, p, hdr->len);
		else
			ecode = tdb_write(tdb, hdr->off, p, hdr->len);
		*hp = hdr->next;
		free(hdr);
	} else {
		tdb->tdb2.direct_access--;
		ecode = TDB_SUCCESS;
	}

	return ecode;
}

static void *tdb_direct(struct tdb_context *tdb, tdb_off_t off, size_t len,
			bool write_mode)
{
	enum TDB_ERROR ecode;

	if (unlikely(!tdb->file->map_ptr))
		return NULL;

	ecode = tdb_oob(tdb, off, len, false);
	if (unlikely(ecode != TDB_SUCCESS))
		return TDB_ERR_PTR(ecode);
	return (char *)tdb->file->map_ptr + off;
}

void tdb_inc_seqnum(struct tdb_context *tdb)
{
	tdb_off_t seq;

	if (tdb->flags & TDB_VERSION1) {
		tdb1_increment_seqnum_nonblock(tdb);
		return;
	}

	if (likely(!(tdb->flags & TDB_CONVERT))) {
		int64_t *direct;

		direct = tdb->tdb2.io->direct(tdb,
					      offsetof(struct tdb_header,
						       seqnum),
					      sizeof(*direct), true);
		if (likely(direct)) {
			/* Don't let it go negative, even briefly */
			if (unlikely((*direct) + 1) < 0)
				*direct = 0;
			(*direct)++;
			return;
		}
	}

	seq = tdb_read_off(tdb, offsetof(struct tdb_header, seqnum));
	if (!TDB_OFF_IS_ERR(seq)) {
		seq++;
		if (unlikely((int64_t)seq < 0))
			seq = 0;
		tdb_write_off(tdb, offsetof(struct tdb_header, seqnum), seq);
	}
}

static const struct tdb_methods io_methods = {
	tdb_read,
	tdb_write,
	tdb_oob,
	tdb_expand_file,
	tdb_direct,
};

/*
  initialise the default methods table
*/
void tdb_io_init(struct tdb_context *tdb)
{
	tdb->tdb2.io = &io_methods;
}