d8307d
commit d4625a19fe64f664119a541b317fb83de01bb273
d8307d
Author: Florian Weimer <fweimer@redhat.com>
d8307d
Date:   Tue Nov 12 12:25:49 2019 +0100
d8307d
d8307d
    login: Use pread64 in utmp implementation
d8307d
    
d8307d
    This reduces the possible error scenarios considerably because
d8307d
    no longer can file seek fail, leaving the file descriptor in an
d8307d
    inconsistent state and out of sync with the cache.
d8307d
    
d8307d
    As a result, it is possible to avoid setting file_offset to -1
d8307d
    to make an error persistent.  Instead, subsequent calls will retry
d8307d
    the operation and report any errors returned by the kernel.
d8307d
    
d8307d
    This change also avoids reading the file from the start if pututline
d8307d
    is called multiple times, to work around lock acquisition failures
d8307d
    due to timeouts.
d8307d
    
d8307d
    Change-Id: If21ea0c162c38830a89331ea93cddec14c0974de
d8307d
d8307d
diff --git a/login/utmp_file.c b/login/utmp_file.c
d8307d
index e653d14967c4fb7a..c828a28ac54c150e 100644
d8307d
--- a/login/utmp_file.c
d8307d
+++ b/login/utmp_file.c
d8307d
@@ -162,12 +162,35 @@ maybe_setutent (void)
d8307d
   return file_fd >= 0 || __libc_setutent ();
d8307d
 }
d8307d
 
d8307d
+/* Reads the entry at file_offset, storing it in last_entry and
d8307d
+   updating file_offset on success.  Returns -1 for a read error, 0
d8307d
+   for EOF, and 1 for a successful read.  last_entry and file_offset
d8307d
+   are only updated on a successful and complete read.  */
d8307d
+static ssize_t
d8307d
+read_last_entry (void)
d8307d
+{
d8307d
+  struct utmp buffer;
d8307d
+  ssize_t nbytes = __pread64_nocancel (file_fd, &buffer, sizeof (buffer),
d8307d
+				       file_offset);
d8307d
+  if (nbytes < 0)
d8307d
+    return -1;
d8307d
+  else if (nbytes != sizeof (buffer))
d8307d
+    /* Assume EOF.  */
d8307d
+    return 0;
d8307d
+  else
d8307d
+    {
d8307d
+      last_entry = buffer;
d8307d
+      file_offset += sizeof (buffer);
d8307d
+      return 1;
d8307d
+    }
d8307d
+}
d8307d
+
d8307d
 int
d8307d
 __libc_getutent_r (struct utmp *buffer, struct utmp **result)
d8307d
 {
d8307d
-  ssize_t nbytes;
d8307d
+  int saved_errno = errno;
d8307d
 
d8307d
-  if (!maybe_setutent () || file_offset == -1l)
d8307d
+  if (!maybe_setutent ())
d8307d
     {
d8307d
       /* Not available.  */
d8307d
       *result = NULL;
d8307d
@@ -175,25 +198,22 @@ __libc_getutent_r (struct utmp *buffer, struct utmp **result)
d8307d
     }
d8307d
 
d8307d
   if (try_file_lock (file_fd, F_RDLCK))
d8307d
-    nbytes = 0;
d8307d
-  else
d8307d
-    {
d8307d
-      /* Read the next entry.  */
d8307d
-      nbytes = __read_nocancel (file_fd, &last_entry, sizeof (struct utmp));
d8307d
-      file_unlock (file_fd);
d8307d
-    }
d8307d
+    return -1;
d8307d
 
d8307d
-  if (nbytes != sizeof (struct utmp))
d8307d
+  ssize_t nbytes = read_last_entry ();
d8307d
+  file_unlock (file_fd);
d8307d
+
d8307d
+  if (nbytes <= 0)		/* Read error or EOF.  */
d8307d
     {
d8307d
-      if (nbytes != 0)
d8307d
-	file_offset = -1l;
d8307d
+      if (nbytes == 0)
d8307d
+	/* errno should be unchanged to indicate success.  A premature
d8307d
+	   EOF is treated like an EOF (missing complete record at the
d8307d
+	   end).  */
d8307d
+	__set_errno (saved_errno);
d8307d
       *result = NULL;
d8307d
       return -1;
d8307d
     }
d8307d
 
d8307d
-  /* Update position pointer.  */
d8307d
-  file_offset += sizeof (struct utmp);
d8307d
-
d8307d
   memcpy (buffer, &last_entry, sizeof (struct utmp));
d8307d
   *result = buffer;
d8307d
 
d8307d
@@ -209,15 +229,15 @@ internal_getut_nolock (const struct utmp *id)
d8307d
 {
d8307d
   while (1)
d8307d
     {
d8307d
-      /* Read the next entry.  */
d8307d
-      if (__read_nocancel (file_fd, &last_entry, sizeof (struct utmp))
d8307d
-	  != sizeof (struct utmp))
d8307d
+      ssize_t nbytes = read_last_entry ();
d8307d
+      if (nbytes < 0)
d8307d
+	return -1;
d8307d
+      if (nbytes == 0)
d8307d
 	{
d8307d
+	  /* End of file reached.  */
d8307d
 	  __set_errno (ESRCH);
d8307d
-	  file_offset = -1l;
d8307d
 	  return -1;
d8307d
 	}
d8307d
-      file_offset += sizeof (struct utmp);
d8307d
 
d8307d
       if (matches_last_entry (id))
d8307d
 	break;
d8307d
@@ -249,7 +269,7 @@ int
d8307d
 __libc_getutid_r (const struct utmp *id, struct utmp *buffer,
d8307d
 		  struct utmp **result)
d8307d
 {
d8307d
-  if (!maybe_setutent () || file_offset == -1l)
d8307d
+  if (!maybe_setutent ())
d8307d
     {
d8307d
       *result = NULL;
d8307d
       return -1;
d8307d
@@ -276,7 +296,7 @@ int
d8307d
 __libc_getutline_r (const struct utmp *line, struct utmp *buffer,
d8307d
 		    struct utmp **result)
d8307d
 {
d8307d
-  if (!maybe_setutent () || file_offset == -1l)
d8307d
+  if (!maybe_setutent ())
d8307d
     {
d8307d
       *result = NULL;
d8307d
       return -1;
d8307d
@@ -290,16 +310,21 @@ __libc_getutline_r (const struct utmp *line, struct utmp *buffer,
d8307d
 
d8307d
   while (1)
d8307d
     {
d8307d
-      /* Read the next entry.  */
d8307d
-      if (__read_nocancel (file_fd, &last_entry, sizeof (struct utmp))
d8307d
-	  != sizeof (struct utmp))
d8307d
+      ssize_t nbytes = read_last_entry ();
d8307d
+      if (nbytes < 0)
d8307d
 	{
d8307d
+	  file_unlock (file_fd);
d8307d
+	  *result = NULL;
d8307d
+	  return -1;
d8307d
+	}
d8307d
+      if (nbytes == 0)
d8307d
+	{
d8307d
+	  /* End of file reached.  */
d8307d
+	  file_unlock (file_fd);
d8307d
 	  __set_errno (ESRCH);
d8307d
-	  file_offset = -1l;
d8307d
 	  *result = NULL;
d8307d
-	  goto unlock_return;
d8307d
+	  return -1;
d8307d
 	}
d8307d
-      file_offset += sizeof (struct utmp);
d8307d
 
d8307d
       /* Stop if we found a user or login entry.  */
d8307d
       if ((last_entry.ut_type == USER_PROCESS
d8307d
@@ -309,20 +334,18 @@ __libc_getutline_r (const struct utmp *line, struct utmp *buffer,
d8307d
 	break;
d8307d
     }
d8307d
 
d8307d
+  file_unlock (file_fd);
d8307d
   memcpy (buffer, &last_entry, sizeof (struct utmp));
d8307d
   *result = buffer;
d8307d
 
d8307d
-unlock_return:
d8307d
-  file_unlock (file_fd);
d8307d
-
d8307d
-  return ((*result == NULL) ? -1 : 0);
d8307d
+  return 0;
d8307d
 }
d8307d
 
d8307d
 
d8307d
 struct utmp *
d8307d
 __libc_pututline (const struct utmp *data)
d8307d
 {
d8307d
-  if (!maybe_setutent () || file_offset == -1l)
d8307d
+  if (!maybe_setutent ())
d8307d
     return NULL;
d8307d
 
d8307d
   struct utmp *pbuf;
d8307d
@@ -337,8 +360,7 @@ __libc_pututline (const struct utmp *data)
d8307d
       if (new_fd == -1)
d8307d
 	return NULL;
d8307d
 
d8307d
-      if (__lseek64 (new_fd, __lseek64 (file_fd, 0, SEEK_CUR), SEEK_SET) == -1
d8307d
-	  || __dup2 (new_fd, file_fd) < 0)
d8307d
+      if (__dup2 (new_fd, file_fd) < 0)
d8307d
 	{
d8307d
 	  __close_nocancel_nostatus (new_fd);
d8307d
 	  return NULL;
d8307d
@@ -355,69 +377,70 @@ __libc_pututline (const struct utmp *data)
d8307d
   bool found = false;
d8307d
   if (matches_last_entry (data))
d8307d
     {
d8307d
-      if (__lseek64 (file_fd, file_offset, SEEK_SET) < 0)
d8307d
+      /* Read back the entry under the write lock.  */
d8307d
+      file_offset -= sizeof (last_entry);
d8307d
+      ssize_t nbytes = read_last_entry ();
d8307d
+      if (nbytes < 0)
d8307d
 	{
d8307d
 	  file_unlock (file_fd);
d8307d
 	  return NULL;
d8307d
 	}
d8307d
-      if (__read_nocancel (file_fd, &last_entry, sizeof (last_entry))
d8307d
-	  != sizeof (last_entry))
d8307d
-	{
d8307d
-	  if (__lseek64 (file_fd, file_offset, SEEK_SET) < 0)
d8307d
-	    {
d8307d
-	      file_unlock (file_fd);
d8307d
-	      return NULL;
d8307d
-	    }
d8307d
-	  found = false;
d8307d
-	}
d8307d
+
d8307d
+      if (nbytes == 0)
d8307d
+	/* End of file reached.  */
d8307d
+	found = false;
d8307d
       else
d8307d
 	found = matches_last_entry (data);
d8307d
     }
d8307d
 
d8307d
   if (!found)
d8307d
+    /* Search forward for the entry.  */
d8307d
     found = internal_getut_nolock (data) >= 0;
d8307d
 
d8307d
+  off64_t write_offset;
d8307d
   if (!found)
d8307d
     {
d8307d
       /* We append the next entry.  */
d8307d
-      file_offset = __lseek64 (file_fd, 0, SEEK_END);
d8307d
-      if (file_offset % sizeof (struct utmp) != 0)
d8307d
-	{
d8307d
-	  file_offset -= file_offset % sizeof (struct utmp);
d8307d
-	  __ftruncate64 (file_fd, file_offset);
d8307d
-
d8307d
-	  if (__lseek64 (file_fd, 0, SEEK_END) < 0)
d8307d
-	    {
d8307d
-	      pbuf = NULL;
d8307d
-	      goto unlock_return;
d8307d
-	    }
d8307d
-	}
d8307d
+      write_offset = __lseek64 (file_fd, 0, SEEK_END);
d8307d
+
d8307d
+      /* Round down to the next multiple of the entry size.  This
d8307d
+	 ensures any partially-written record is overwritten by the
d8307d
+	 new record.  */
d8307d
+      write_offset = (write_offset / sizeof (struct utmp)
d8307d
+		      * sizeof (struct utmp));
d8307d
     }
d8307d
   else
d8307d
+    /* Overwrite last_entry.  */
d8307d
+    write_offset = file_offset - sizeof (struct utmp);
d8307d
+
d8307d
+  /* Write the new data.  */
d8307d
+  ssize_t nbytes;
d8307d
+  if (__lseek64 (file_fd, write_offset, SEEK_SET) < 0
d8307d
+      || (nbytes = __write_nocancel (file_fd, data, sizeof (struct utmp))) < 0)
d8307d
     {
d8307d
-      /* We replace the just read entry.  */
d8307d
-      file_offset -= sizeof (struct utmp);
d8307d
-      __lseek64 (file_fd, file_offset, SEEK_SET);
d8307d
+      /* There is no need to recover the file position because all
d8307d
+	 reads use pread64, and any future write is preceded by
d8307d
+	 another seek.  */
d8307d
+      file_unlock (file_fd);
d8307d
+      return NULL;
d8307d
     }
d8307d
 
d8307d
-  /* Write the new data.  */
d8307d
-  if (__write_nocancel (file_fd, data, sizeof (struct utmp))
d8307d
-      != sizeof (struct utmp))
d8307d
+  if (nbytes != sizeof (struct utmp))
d8307d
     {
d8307d
       /* If we appended a new record this is only partially written.
d8307d
 	 Remove it.  */
d8307d
       if (!found)
d8307d
-	(void) __ftruncate64 (file_fd, file_offset);
d8307d
-      pbuf = NULL;
d8307d
-    }
d8307d
-  else
d8307d
-    {
d8307d
-      file_offset += sizeof (struct utmp);
d8307d
-      pbuf = (struct utmp *) data;
d8307d
+	(void) __ftruncate64 (file_fd, write_offset);
d8307d
+      file_unlock (file_fd);
d8307d
+      /* Assume that the write failure was due to missing disk
d8307d
+	 space.  */
d8307d
+      __set_errno (ENOSPC);
d8307d
+      return NULL;
d8307d
     }
d8307d
 
d8307d
- unlock_return:
d8307d
   file_unlock (file_fd);
d8307d
+  file_offset = write_offset + sizeof (struct utmp);
d8307d
+  pbuf = (struct utmp *) data;
d8307d
 
d8307d
   return pbuf;
d8307d
 }