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