bf.c revision 98121
1/*
2 * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
3 *	All rights reserved.
4 *
5 * By using this file, you agree to the terms and conditions set
6 * forth in the LICENSE file which can be found at the top level of
7 * the sendmail distribution.
8 *
9 * Contributed by Exactis.com, Inc.
10 *
11 */
12
13/*
14**  This is in transition. Changed from the original bf_torek.c code
15**  to use sm_io function calls directly rather than through stdio
16**  translation layer. Will be made a built-in file type of libsm
17**  next (once safeopen() linkable from libsm).
18*/
19
20#include <sm/gen.h>
21SM_RCSID("@(#)$Id: bf.c,v 8.54 2002/04/20 18:03:42 gshapiro Exp $")
22
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <sys/uio.h>
26#include <fcntl.h>
27#include <unistd.h>
28#include <stdlib.h>
29#include <string.h>
30#include <errno.h>
31#include "sendmail.h"
32#include "bf.h"
33
34#include <syslog.h>
35
36/* bf io functions */
37static ssize_t	sm_bfread __P((SM_FILE_T *, char *, size_t));
38static ssize_t	sm_bfwrite __P((SM_FILE_T *, const char *, size_t));
39static off_t	sm_bfseek __P((SM_FILE_T *, off_t, int));
40static int	sm_bfclose __P((SM_FILE_T *));
41
42static int	sm_bfopen __P((SM_FILE_T *, const void *, int, const void *));
43static int	sm_bfsetinfo __P((SM_FILE_T *, int , void *));
44static int	sm_bfgetinfo __P((SM_FILE_T *, int , void *));
45
46/*
47**  Data structure for storing information about each buffered file
48**  (Originally in sendmail/bf_torek.h for the curious.)
49*/
50
51struct bf
52{
53	bool	bf_committed;	/* Has this buffered file been committed? */
54	bool	bf_ondisk;	/* On disk: committed or buffer overflow */
55	long	bf_flags;
56	int	bf_disk_fd;	/* If on disk, associated file descriptor */
57	char	*bf_buf;	/* Memory buffer */
58	int	bf_bufsize;	/* Length of above buffer */
59	int	bf_buffilled;	/* Bytes of buffer actually filled */
60	char	*bf_filename;	/* Name of buffered file, if ever committed */
61	MODE_T	bf_filemode;	/* Mode of buffered file, if ever committed */
62	off_t	bf_offset;	/* Currect file offset */
63	int	bf_size;	/* Total current size of file */
64};
65
66#ifdef BF_STANDALONE
67# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode)
68#else /* BF_STANDALONE */
69# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff)
70#endif /* BF_STANDALONE */
71
72struct bf_info
73{
74	char	*bi_filename;
75	MODE_T	bi_fmode;
76	size_t	bi_bsize;
77	long	bi_flags;
78};
79
80/*
81**  SM_BFOPEN -- the "base" open function called by sm_io_open() for the
82**		internal, file-type-specific info setup.
83**
84**	Parameters:
85**		fp -- file pointer being filled-in for file being open'd
86**		info -- information about file being opened
87**		flags -- ignored
88**		rpool -- ignored (currently)
89**
90**	Returns:
91**		Failure: -1 and sets errno
92**		Success: 0 (zero)
93*/
94
95static int
96sm_bfopen(fp, info, flags, rpool)
97	SM_FILE_T *fp;
98	const void *info;
99	int flags;
100	const void *rpool;
101{
102	char *filename;
103	MODE_T fmode;
104	size_t bsize;
105	long sflags;
106	struct bf *bfp;
107	int l;
108	struct stat st;
109
110	filename = ((struct bf_info *) info)->bi_filename;
111	fmode = ((struct bf_info *) info)->bi_fmode;
112	bsize = ((struct bf_info *) info)->bi_bsize;
113	sflags = ((struct bf_info *) info)->bi_flags;
114
115	/* Sanity checks */
116	if (*filename == '\0')
117	{
118		/* Empty filename string */
119		errno = ENOENT;
120		return -1;
121	}
122	if (stat(filename, &st) == 0)
123	{
124		/* File already exists on disk */
125		errno = EEXIST;
126		return -1;
127	}
128
129	/* Allocate memory */
130	bfp = (struct bf *) sm_malloc(sizeof(struct bf));
131	if (bfp == NULL)
132	{
133		errno = ENOMEM;
134		return -1;
135	}
136
137	/* Assign data buffer */
138	/* A zero bsize is valid, just don't allocate memory */
139	if (bsize > 0)
140	{
141		bfp->bf_buf = (char *) sm_malloc(bsize);
142		if (bfp->bf_buf == NULL)
143		{
144			bfp->bf_bufsize = 0;
145			sm_free(bfp);
146			errno = ENOMEM;
147			return -1;
148		}
149	}
150	else
151		bfp->bf_buf = NULL;
152
153	/* Nearly home free, just set all the parameters now */
154	bfp->bf_committed = false;
155	bfp->bf_ondisk = false;
156	bfp->bf_flags = sflags;
157	bfp->bf_bufsize = bsize;
158	bfp->bf_buffilled = 0;
159	l = strlen(filename) + 1;
160	bfp->bf_filename = (char *) sm_malloc(l);
161	if (bfp->bf_filename == NULL)
162	{
163		if (bfp->bf_buf != NULL)
164			sm_free(bfp->bf_buf);
165		sm_free(bfp);
166		errno = ENOMEM;
167		return -1;
168	}
169	(void) sm_strlcpy(bfp->bf_filename, filename, l);
170	bfp->bf_filemode = fmode;
171	bfp->bf_offset = 0;
172	bfp->bf_size = 0;
173	bfp->bf_disk_fd = -1;
174	fp->f_cookie = bfp;
175
176	if (tTd(58, 8))
177		sm_dprintf("sm_bfopen(%s)\n", filename);
178
179	return 0;
180}
181
182/*
183**  BFOPEN -- create a new buffered file
184**
185**	Parameters:
186**		filename -- the file's name
187**		fmode -- what mode the file should be created as
188**		bsize -- amount of buffer space to allocate (may be 0)
189**		flags -- if running under sendmail, passed directly to safeopen
190**
191**	Returns:
192**		a SM_FILE_T * which may then be used with stdio functions,
193**		or NULL	on failure. SM_FILE_T * is opened for writing
194**		"SM_IO_WHAT_VECTORS").
195**
196**	Side Effects:
197**		none.
198**
199**	Sets errno:
200**		any value of errno specified by sm_io_setinfo_type()
201**		any value of errno specified by sm_io_open()
202**		any value of errno specified by sm_io_setinfo()
203*/
204
205#ifdef __STDC__
206/*
207**  XXX This is a temporary hack since MODE_T on HP-UX 10.x is short.
208**	If we use K&R here, the compiler will complain about
209**	Inconsistent parameter list declaration
210**	due to the change from short to int.
211*/
212
213SM_FILE_T *
214bfopen(char *filename, MODE_T fmode, size_t bsize, long flags)
215#else /* __STDC__ */
216SM_FILE_T *
217bfopen(filename, fmode, bsize, flags)
218	char *filename;
219	MODE_T fmode;
220	size_t bsize;
221	long flags;
222#endif /* __STDC__ */
223{
224	MODE_T omask;
225	SM_FILE_T SM_IO_SET_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
226		sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
227		SM_TIME_FOREVER);
228	struct bf_info info;
229
230	/*
231	**  Apply current umask to fmode as it may change by the time
232	**  the file is actually created.  fmode becomes the true
233	**  permissions of the file, which OPEN() must obey.
234	*/
235
236	omask = umask(0);
237	fmode &= ~omask;
238	(void) umask(omask);
239
240	SM_IO_INIT_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
241		sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
242		SM_TIME_FOREVER);
243	info.bi_filename = filename;
244	info.bi_fmode = fmode;
245	info.bi_bsize = bsize;
246	info.bi_flags = flags;
247
248	return sm_io_open(&vector, SM_TIME_DEFAULT, &info, SM_IO_RDWR, NULL);
249}
250
251/*
252**  SM_BFGETINFO -- returns info about an open file pointer
253**
254**	Parameters:
255**		fp -- file pointer to get info about
256**		what -- type of info to obtain
257**		valp -- thing to return the info in
258*/
259
260static int
261sm_bfgetinfo(fp, what, valp)
262	SM_FILE_T *fp;
263	int what;
264	void *valp;
265{
266	struct bf *bfp;
267
268	bfp = (struct bf *) fp->f_cookie;
269	switch (what)
270	{
271	  case SM_IO_WHAT_FD:
272		return bfp->bf_disk_fd;
273	  case SM_IO_WHAT_SIZE:
274		return bfp->bf_size;
275	  default:
276		return -1;
277	}
278}
279
280/*
281**  SM_BFCLOSE -- close a buffered file
282**
283**	Parameters:
284**		fp -- cookie of file to close
285**
286**	Returns:
287**		0 to indicate success
288**
289**	Side Effects:
290**		deletes backing file, sm_frees memory.
291**
292**	Sets errno:
293**		never.
294*/
295
296static int
297sm_bfclose(fp)
298	SM_FILE_T *fp;
299{
300	struct bf *bfp;
301
302	/* Cast cookie back to correct type */
303	bfp = (struct bf *) fp->f_cookie;
304
305	/* Need to clean up the file */
306	if (bfp->bf_ondisk && !bfp->bf_committed)
307		unlink(bfp->bf_filename);
308	sm_free(bfp->bf_filename);
309
310	if (bfp->bf_disk_fd != -1)
311		close(bfp->bf_disk_fd);
312
313	/* Need to sm_free the buffer */
314	if (bfp->bf_bufsize > 0)
315		sm_free(bfp->bf_buf);
316
317	/* Finally, sm_free the structure */
318	sm_free(bfp);
319	return 0;
320}
321
322/*
323**  SM_BFREAD -- read a buffered file
324**
325**	Parameters:
326**		cookie -- cookie of file to read
327**		buf -- buffer to fill
328**		nbytes -- how many bytes to read
329**
330**	Returns:
331**		number of bytes read or -1 indicate failure
332**
333**	Side Effects:
334**		none.
335**
336*/
337
338static ssize_t
339sm_bfread(fp, buf, nbytes)
340	SM_FILE_T *fp;
341	char *buf;
342	size_t nbytes;
343{
344	struct bf *bfp;
345	ssize_t count = 0;	/* Number of bytes put in buf so far */
346	int retval;
347
348	/* Cast cookie back to correct type */
349	bfp = (struct bf *) fp->f_cookie;
350
351	if (bfp->bf_offset < bfp->bf_buffilled)
352	{
353		/* Need to grab some from buffer */
354		count = nbytes;
355		if ((bfp->bf_offset + count) > bfp->bf_buffilled)
356			count = bfp->bf_buffilled - bfp->bf_offset;
357
358		memcpy(buf, bfp->bf_buf + bfp->bf_offset, count);
359	}
360
361	if ((bfp->bf_offset + nbytes) > bfp->bf_buffilled)
362	{
363		/* Need to grab some from file */
364		if (!bfp->bf_ondisk)
365		{
366			/* Oops, the file doesn't exist. EOF. */
367			if (tTd(58, 8))
368				sm_dprintf("sm_bfread(%s): to disk\n",
369					   bfp->bf_filename);
370			goto finished;
371		}
372
373		/* Catch a read() on an earlier failed write to disk */
374		if (bfp->bf_disk_fd < 0)
375		{
376			errno = EIO;
377			return -1;
378		}
379
380		if (lseek(bfp->bf_disk_fd,
381			  bfp->bf_offset + count, SEEK_SET) < 0)
382		{
383			if ((errno == EINVAL) || (errno == ESPIPE))
384			{
385				/*
386				**  stdio won't be expecting these
387				**  errnos from read()! Change them
388				**  into something it can understand.
389				*/
390
391				errno = EIO;
392			}
393			return -1;
394		}
395
396		while (count < nbytes)
397		{
398			retval = read(bfp->bf_disk_fd,
399				      buf + count,
400				      nbytes - count);
401			if (retval < 0)
402			{
403				/* errno is set implicitly by read() */
404				return -1;
405			}
406			else if (retval == 0)
407				goto finished;
408			else
409				count += retval;
410		}
411	}
412
413finished:
414	bfp->bf_offset += count;
415	return count;
416}
417
418/*
419**  SM_BFSEEK -- seek to a position in a buffered file
420**
421**	Parameters:
422**		fp     -- fp of file to seek
423**		offset -- position to seek to
424**		whence -- how to seek
425**
426**	Returns:
427**		new file offset or -1 indicate failure
428**
429**	Side Effects:
430**		none.
431**
432*/
433
434static off_t
435sm_bfseek(fp, offset, whence)
436	SM_FILE_T *fp;
437	off_t offset;
438	int whence;
439
440{
441	struct bf *bfp;
442
443	/* Cast cookie back to correct type */
444	bfp = (struct bf *) fp->f_cookie;
445
446	switch (whence)
447	{
448	  case SEEK_SET:
449		bfp->bf_offset = offset;
450		break;
451
452	  case SEEK_CUR:
453		bfp->bf_offset += offset;
454		break;
455
456	  case SEEK_END:
457		bfp->bf_offset = bfp->bf_size + offset;
458		break;
459
460	  default:
461		errno = EINVAL;
462		return -1;
463	}
464	return bfp->bf_offset;
465}
466
467/*
468**  SM_BFWRITE -- write to a buffered file
469**
470**	Parameters:
471**		fp -- fp of file to write
472**		buf -- data buffer
473**		nbytes -- how many bytes to write
474**
475**	Returns:
476**		number of bytes written or -1 indicate failure
477**
478**	Side Effects:
479**		may create backing file if over memory limit for file.
480**
481*/
482
483static ssize_t
484sm_bfwrite(fp, buf, nbytes)
485	SM_FILE_T *fp;
486	const char *buf;
487	size_t nbytes;
488{
489	struct bf *bfp;
490	ssize_t count = 0;	/* Number of bytes written so far */
491	int retval;
492
493	/* Cast cookie back to correct type */
494	bfp = (struct bf *) fp->f_cookie;
495
496	/* If committed, go straight to disk */
497	if (bfp->bf_committed)
498	{
499		if (lseek(bfp->bf_disk_fd, bfp->bf_offset, SEEK_SET) < 0)
500		{
501			if ((errno == EINVAL) || (errno == ESPIPE))
502			{
503				/*
504				**  stdio won't be expecting these
505				**  errnos from write()! Change them
506				**  into something it can understand.
507				*/
508
509				errno = EIO;
510			}
511			return -1;
512		}
513
514		count = write(bfp->bf_disk_fd, buf, nbytes);
515		if (count < 0)
516		{
517			/* errno is set implicitly by write() */
518			return -1;
519		}
520		goto finished;
521	}
522
523	if (bfp->bf_offset < bfp->bf_bufsize)
524	{
525		/* Need to put some in buffer */
526		count = nbytes;
527		if ((bfp->bf_offset + count) > bfp->bf_bufsize)
528			count = bfp->bf_bufsize - bfp->bf_offset;
529
530		memcpy(bfp->bf_buf + bfp->bf_offset, buf, count);
531		if ((bfp->bf_offset + count) > bfp->bf_buffilled)
532			bfp->bf_buffilled = bfp->bf_offset + count;
533	}
534
535	if ((bfp->bf_offset + nbytes) > bfp->bf_bufsize)
536	{
537		/* Need to put some in file */
538		if (!bfp->bf_ondisk)
539		{
540			MODE_T omask;
541
542			/* Clear umask as bf_filemode are the true perms */
543			omask = umask(0);
544			retval = OPEN(bfp->bf_filename,
545				      O_RDWR | O_CREAT | O_TRUNC,
546				      bfp->bf_filemode, bfp->bf_flags);
547			(void) umask(omask);
548
549			/* Couldn't create file: failure */
550			if (retval < 0)
551			{
552				/*
553				**  stdio may not be expecting these
554				**  errnos from write()! Change to
555				**  something which it can understand.
556				**  Note that ENOSPC and EDQUOT are saved
557				**  because they are actually valid for
558				**  write().
559				*/
560
561				if (!(errno == ENOSPC
562#ifdef EDQUOT
563				      || errno == EDQUOT
564#endif /* EDQUOT */
565				     ))
566					errno = EIO;
567
568				return -1;
569			}
570			bfp->bf_disk_fd = retval;
571			bfp->bf_ondisk = true;
572		}
573
574		/* Catch a write() on an earlier failed write to disk */
575		if (bfp->bf_ondisk && bfp->bf_disk_fd < 0)
576		{
577			errno = EIO;
578			return -1;
579		}
580
581		if (lseek(bfp->bf_disk_fd,
582			  bfp->bf_offset + count, SEEK_SET) < 0)
583		{
584			if ((errno == EINVAL) || (errno == ESPIPE))
585			{
586				/*
587				**  stdio won't be expecting these
588				**  errnos from write()! Change them into
589				**  something which it can understand.
590				*/
591
592				errno = EIO;
593			}
594			return -1;
595		}
596
597		while (count < nbytes)
598		{
599			retval = write(bfp->bf_disk_fd, buf + count,
600				       nbytes - count);
601			if (retval < 0)
602			{
603				/* errno is set implicitly by write() */
604				return -1;
605			}
606			else
607				count += retval;
608		}
609	}
610
611finished:
612	bfp->bf_offset += count;
613	if (bfp->bf_offset > bfp->bf_size)
614		bfp->bf_size = bfp->bf_offset;
615	return count;
616}
617
618/*
619**  BFREWIND -- rewinds the SM_FILE_T *
620**
621**	Parameters:
622**		fp -- SM_FILE_T * to rewind
623**
624**	Returns:
625**		0 on success, -1 on error
626**
627**	Side Effects:
628**		rewinds the SM_FILE_T * and puts it into read mode. Normally
629**		one would bfopen() a file, write to it, then bfrewind() and
630**		fread(). If fp is not a buffered file, this is equivalent to
631**		rewind().
632**
633**	Sets errno:
634**		any value of errno specified by sm_io_rewind()
635*/
636
637int
638bfrewind(fp)
639	SM_FILE_T *fp;
640{
641	(void) sm_io_flush(fp, SM_TIME_DEFAULT);
642	sm_io_clearerr(fp); /* quicker just to do it */
643	return sm_io_seek(fp, SM_TIME_DEFAULT, 0, SM_IO_SEEK_SET);
644}
645
646/*
647**  SM_BFCOMMIT -- "commits" the buffered file
648**
649**	Parameters:
650**		fp -- SM_FILE_T * to commit to disk
651**
652**	Returns:
653**		0 on success, -1 on error
654**
655**	Side Effects:
656**		Forces the given SM_FILE_T * to be written to disk if it is not
657**		already, and ensures that it will be kept after closing. If
658**		fp is not a buffered file, this is a no-op.
659**
660**	Sets errno:
661**		any value of errno specified by open()
662**		any value of errno specified by write()
663**		any value of errno specified by lseek()
664*/
665
666static int
667sm_bfcommit(fp)
668	SM_FILE_T *fp;
669{
670	struct bf *bfp;
671	int retval;
672	int byteswritten;
673
674	/* Get associated bf structure */
675	bfp = (struct bf *) fp->f_cookie;
676
677	/* If already committed, noop */
678	if (bfp->bf_committed)
679		return 0;
680
681	/* Do we need to open a file? */
682	if (!bfp->bf_ondisk)
683	{
684		MODE_T omask;
685		struct stat st;
686
687		if (tTd(58, 8))
688		{
689			sm_dprintf("bfcommit(%s): to disk\n", bfp->bf_filename);
690			if (tTd(58, 32))
691				sm_dprintf("bfcommit(): filemode %o flags %ld\n",
692					   bfp->bf_filemode, bfp->bf_flags);
693		}
694
695		if (stat(bfp->bf_filename, &st) == 0)
696		{
697			errno = EEXIST;
698			return -1;
699		}
700
701		/* Clear umask as bf_filemode are the true perms */
702		omask = umask(0);
703		retval = OPEN(bfp->bf_filename, O_RDWR | O_CREAT | O_TRUNC,
704			      bfp->bf_filemode, bfp->bf_flags);
705		(void) umask(omask);
706
707		/* Couldn't create file: failure */
708		if (retval < 0)
709		{
710			/* errno is set implicitly by open() */
711			return -1;
712		}
713
714		bfp->bf_disk_fd = retval;
715		bfp->bf_ondisk = true;
716	}
717
718	/* Write out the contents of our buffer, if we have any */
719	if (bfp->bf_buffilled > 0)
720	{
721		byteswritten = 0;
722
723		if (lseek(bfp->bf_disk_fd, 0, SEEK_SET) < 0)
724		{
725			/* errno is set implicitly by lseek() */
726			return -1;
727		}
728
729		while (byteswritten < bfp->bf_buffilled)
730		{
731			retval = write(bfp->bf_disk_fd,
732				       bfp->bf_buf + byteswritten,
733				       bfp->bf_buffilled - byteswritten);
734			if (retval < 0)
735			{
736				/* errno is set implicitly by write() */
737				return -1;
738			}
739			else
740				byteswritten += retval;
741		}
742	}
743	bfp->bf_committed = true;
744
745	/* Invalidate buf; all goes to file now */
746	bfp->bf_buffilled = 0;
747	if (bfp->bf_bufsize > 0)
748	{
749		/* Don't need buffer anymore; free it */
750		bfp->bf_bufsize = 0;
751		sm_free(bfp->bf_buf);
752	}
753	return 0;
754}
755
756/*
757**  SM_BFTRUNCATE -- rewinds and truncates the SM_FILE_T *
758**
759**	Parameters:
760**		fp -- SM_FILE_T * to truncate
761**
762**	Returns:
763**		0 on success, -1 on error
764**
765**	Side Effects:
766**		rewinds the SM_FILE_T *, truncates it to zero length, and puts
767**		it into write mode.
768**
769**	Sets errno:
770**		any value of errno specified by fseek()
771**		any value of errno specified by ftruncate()
772*/
773
774static int
775sm_bftruncate(fp)
776	SM_FILE_T *fp;
777{
778	struct bf *bfp;
779
780	if (bfrewind(fp) < 0)
781		return -1;
782
783	/* Get bf structure */
784	bfp = (struct bf *) fp->f_cookie;
785	bfp->bf_buffilled = 0;
786	bfp->bf_size = 0;
787
788	/* Need to zero the buffer */
789	if (bfp->bf_bufsize > 0)
790		memset(bfp->bf_buf, '\0', bfp->bf_bufsize);
791	if (bfp->bf_ondisk)
792	{
793#if NOFTRUNCATE
794		/* XXX: Not much we can do except rewind it */
795		errno = EINVAL;
796		return -1;
797#else /* NOFTRUNCATE */
798		return ftruncate(bfp->bf_disk_fd, 0);
799#endif /* NOFTRUNCATE */
800	}
801	return 0;
802}
803
804/*
805**  SM_BFSETINFO -- set/change info for an open file pointer
806**
807**	Parameters:
808**		fp -- file pointer to get info about
809**		what -- type of info to set/change
810**		valp -- thing to set/change the info to
811**
812*/
813
814static int
815sm_bfsetinfo(fp, what, valp)
816	SM_FILE_T *fp;
817	int what;
818	void *valp;
819{
820	struct bf *bfp;
821	int bsize;
822
823	/* Get bf structure */
824	bfp = (struct bf *) fp->f_cookie;
825	switch (what)
826	{
827	  case SM_BF_SETBUFSIZE:
828		bsize = *((int *) valp);
829		bfp->bf_bufsize = bsize;
830
831		/* A zero bsize is valid, just don't allocate memory */
832		if (bsize > 0)
833		{
834			bfp->bf_buf = (char *) sm_malloc(bsize);
835			if (bfp->bf_buf == NULL)
836			{
837				bfp->bf_bufsize = 0;
838				errno = ENOMEM;
839				return -1;
840			}
841		}
842		else
843			bfp->bf_buf = NULL;
844		return 0;
845	  case SM_BF_COMMIT:
846		return sm_bfcommit(fp);
847	  case SM_BF_TRUNCATE:
848		return sm_bftruncate(fp);
849	  case SM_BF_TEST:
850		return 1; /* always */
851	  default:
852		errno = EINVAL;
853		return -1;
854	}
855}
856