summaryrefslogtreecommitdiff
path: root/imap/src/mlock/mlock.c
blob: 35d6be4d8292d73d71b69b2614fd54d2411b0b7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/* ========================================================================
 * Copyright 1988-2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 
 * ========================================================================
 */

/*
 * Program:	Standalone Mailbox Lock program
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	8 February 1999
 * Last Edited:	3 March 2008
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sysexits.h>
#include <syslog.h>
#include <grp.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdlib.h>
#include <netdb.h>
#include <ctype.h>
#include <string.h>
#include <time.h>

#define LOCKTIMEOUT 5		/* lock timeout in minutes */
#define LOCKPROTECTION 0664

#ifndef MAXHOSTNAMELEN		/* Solaris still sucks */
#define MAXHOSTNAMELEN 256
#endif

/* Fatal error
 * Accepts: Message string
 *	    exit code
 * Returns: code
 */

int die (char *msg,int code)
{
  syslog (LOG_NOTICE,"(%u) %s",code,msg);
  write (1,"?",1);		/* indicate "impossible" failure */
  return code;
}


int main (int argc,char *argv[])
{
  int ld,i;
  int tries = LOCKTIMEOUT * 60 - 1;
  char *s,*dir,*file,*lock,*hitch,tmp[1024];
  size_t dlen,len;
  struct stat sb,fsb;
  struct group *grp = getgrnam ("mail");
				/* get syslog */
  openlog (argv[0],LOG_PID,LOG_MAIL);
  if (!grp || (grp->gr_gid != getegid ()))
    return die ("not setgid mail",EX_USAGE);
  if (argc != 3) return die ("invalid arguments",EX_USAGE);
  for (s = argv[1]; *s; s++)
    if (!isdigit (*s)) return die ("invalid fd",EX_USAGE);
				/* find directory */
  if ((*argv[2] != '/') || !(file = strrchr (argv[2],'/')) || !file[1])
    return die ("invalid path",EX_USAGE);
				/* calculate lengths of directory and file */
  if (!(dlen = file - argv[2])) dlen = 1;
  len = strlen (++file);
				/* make buffers */
  dir = (char *) malloc (dlen + 1);
  lock = (char *) malloc (len + 6);
  hitch = (char *) malloc (len + 6 + 40 + MAXHOSTNAMELEN);
  if (!dir || !lock || !hitch) return die ("malloc failure",errno);
  strncpy (dir,argv[2],dlen);	/* connect to desired directory */
  dir[dlen] = '\0';
  printf ("dir=%s, file=%s\n",dir,file);
  chdir (dir);
				/* get device/inode of file descriptor */
  if (fstat (atoi (argv[1]),&fsb)) return die ("fstat failure",errno);
				/* better be a regular file */
  if ((fsb.st_mode & S_IFMT) != S_IFREG)
    return die ("fd not regular file",EX_USAGE);
				/* now get device/inode of file */
  if (lstat (file,&sb)) return die ("lstat failure",errno);
				/* does it match? */
  if ((sb.st_mode & S_IFMT) != S_IFREG)
    return die ("name not regular file",EX_USAGE);
  if ((sb.st_dev != fsb.st_dev) || (sb.st_ino != fsb.st_ino))
    return die ("fd and name different",EX_USAGE);
				/* build lock filename */
  sprintf (lock,"%s.lock",file);
  if (!lstat (lock,&sb) && ((sb.st_mode & S_IFMT) != S_IFREG))
    return die ("existing lock not regular file",EX_NOPERM);

  do {				/* until OK or out of tries */
    if (!stat (lock,&sb) && (time (0) > (sb.st_ctime + LOCKTIMEOUT * 60)))
      unlink (lock);		/* time out lock if enough time has passed */
    /* SUN-OS had an NFS
     * As kludgy as an albatross;
     * And everywhere that it was installed,
     * It was a total loss.
     * -- MRC 9/25/91
     */
				/* build hitching post file name */
    sprintf (hitch,"%s.%lu.%lu.",lock,(unsigned long) time (0),
	     (unsigned long) getpid ());
    len = strlen (hitch);	/* append local host name */
    gethostname (hitch + len,MAXHOSTNAMELEN);
				/* try to get hitching-post file */
    if ((ld = open (hitch,O_WRONLY|O_CREAT|O_EXCL,LOCKPROTECTION)) >= 0) {
				/* make sure others can break the lock */
      chmod (hitch,LOCKPROTECTION);
				/* get device/inode of hitch file */
      if (fstat (ld,&fsb)) return die ("hitch fstat failure",errno);
      close (ld);		/* close the hitching-post */
      /* Note: link() may return an error even if it actually succeeded.  So we
       * always check for success via the link count, and ignore the error if
       * the link count is right.
       */
				/* tie hitching-post to lock */
      i = link (hitch,lock) ? errno : 0;
				/* success if link count now 2 */
      if (stat (hitch,&sb) || (sb.st_nlink != 2) ||
	  (fsb.st_dev != sb.st_dev) || (fsb.st_ino != sb.st_ino)) {
	ld = -1;		/* failed to hitch */
	if (i == EPERM) {	/* was it because links not allowed? */
	  /* Probably a FAT filesystem on Linux.  It can't be NFS, so try
	   * creating the lock file directly.
	   */
	  if ((ld = open (lock,O_WRONLY|O_CREAT|O_EXCL,LOCKPROTECTION)) >= 0) {
	    /* get device/inode of lock file */
	    if (fstat (ld,&fsb)) return die ("lock fstat failure",errno);
	    close (ld);		/* close the file */
	  }
				/* give up immediately if protection failure */
	  else if (errno != EEXIST) tries = 0;
	}
      }
      unlink (hitch);		/* flush hitching post */
    }
				/* give up immediately if protection failure */
    else if (errno == EACCES) tries = 0;
    if (ld < 0) {		/* lock failed */
      if (tries--) sleep (1);	/* sleep 1 second and try again */
      else {
	write (1,"-",1);	/* hard failure */
	return EX_CANTCREAT;
      }
    }
  } while (ld < 0);
  write (1,"+",1);		/* indicate that all is well */
  read (0,tmp,1);		/* read continue signal from parent */
				/* flush the lock file */
  if (!stat (lock,&sb) && (fsb.st_dev == sb.st_dev) &&
      (fsb.st_ino == sb.st_ino)) unlink (lock);
  else syslog (LOG_NOTICE,"lock file %s/%s changed dev/inode",dir,lock);
  return EX_OK;
}