All of lore.kernel.org
 help / color / mirror / Atom feed
* SMP enabled restorecon
@ 2006-07-29 15:17 Russell Coker
  2006-07-29 15:20 ` Russell Coker
  2006-07-30 13:13 ` Russell Coker
  0 siblings, 2 replies; 7+ messages in thread
From: Russell Coker @ 2006-07-29 15:17 UTC (permalink / raw)
  To: SE-Linux

[-- Attachment #1: Type: text/plain, Size: 1500 bytes --]

Below are the results of running the FC5+updates version of restorecon vs my 
patched version with SMP support on a PentiumD-2.8GHz model 920.  The elapsed 
time is cut by 40%!  The user time reported appears to be a bug in the time 
command, when I observe the run via the "top" command I see two restorecon 
processes each taking about 26 seconds of CPU time before they complete.

The theoretical benefit of the SMP change should be a factor of two 
performance boost minus the overhead of the process that stats files to get 
the cache hot.  What I am seeing in my tests is quite a bit less than that, 
I'm not sure how much of this is due to my coding not being as efficient as 
it might and how much is due to inherent overheads of SMP operation.

orig:
time restorecon -R -v /usr/share
real    0m50.859s
user    0m44.743s
sys     0m5.704s

smp:
time ./restorecon -R -v /usr/share
real    0m31.275s
user    0m26.690s
sys     0m4.108s


With this patch a fairly default install of FC5 on a Pentium-D system should 
be able to perform an autorelabel operation in less than one minute.

Please test this out and let me know how it goes.  Also note that setfiles 
needs the same work done (but it's easier to test it out on restorecon 
first).

-- 
http://www.coker.com.au/selinux/   My NSA Security Enhanced Linux packages
http://www.coker.com.au/bonnie++/  Bonnie++ hard drive benchmark
http://www.coker.com.au/postal/    Postal SMTP/POP benchmark
http://www.coker.com.au/~russell/  My home page

[-- Attachment #2: diff --]
[-- Type: text/x-diff, Size: 7378 bytes --]

Only in policycoreutils-1.30.10/restorecon: restorecon
diff -ru policycoreutils-1.30.10.orig/restorecon/restorecon.c policycoreutils-1.30.10/restorecon/restorecon.c
--- policycoreutils-1.30.10.orig/restorecon/restorecon.c	2006-05-23 20:20:02.000000000 +1000
+++ policycoreutils-1.30.10/restorecon/restorecon.c	2006-07-30 01:05:36.000000000 +1000
@@ -50,6 +50,15 @@
 #define STAT_BLOCK_SIZE 1
 static int pipe_fds[2] = { -1, -1 };
 
+#define WORKER_SUCCESS 'g'
+#define WORKER_FAILURE 'b'
+int *worker_send_fds = NULL;
+int *worker_recv_fds = NULL;
+int num_workers = 0;
+int max_worker_handle = 0;
+int *worker_use_count = NULL;
+#define MAX(XA, XB) ((XA) > (XB)) ? (XA) : (XB)
+
 #define MAX_EXCLUDES 100
 static int excludeCtr=0;
 struct edir {
@@ -241,32 +250,244 @@
   return 0;
 }
 
+void get_worker_data(int finishing)
+{
+  int i, count;
+  fd_set read_set;
+  struct timeval tv;
+  tv.tv_sec = finishing ? 1 : 0;
+  tv.tv_usec = 0;
+
+  FD_ZERO(&read_set);
+  for(i = 0; i < num_workers; i++)
+    FD_SET(worker_recv_fds[i], &read_set);
+
+  count = select(max_worker_handle, &read_set, NULL, NULL, &tv);
+  if(count == 0)
+    return;
+  if(count == -1)
+  {
+    perror("Worker select error");
+    exit(1);
+  }
+  for(i = 0; i < num_workers; i++)
+  {
+    if(FD_ISSET(worker_recv_fds[i], &read_set))
+    {
+      char c;
+      int rc = read(worker_recv_fds[i], &c, 1);
+      if(rc != 1)
+      {
+        fprintf(stderr, "Error reading result from worker %d, aborting.\n", i);
+        exit(1);
+      }
+      if(c != WORKER_SUCCESS)
+        errors++;
+      worker_use_count[i]--;
+      count--;
+    }
+  }
+  if(count != 0)
+  {
+    fprintf(stderr, "Count should be 0!\n");
+    exit(1);
+  }
+}
+
 static int apply_spec(const char *file,
 		      const struct stat *sb_unused __attribute__((unused)),
 		      int flag,
 		      struct FTW *s_unused __attribute__((unused)))
 {
-	char buf[STAT_BLOCK_SIZE];
-	if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
-	{
-		fprintf(stderr, "Read error on pipe.\n");
-		pipe_fds[0] = -1;
-	}
-	if (flag == FTW_DNR) {
-		fprintf(stderr, "%s:  unable to read directory %s\n",
-			progname, file);
-		return 0;
-	}
-	errors=errors+restore(file);
-	return 0;
+  char buf[STAT_BLOCK_SIZE];
+
+  if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
+  {
+    fprintf(stderr, "Read error on pipe.\n");
+    pipe_fds[0] = -1;
+  }
+  if (flag == FTW_DNR) {
+    fprintf(stderr, "%s:  unable to read directory %s\n",
+    progname, file);
+    return 0;
+  }
+  if(num_workers)
+  {
+    int i, rc;
+    get_worker_data(0);
+    for(i = 0; i < num_workers; i++)
+    {
+      /* Have two entries in the queue for each worker so that if the master
+         starts doing some regex work the workers don't starve */
+      if(worker_use_count[i] < 2)
+      {
+        int offset = 0, len = strlen(file) + 1;
+
+        worker_use_count[i]++;
+        /* Could hurt performance if FILE_MAX*2 > the max pipe buffer */
+        while(1)
+        {
+          rc = write(worker_send_fds[i], &file[offset], len - offset);
+          if(rc == -1)
+          {
+            fprintf(stderr, "Can't write to worker %d\n", i);
+            exit(1);
+          }
+          offset += rc;
+          if(offset == len)
+            return 0;
+        }
+      }
+    }
+  }
+  errors=errors+restore(file);
+  return 0;
 }
+
+int num_cpus()
+{
+  FILE *fp = fopen("/proc/cpuinfo", "r");
+  char buf[1024];
+  int num = 0;
+
+  if(!fp)
+    return 1;
+  while(fgets(buf, sizeof(buf), fp))
+  {
+    if(!strncmp(buf, "processor", 9))
+      num++;
+  }
+  fclose(fp);
+  if(num < 1)
+    return 1;
+  return num;
+}
+
+void worker_restore(int id, const char * const file, int fd_out)
+{
+  char res = WORKER_SUCCESS;
+  if(restore(file))
+  {
+    res = WORKER_FAILURE;
+  }
+  if(write(fd_out, &res, 1) != 1)
+  {
+    fprintf(stderr, "Worker %d can't write to result pipe, aborting.\n", id);
+    exit(1);
+  }
+}
+
+void worker(int id, int fd_in, int fd_out)
+{
+  char buf[PATH_MAX+1];
+  int len = 0;
+
+  while(1)
+  {
+    while(len == 0 || buf[len - 1] != '\0')
+    {
+      int string_len;
+      int rc = read(fd_in, buf + len, PATH_MAX - len);
+      if(rc == -1)
+      {
+        fprintf(stderr, "Worker %d exiting on read error.\n", id);
+        exit(1);
+      }
+      if(rc == 0)
+        exit(0); /* parent has exited */
+      len += rc;
+      buf[len] = '\0';
+      string_len = strlen(buf);
+      while(string_len != len)
+      {
+        worker_restore(id, buf, fd_out);
+        len = len - string_len - 1;
+        memmove(buf, buf + string_len + 1, len + 1);
+        string_len = strlen(buf);
+      }
+      if(len >= PATH_MAX)
+      {
+        fprintf(stderr, "Worker %d received excess data from parent.\n", id);
+        exit(1);
+      }
+    }
+    worker_restore(id, buf, fd_out);
+    len = 0;
+  }
+  if(len != 0)
+  {
+    fprintf(stderr, "Worker %d can't read from pipe, aborting.\n", id);
+    exit(1);
+  }
+  exit(0);
+}
+
+void start_workers()
+{
+  int send_fds[2], recv_fds[2], i;
+
+  num_workers = num_cpus() - 1;
+  if(!num_workers)
+    return;
+  worker_send_fds = malloc(sizeof(int) * num_workers);
+  worker_recv_fds = malloc(sizeof(int) * num_workers);
+  worker_use_count = malloc(sizeof(int) * num_workers);
+  if(!worker_send_fds || !worker_recv_fds || !worker_use_count)
+  {
+    fprintf(stderr, "Malloc failure.\n");
+    exit(1);
+  }
+
+  matchpathcon_init(NULL);
+
+  for(i = 0; i < num_workers; i++)
+  {
+    int rc;
+
+    if(pipe(send_fds) || pipe(recv_fds))
+    {
+      fprintf(stderr, "Can't create pipe.\n");
+      exit(1);
+    }
+    worker_send_fds[i] = send_fds[1];
+    worker_recv_fds[i] = recv_fds[0];
+    worker_use_count[i] = 0;
+    max_worker_handle = MAX(max_worker_handle, MAX(worker_send_fds[i], worker_recv_fds[i]));
+    rc = fork();
+    if(rc == -1)
+    {
+      fprintf(stderr, "fork error\n");
+      exit(1);
+    }
+    if(!rc)
+    {
+      int j;
+      for(j = i - 1; j >= 0; j--)
+      {
+        close(worker_send_fds[j]);
+        close(worker_recv_fds[j]);
+      }
+      close(send_fds[1]);
+      close(recv_fds[0]);
+      worker(i, send_fds[0], recv_fds[1]);
+      exit(2); /* should never reach this code */
+    }
+    close(send_fds[0]);
+    close(recv_fds[1]);
+  }
+  max_worker_handle++;
+}
+
 void process(char *buf) {
       int rc;
+
       if (recurse) {
 	if(pipe(pipe_fds) == -1)
-	  rc = -1;
-	else
-	  rc = fork();
+        {
+          fprintf(stderr, "Can't create pipe.\n");
+          exit(1);
+        }
+	rc = fork();
 	if(rc == 0)
 	{
 	  close(pipe_fds[0]);
@@ -276,10 +497,22 @@
 	if(rc > 0)
 	  close(pipe_fds[1]);
 	if(rc == -1 || rc > 0) {
+          start_workers();
 	  if (nftw(buf, apply_spec, 1024, FTW_PHYS)) {
 	    fprintf(stderr, "%s:  error while labeling files under %s\n",
 		  progname, buf);
 	    errors++;
+            if(num_workers)
+            {
+              int i;
+              for(i = 0; i < num_workers; i++)
+              {
+                while(worker_use_count[i])
+                  get_worker_data(1);
+                close(worker_send_fds[i]);
+                close(worker_recv_fds[i]);
+              }
+            }
 	  }
 	}
       }
Only in policycoreutils-1.30.10/restorecon: restorecon.o

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: SMP enabled restorecon
  2006-07-29 15:17 SMP enabled restorecon Russell Coker
@ 2006-07-29 15:20 ` Russell Coker
  2006-07-30 13:13 ` Russell Coker
  1 sibling, 0 replies; 7+ messages in thread
From: Russell Coker @ 2006-07-29 15:20 UTC (permalink / raw)
  To: SE-Linux

> Please test this out and let me know how it goes.  Also note that setfiles
> needs the same work done (but it's easier to test it out on restorecon
> first).

I almost forgot, the code for recognising a SMP system isn't particularly 
good.  On a HT system you will get a restorecon process for each thread which 
will probably give bad performance.  If someone knows how to recognise the 
real number of CPUs in a HT system then please let me know.

Also does Linux correctly optimise for HT yet?  If I have two HT CPUs and two 
processes running will I have one process on each CPU not two processes on 
the same CPU?

-- 
http://www.coker.com.au/selinux/   My NSA Security Enhanced Linux packages
http://www.coker.com.au/bonnie++/  Bonnie++ hard drive benchmark
http://www.coker.com.au/postal/    Postal SMTP/POP benchmark
http://www.coker.com.au/~russell/  My home page

--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: SMP enabled restorecon
  2006-07-29 15:17 SMP enabled restorecon Russell Coker
  2006-07-29 15:20 ` Russell Coker
@ 2006-07-30 13:13 ` Russell Coker
  2006-07-30 14:12   ` Russell Coker
  1 sibling, 1 reply; 7+ messages in thread
From: Russell Coker @ 2006-07-30 13:13 UTC (permalink / raw)
  To: SE-Linux

[-- Attachment #1: Type: text/plain, Size: 2987 bytes --]

On Sunday 30 July 2006 01:17, Russell Coker <russell@coker.com.au> wrote:
> Below are the results of running the FC5+updates version of restorecon vs
> my patched version with SMP support on a PentiumD-2.8GHz model 920.  The
> elapsed time is cut by 40%!  The user time reported appears to be a bug in
> the time command, when I observe the run via the "top" command I see two
> restorecon processes each taking about 26 seconds of CPU time before they
> complete.

I've attached a similar patch for setfiles, this patch adds a "-a" option to 
setfiles to skip the association check because it's really difficult to 
preserve that check while supporting SMP and giving good performance, and 
also because checking for associations isn't as useful when the results go 
to /dev/null anyway.

On my Pentium-D system with a 160G S-ATA disk the regular setfiles causes an 
autorelabel to take the following amount of time:
real    1m23.697s
user    0m59.916s
sys     0m4.892s

While my modified version takes the following:
real    1m19.236s
user    0m37.498s
sys     0m4.352s

Surprisingly it only saves 4.4s (5.5% performance increase).  This isn't 
great, but we have to keep in mind the fact that my test machine has a very 
fast CPU and only a single disk so having IO being the bottleneck is expected 
(in fact it's desired but I didn't expect to hit it so soon).

I have a suspicion that if I was to perform the same test on a machine with a 
root filesystem on an EMC SAN (I currently run a couple of dozen such 
machines at the moment) then I would find that IO isn't the bottleneck and 
that my modified version of setfiles would probably double the performance.  
I don't plan to perform such tests as the EMC connected machines in question 
only run RHEL3 and RHEL4 and I am not going to back-port the code (no-one 
would be prepared to run such modified code on RHEL4 in a production machine 
so it's a bit pointless).

I also suspect that if I installed the four S-ATA disks and three ATA-133 
disks that my Pentium-D machine supports and ran Linux software RAID-5 then 
again I could get significantly better than a 5.5% performance boost from the 
SMP aware version of setfiles.

I hope that no-one will reject my patch because I don't have suitable test 
hardware to demonstrate it's benefits.  It seems quite obvious to me that 
there is a need to get the best possible performance from SMP machines that 
have CPUs that are slower than a new Pentium-D and which also have better IO 
performance than a single S-ATA disk - I expect that most SMP machines that 
are in production satisfy at least one of these criteria and a large portion 
of them satisfy both.

-- 
http://www.coker.com.au/selinux/   My NSA Security Enhanced Linux packages
http://www.coker.com.au/bonnie++/  Bonnie++ hard drive benchmark
http://www.coker.com.au/postal/    Postal SMTP/POP benchmark
http://www.coker.com.au/~russell/  My home page

[-- Attachment #2: diff --]
[-- Type: text/x-diff, Size: 15702 bytes --]

Only in policycoreutils-1.30.10/restorecon: restorecon
diff -ru policycoreutils-1.30.10.orig/restorecon/restorecon.c policycoreutils-1.30.10/restorecon/restorecon.c
--- policycoreutils-1.30.10.orig/restorecon/restorecon.c	2006-05-23 20:20:02.000000000 +1000
+++ policycoreutils-1.30.10/restorecon/restorecon.c	2006-07-30 01:05:36.000000000 +1000
@@ -50,6 +50,15 @@
 #define STAT_BLOCK_SIZE 1
 static int pipe_fds[2] = { -1, -1 };
 
+#define WORKER_SUCCESS 'g'
+#define WORKER_FAILURE 'b'
+int *worker_send_fds = NULL;
+int *worker_recv_fds = NULL;
+int num_workers = 0;
+int max_worker_handle = 0;
+int *worker_use_count = NULL;
+#define MAX(XA, XB) ((XA) > (XB)) ? (XA) : (XB)
+
 #define MAX_EXCLUDES 100
 static int excludeCtr=0;
 struct edir {
@@ -241,32 +250,244 @@
   return 0;
 }
 
+void get_worker_data(int finishing)
+{
+  int i, count;
+  fd_set read_set;
+  struct timeval tv;
+  tv.tv_sec = finishing ? 1 : 0;
+  tv.tv_usec = 0;
+
+  FD_ZERO(&read_set);
+  for(i = 0; i < num_workers; i++)
+    FD_SET(worker_recv_fds[i], &read_set);
+
+  count = select(max_worker_handle, &read_set, NULL, NULL, &tv);
+  if(count == 0)
+    return;
+  if(count == -1)
+  {
+    perror("Worker select error");
+    exit(1);
+  }
+  for(i = 0; i < num_workers; i++)
+  {
+    if(FD_ISSET(worker_recv_fds[i], &read_set))
+    {
+      char c;
+      int rc = read(worker_recv_fds[i], &c, 1);
+      if(rc != 1)
+      {
+        fprintf(stderr, "Error reading result from worker %d, aborting.\n", i);
+        exit(1);
+      }
+      if(c != WORKER_SUCCESS)
+        errors++;
+      worker_use_count[i]--;
+      count--;
+    }
+  }
+  if(count != 0)
+  {
+    fprintf(stderr, "Count should be 0!\n");
+    exit(1);
+  }
+}
+
 static int apply_spec(const char *file,
 		      const struct stat *sb_unused __attribute__((unused)),
 		      int flag,
 		      struct FTW *s_unused __attribute__((unused)))
 {
-	char buf[STAT_BLOCK_SIZE];
-	if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
-	{
-		fprintf(stderr, "Read error on pipe.\n");
-		pipe_fds[0] = -1;
-	}
-	if (flag == FTW_DNR) {
-		fprintf(stderr, "%s:  unable to read directory %s\n",
-			progname, file);
-		return 0;
-	}
-	errors=errors+restore(file);
-	return 0;
+  char buf[STAT_BLOCK_SIZE];
+
+  if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
+  {
+    fprintf(stderr, "Read error on pipe.\n");
+    pipe_fds[0] = -1;
+  }
+  if (flag == FTW_DNR) {
+    fprintf(stderr, "%s:  unable to read directory %s\n",
+    progname, file);
+    return 0;
+  }
+  if(num_workers)
+  {
+    int i, rc;
+    get_worker_data(0);
+    for(i = 0; i < num_workers; i++)
+    {
+      /* Have two entries in the queue for each worker so that if the master
+         starts doing some regex work the workers don't starve */
+      if(worker_use_count[i] < 2)
+      {
+        int offset = 0, len = strlen(file) + 1;
+
+        worker_use_count[i]++;
+        /* Could hurt performance if FILE_MAX*2 > the max pipe buffer */
+        while(1)
+        {
+          rc = write(worker_send_fds[i], &file[offset], len - offset);
+          if(rc == -1)
+          {
+            fprintf(stderr, "Can't write to worker %d\n", i);
+            exit(1);
+          }
+          offset += rc;
+          if(offset == len)
+            return 0;
+        }
+      }
+    }
+  }
+  errors=errors+restore(file);
+  return 0;
 }
+
+int num_cpus()
+{
+  FILE *fp = fopen("/proc/cpuinfo", "r");
+  char buf[1024];
+  int num = 0;
+
+  if(!fp)
+    return 1;
+  while(fgets(buf, sizeof(buf), fp))
+  {
+    if(!strncmp(buf, "processor", 9))
+      num++;
+  }
+  fclose(fp);
+  if(num < 1)
+    return 1;
+  return num;
+}
+
+void worker_restore(int id, const char * const file, int fd_out)
+{
+  char res = WORKER_SUCCESS;
+  if(restore(file))
+  {
+    res = WORKER_FAILURE;
+  }
+  if(write(fd_out, &res, 1) != 1)
+  {
+    fprintf(stderr, "Worker %d can't write to result pipe, aborting.\n", id);
+    exit(1);
+  }
+}
+
+void worker(int id, int fd_in, int fd_out)
+{
+  char buf[PATH_MAX+1];
+  int len = 0;
+
+  while(1)
+  {
+    while(len == 0 || buf[len - 1] != '\0')
+    {
+      int string_len;
+      int rc = read(fd_in, buf + len, PATH_MAX - len);
+      if(rc == -1)
+      {
+        fprintf(stderr, "Worker %d exiting on read error.\n", id);
+        exit(1);
+      }
+      if(rc == 0)
+        exit(0); /* parent has exited */
+      len += rc;
+      buf[len] = '\0';
+      string_len = strlen(buf);
+      while(string_len != len)
+      {
+        worker_restore(id, buf, fd_out);
+        len = len - string_len - 1;
+        memmove(buf, buf + string_len + 1, len + 1);
+        string_len = strlen(buf);
+      }
+      if(len >= PATH_MAX)
+      {
+        fprintf(stderr, "Worker %d received excess data from parent.\n", id);
+        exit(1);
+      }
+    }
+    worker_restore(id, buf, fd_out);
+    len = 0;
+  }
+  if(len != 0)
+  {
+    fprintf(stderr, "Worker %d can't read from pipe, aborting.\n", id);
+    exit(1);
+  }
+  exit(0);
+}
+
+void start_workers()
+{
+  int send_fds[2], recv_fds[2], i;
+
+  num_workers = num_cpus() - 1;
+  if(!num_workers)
+    return;
+  worker_send_fds = malloc(sizeof(int) * num_workers);
+  worker_recv_fds = malloc(sizeof(int) * num_workers);
+  worker_use_count = malloc(sizeof(int) * num_workers);
+  if(!worker_send_fds || !worker_recv_fds || !worker_use_count)
+  {
+    fprintf(stderr, "Malloc failure.\n");
+    exit(1);
+  }
+
+  matchpathcon_init(NULL);
+
+  for(i = 0; i < num_workers; i++)
+  {
+    int rc;
+
+    if(pipe(send_fds) || pipe(recv_fds))
+    {
+      fprintf(stderr, "Can't create pipe.\n");
+      exit(1);
+    }
+    worker_send_fds[i] = send_fds[1];
+    worker_recv_fds[i] = recv_fds[0];
+    worker_use_count[i] = 0;
+    max_worker_handle = MAX(max_worker_handle, MAX(worker_send_fds[i], worker_recv_fds[i]));
+    rc = fork();
+    if(rc == -1)
+    {
+      fprintf(stderr, "fork error\n");
+      exit(1);
+    }
+    if(!rc)
+    {
+      int j;
+      for(j = i - 1; j >= 0; j--)
+      {
+        close(worker_send_fds[j]);
+        close(worker_recv_fds[j]);
+      }
+      close(send_fds[1]);
+      close(recv_fds[0]);
+      worker(i, send_fds[0], recv_fds[1]);
+      exit(2); /* should never reach this code */
+    }
+    close(send_fds[0]);
+    close(recv_fds[1]);
+  }
+  max_worker_handle++;
+}
+
 void process(char *buf) {
       int rc;
+
       if (recurse) {
 	if(pipe(pipe_fds) == -1)
-	  rc = -1;
-	else
-	  rc = fork();
+        {
+          fprintf(stderr, "Can't create pipe.\n");
+          exit(1);
+        }
+	rc = fork();
 	if(rc == 0)
 	{
 	  close(pipe_fds[0]);
@@ -276,10 +497,22 @@
 	if(rc > 0)
 	  close(pipe_fds[1]);
 	if(rc == -1 || rc > 0) {
+          start_workers();
 	  if (nftw(buf, apply_spec, 1024, FTW_PHYS)) {
 	    fprintf(stderr, "%s:  error while labeling files under %s\n",
 		  progname, buf);
 	    errors++;
+            if(num_workers)
+            {
+              int i;
+              for(i = 0; i < num_workers; i++)
+              {
+                while(worker_use_count[i])
+                  get_worker_data(1);
+                close(worker_send_fds[i]);
+                close(worker_recv_fds[i]);
+              }
+            }
 	  }
 	}
       }
Only in policycoreutils-1.30.10/restorecon: restorecon.o
Only in policycoreutils-1.30.10/setfiles: setfiles
diff -ru policycoreutils-1.30.10.orig/setfiles/setfiles.c policycoreutils-1.30.10/setfiles/setfiles.c
--- policycoreutils-1.30.10.orig/setfiles/setfiles.c	2006-05-23 20:20:03.000000000 +1000
+++ policycoreutils-1.30.10/setfiles/setfiles.c	2006-07-30 22:19:35.000000000 +1000
@@ -82,6 +82,15 @@
 #define STAT_BLOCK_SIZE 1
 static int pipe_fds[2] = { -1, -1 };
 
+#define WORKER_SUCCESS 'g'
+#define WORKER_FAILURE 'b'
+int *worker_send_fds = NULL;
+int *worker_recv_fds = NULL;
+int num_workers = 0;
+int max_worker_handle = 0;
+int *worker_use_count = NULL;
+#define MAX(XA, XB) ((XA) > (XB)) ? (XA) : (XB)
+
 #define MAX_EXCLUDES 100
 static int excludeCtr=0;
 struct edir {
@@ -232,27 +241,57 @@
 	return  (strcmp(rest_a, rest_b) == 0);
 }
 
-/*
- * Apply the last matching specification to a file.
- * This function is called by nftw on each file during
- * the directory traversal.
- */
-static int apply_spec(const char *file,
-		      const struct stat *sb_unused __attribute__((unused)),
-		      int flag,
-		      struct FTW *s_unused __attribute__((unused)))
+void get_worker_data(int finishing)
+{
+  int i, count;
+  fd_set read_set;
+  struct timeval tv;
+  tv.tv_sec = finishing ? 1 : 0;
+  tv.tv_usec = 0;
+
+  FD_ZERO(&read_set);
+  for(i = 0; i < num_workers; i++)
+    FD_SET(worker_recv_fds[i], &read_set);
+
+  count = select(max_worker_handle, &read_set, NULL, NULL, &tv);
+  if(count == 0)
+    return;
+  if(count == -1)
+  {
+    perror("Worker select error");
+    exit(1);
+  }
+  for(i = 0; i < num_workers; i++)
+  {
+    if(FD_ISSET(worker_recv_fds[i], &read_set))
+    {
+      char c;
+      int rc = read(worker_recv_fds[i], &c, 1);
+      if(rc != 1)
+      {
+        fprintf(stderr, "Error reading result from worker %d, aborting.\n", i);
+        exit(1);
+      }
+/*      if(c != WORKER_SUCCESS)
+        errors++;*/
+      worker_use_count[i]--;
+      count--;
+    }
+  }
+  if(count != 0)
+  {
+    fprintf(stderr, "Count should be 0!\n");
+    exit(1);
+  }
+}
+
+static int set_context(const char *file)
 {
 	const char *my_file;
 	struct stat my_sb;
 	int i, j, ret;
 	char *context, *newcon; 
 	int user_only_changed=0;
-	char buf[STAT_BLOCK_SIZE];
-	if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
-	{
-		fprintf(stderr, "Read error on pipe.\n");
-		pipe_fds[0] = -1;
-	}
 
 	/* Skip the extra slash at the beginning, if present. */
 	if (file[0] == '/' && file[1] == '/')
@@ -260,12 +299,6 @@
 	else
 		my_file = file;
 
-	if (flag == FTW_DNR) {
-		fprintf(stderr, "%s:  unable to read directory %s\n",
-			progname, my_file);
-		return 0;
-	}
-
 	i = match(my_file, &my_sb, &newcon);
 	if (i < 0)
 		/* No matching specification. */
@@ -382,6 +415,198 @@
 	return -1;
 }
 
+/*
+ * Apply the last matching specification to a file.
+ * This function is called by nftw on each file during
+ * the directory traversal.
+ */
+static int apply_spec(const char *file,
+		      const struct stat *sb_unused __attribute__((unused)),
+		      int flag,
+		      struct FTW *s_unused __attribute__((unused)))
+{
+  char buf[STAT_BLOCK_SIZE];
+  if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
+  {
+    fprintf(stderr, "Read error on pipe.\n");
+    pipe_fds[0] = -1;
+  }
+
+  if (flag == FTW_DNR)
+  {
+    fprintf(stderr, "%s:  unable to read directory %s\n",
+    progname, file);
+    return 0;
+  }
+
+  if(num_workers)
+  {
+    int i, rc;
+    get_worker_data(0);
+    for(i = 0; i < num_workers; i++)
+    {
+      /* Have two entries in the queue for each worker so that if the master
+         starts doing some regex work the workers don't starve */
+      if(worker_use_count[i] < 2)
+      {
+        int offset = 0, len = strlen(file) + 1;
+
+        worker_use_count[i]++;
+        /* Could hurt performance if FILE_MAX*2 > the max pipe buffer */
+        while(1)
+        {
+          rc = write(worker_send_fds[i], &file[offset], len - offset);
+          if(rc == -1)
+          {
+            fprintf(stderr, "Can't write to worker %d\n", i);
+            exit(1);
+          }
+          offset += rc;
+          if(offset == len)
+            return 0;
+        }
+      }
+    }
+  }
+  return set_context(file);
+}
+
+int num_cpus()
+{
+  FILE *fp = fopen("/proc/cpuinfo", "r");
+  char buf[1024];
+  int num = 0;
+
+  if(!fp)
+    return 1;
+  while(fgets(buf, sizeof(buf), fp))
+  {
+    if(!strncmp(buf, "processor", 9))
+      num++;
+  }
+  fclose(fp);
+  if(num < 1)
+    return 1;
+  return num;
+}
+
+void worker_restore(int id, const char * const file, int fd_out)
+{
+  char res = WORKER_SUCCESS;
+  if(set_context(file))
+  {
+    res = WORKER_FAILURE;
+  }
+  if(write(fd_out, &res, 1) != 1)
+  {
+    fprintf(stderr, "Worker %d can't write to result pipe, aborting.\n", id);
+    exit(1);
+  }
+}
+
+void worker(int id, int fd_in, int fd_out)
+{
+  char buf[PATH_MAX+1];
+  int len = 0;
+
+  while(1)
+  {
+    while(len == 0 || buf[len - 1] != '\0')
+    {
+      int string_len;
+      int rc = read(fd_in, buf + len, PATH_MAX - len);
+      if(rc == -1)
+      {
+        fprintf(stderr, "Worker %d exiting on read error.\n", id);
+        exit(1);
+      }
+      if(rc == 0)
+        exit(0); /* parent has exited */
+      len += rc;
+      buf[len] = '\0';
+      string_len = strlen(buf);
+      while(string_len != len)
+      {
+        worker_restore(id, buf, fd_out);
+        len = len - string_len - 1;
+        memmove(buf, buf + string_len + 1, len + 1);
+        string_len = strlen(buf);
+      }
+      if(len >= PATH_MAX)
+      {
+        fprintf(stderr, "Worker %d received excess data from parent.\n", id);
+        exit(1);
+      }
+    }
+    worker_restore(id, buf, fd_out);
+    len = 0;
+  }
+  if(len != 0)
+  {
+    fprintf(stderr, "Worker %d can't read from pipe, aborting.\n", id);
+    exit(1);
+  }
+  exit(0);
+}
+
+void start_workers()
+{
+  int send_fds[2], recv_fds[2], i;
+
+  if(add_assoc)
+    return;
+  num_workers = num_cpus() - 1;
+  if(!num_workers)
+    return;
+  worker_send_fds = malloc(sizeof(int) * num_workers);
+  worker_recv_fds = malloc(sizeof(int) * num_workers);
+  worker_use_count = malloc(sizeof(int) * num_workers);
+  if(!worker_send_fds || !worker_recv_fds || !worker_use_count)
+  {
+    fprintf(stderr, "Malloc failure.\n");
+    exit(1);
+  }
+
+  matchpathcon_init(NULL);
+
+  for(i = 0; i < num_workers; i++)
+  {
+    int rc;
+
+    if(pipe(send_fds) || pipe(recv_fds))
+    {
+      fprintf(stderr, "Can't create pipe.\n");
+      exit(1);
+    }
+    worker_send_fds[i] = send_fds[1];
+    worker_recv_fds[i] = recv_fds[0];
+    worker_use_count[i] = 0;
+    max_worker_handle = MAX(max_worker_handle, MAX(worker_send_fds[i], worker_recv_fds[i]));
+    rc = fork();
+    if(rc == -1)
+    {
+      fprintf(stderr, "fork error\n");
+      exit(1);
+    }
+    if(!rc)
+    {
+      int j;
+      for(j = i - 1; j >= 0; j--)
+      {
+        close(worker_send_fds[j]);
+        close(worker_recv_fds[j]);
+      }
+      close(send_fds[1]);
+      close(recv_fds[0]);
+      worker(i, send_fds[0], recv_fds[1]);
+      exit(2); /* should never reach this code */
+    }
+    close(send_fds[0]);
+    close(recv_fds[1]);
+  }
+  max_worker_handle++;
+}
+
 void set_rootpath(const char *arg)
 {
 	int len;
@@ -454,7 +679,7 @@
 	set_matchpathcon_flags(MATCHPATHCON_VALIDATE);
 
 	/* Process any options. */
-	while ((opt = getopt(argc, argv, "Fc:dlnqrsvWe:o:")) > 0) {
+	while ((opt = getopt(argc, argv, "aFc:dlnqrsvWe:o:")) > 0) {
 		switch (opt) {
 		case 'c':
 		{
@@ -529,6 +754,7 @@
 			break;
 		case 's':
 			use_stdin = 1;
+		case 'a':
 			add_assoc = 0;
 			break;
 		case 'v':
@@ -641,6 +867,7 @@
 			close(pipe_fds[1]);
 		if(rc == -1 || rc > 0) {
 
+			start_workers();
 		/* Walk the file tree, calling apply_spec on each file. */
 			if (nftw
 			    (argv[optind], apply_spec, 1024,
@@ -650,6 +877,17 @@
 				argv[0], argv[optind]);
 				exit(1);
 			}
+			if(num_workers)
+			{
+				int i;
+				for(i = 0; i < num_workers; i++)
+				{
+					while(worker_use_count[i])
+						get_worker_data(1);
+					close(worker_send_fds[i]);
+					close(worker_recv_fds[i]);
+				}
+			}
 		}
 
 		/*
Only in policycoreutils-1.30.10/setfiles: setfiles.o

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: SMP enabled restorecon
  2006-07-30 13:13 ` Russell Coker
@ 2006-07-30 14:12   ` Russell Coker
  2006-08-02 19:52     ` Daniel J Walsh
  0 siblings, 1 reply; 7+ messages in thread
From: Russell Coker @ 2006-07-30 14:12 UTC (permalink / raw)
  To: SE-Linux

[-- Attachment #1: Type: text/plain, Size: 3305 bytes --]

On Sunday 30 July 2006 23:13, Russell Coker <russell@coker.com.au> wrote:
> On Sunday 30 July 2006 01:17, Russell Coker <russell@coker.com.au> wrote:
> > Below are the results of running the FC5+updates version of restorecon vs
> > my patched version with SMP support on a PentiumD-2.8GHz model 920.  The
> > elapsed time is cut by 40%!  The user time reported appears to be a bug
> > in the time command, when I observe the run via the "top" command I see
> > two restorecon processes each taking about 26 seconds of CPU time before
> > they complete.
>
> I've attached a similar patch for setfiles, this patch adds a "-a" option
> to setfiles to skip the association check because it's really difficult to
> preserve that check while supporting SMP and giving good performance, and
> also because checking for associations isn't as useful when the results go
> to /dev/null anyway.

Sorry for replying to my own mail so much, I've been involved in discussion on 
IRC and other lists.

I've attached a new patch which calls wait() for each child process.  This 
causes CPU time use to be correctly billed to parent processes, the results 
are as follows:

Modified setfiles:
real    1m22.689s
user    1m13.181s
sys     0m12.641s

Orig setfiles:
real    1m23.697s
user    0m59.916s
sys     0m4.892s

Modified restorecon on /usr/share:
real    0m31.142s
user    0m52.671s
sys     0m7.728s

Orig restorecon:
real    0m51.429s
user    0m45.467s
sys     0m5.568s

It seems that in both cases the amount of CPU time used is significantly 
increased, and that the variance between test runs of setfiles is great 
enough that there can be little performance difference between my version and 
the stock one.

I think that I need to find a more CPU efficient method of IPC for the file 
names.  One possibility might be to use shared memory regions for the data.  
Another one might be to make the programs threaded.

A final possibility is that the current design of setfiles and restorecon may 
be a dead-end as far as SMP is concerned.  Having one process to stat the 
files and another to check the contexts when the cache is hot is efficient 
for UP systems.  For SMP systems it might be better to have one process find 
the files and then pass the names to worker processes that do the regex 
stuff.  Currently processing a file involves two processes calling nftw() 
(doing readdir() and stat()), the seeker writes a byte to a pipe and then the 
master will either pipe the file name to a child or process it directly 
before reading the byte from the seek process.  As I have the worker 
acknowledging each file the average number of pipe writes to process a file 
on a 2P system is two.

If the parent process was to be the only process calling nftw() and it then 
passed the file names via shared memory with specified minimum and maximum 
sizes for the shared memory we could probably get an average of considerably 
less than one IPC operation per file that is processes.

-- 
http://www.coker.com.au/selinux/   My NSA Security Enhanced Linux packages
http://www.coker.com.au/bonnie++/  Bonnie++ hard drive benchmark
http://www.coker.com.au/postal/    Postal SMTP/POP benchmark
http://www.coker.com.au/~russell/  My home page

[-- Attachment #2: diff --]
[-- Type: text/x-diff, Size: 15934 bytes --]

diff -ru policycoreutils-1.30.10.orig/restorecon/restorecon.c policycoreutils-1.30.10/restorecon/restorecon.c
--- policycoreutils-1.30.10.orig/restorecon/restorecon.c	2006-05-23 20:20:02.000000000 +1000
+++ policycoreutils-1.30.10/restorecon/restorecon.c	2006-07-30 23:26:21.000000000 +1000
@@ -34,6 +34,7 @@
 #include <selinux/selinux.h>
 #include <getopt.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -50,6 +51,15 @@
 #define STAT_BLOCK_SIZE 1
 static int pipe_fds[2] = { -1, -1 };
 
+#define WORKER_SUCCESS 'g'
+#define WORKER_FAILURE 'b'
+int *worker_send_fds = NULL;
+int *worker_recv_fds = NULL;
+int num_workers = 0;
+int max_worker_handle = 0;
+int *worker_use_count = NULL;
+#define MAX(XA, XB) ((XA) > (XB)) ? (XA) : (XB)
+
 #define MAX_EXCLUDES 100
 static int excludeCtr=0;
 struct edir {
@@ -241,32 +251,244 @@
   return 0;
 }
 
+void get_worker_data(int finishing)
+{
+  int i, count;
+  fd_set read_set;
+  struct timeval tv;
+  tv.tv_sec = finishing ? 1 : 0;
+  tv.tv_usec = 0;
+
+  FD_ZERO(&read_set);
+  for(i = 0; i < num_workers; i++)
+    FD_SET(worker_recv_fds[i], &read_set);
+
+  count = select(max_worker_handle, &read_set, NULL, NULL, &tv);
+  if(count == 0)
+    return;
+  if(count == -1)
+  {
+    perror("Worker select error");
+    exit(1);
+  }
+  for(i = 0; i < num_workers; i++)
+  {
+    if(FD_ISSET(worker_recv_fds[i], &read_set))
+    {
+      char c;
+      int rc = read(worker_recv_fds[i], &c, 1);
+      if(rc != 1)
+      {
+        fprintf(stderr, "Error reading result from worker %d, aborting.\n", i);
+        exit(1);
+      }
+      if(c != WORKER_SUCCESS)
+        errors++;
+      worker_use_count[i]--;
+      count--;
+    }
+  }
+  if(count != 0)
+  {
+    fprintf(stderr, "Count should be 0!\n");
+    exit(1);
+  }
+}
+
 static int apply_spec(const char *file,
 		      const struct stat *sb_unused __attribute__((unused)),
 		      int flag,
 		      struct FTW *s_unused __attribute__((unused)))
 {
-	char buf[STAT_BLOCK_SIZE];
-	if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
-	{
-		fprintf(stderr, "Read error on pipe.\n");
-		pipe_fds[0] = -1;
-	}
-	if (flag == FTW_DNR) {
-		fprintf(stderr, "%s:  unable to read directory %s\n",
-			progname, file);
-		return 0;
-	}
-	errors=errors+restore(file);
-	return 0;
+  char buf[STAT_BLOCK_SIZE];
+
+  if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
+  {
+    fprintf(stderr, "Read error on pipe.\n");
+    pipe_fds[0] = -1;
+  }
+  if (flag == FTW_DNR) {
+    fprintf(stderr, "%s:  unable to read directory %s\n",
+    progname, file);
+    return 0;
+  }
+  if(num_workers)
+  {
+    int i, rc;
+    get_worker_data(0);
+    for(i = 0; i < num_workers; i++)
+    {
+      /* Have two entries in the queue for each worker so that if the master
+         starts doing some regex work the workers don't starve */
+      if(worker_use_count[i] < 2)
+      {
+        int offset = 0, len = strlen(file) + 1;
+
+        worker_use_count[i]++;
+        /* Could hurt performance if FILE_MAX*2 > the max pipe buffer */
+        while(1)
+        {
+          rc = write(worker_send_fds[i], &file[offset], len - offset);
+          if(rc == -1)
+          {
+            fprintf(stderr, "Can't write to worker %d\n", i);
+            exit(1);
+          }
+          offset += rc;
+          if(offset == len)
+            return 0;
+        }
+      }
+    }
+  }
+  errors=errors+restore(file);
+  return 0;
 }
+
+int num_cpus()
+{
+  FILE *fp = fopen("/proc/cpuinfo", "r");
+  char buf[1024];
+  int num = 0;
+
+  if(!fp)
+    return 1;
+  while(fgets(buf, sizeof(buf), fp))
+  {
+    if(!strncmp(buf, "processor", 9))
+      num++;
+  }
+  fclose(fp);
+  if(num < 1)
+    return 1;
+  return num;
+}
+
+void worker_restore(int id, const char * const file, int fd_out)
+{
+  char res = WORKER_SUCCESS;
+  if(restore(file))
+  {
+    res = WORKER_FAILURE;
+  }
+  if(write(fd_out, &res, 1) != 1)
+  {
+    fprintf(stderr, "Worker %d can't write to result pipe, aborting.\n", id);
+    exit(1);
+  }
+}
+
+void worker(int id, int fd_in, int fd_out)
+{
+  char buf[PATH_MAX+1];
+  int len = 0;
+
+  while(1)
+  {
+    while(len == 0 || buf[len - 1] != '\0')
+    {
+      int string_len;
+      int rc = read(fd_in, buf + len, PATH_MAX - len);
+      if(rc == -1)
+      {
+        fprintf(stderr, "Worker %d exiting on read error.\n", id);
+        exit(1);
+      }
+      if(rc == 0)
+        exit(0); /* parent has exited */
+      len += rc;
+      buf[len] = '\0';
+      string_len = strlen(buf);
+      while(string_len != len)
+      {
+        worker_restore(id, buf, fd_out);
+        len = len - string_len - 1;
+        memmove(buf, buf + string_len + 1, len + 1);
+        string_len = strlen(buf);
+      }
+      if(len >= PATH_MAX)
+      {
+        fprintf(stderr, "Worker %d received excess data from parent.\n", id);
+        exit(1);
+      }
+    }
+    worker_restore(id, buf, fd_out);
+    len = 0;
+  }
+  if(len != 0)
+  {
+    fprintf(stderr, "Worker %d can't read from pipe, aborting.\n", id);
+    exit(1);
+  }
+  exit(0);
+}
+
+void start_workers()
+{
+  int send_fds[2], recv_fds[2], i;
+
+  num_workers = num_cpus() - 1;
+  if(!num_workers)
+    return;
+  worker_send_fds = malloc(sizeof(int) * num_workers);
+  worker_recv_fds = malloc(sizeof(int) * num_workers);
+  worker_use_count = malloc(sizeof(int) * num_workers);
+  if(!worker_send_fds || !worker_recv_fds || !worker_use_count)
+  {
+    fprintf(stderr, "Malloc failure.\n");
+    exit(1);
+  }
+
+  matchpathcon_init(NULL);
+
+  for(i = 0; i < num_workers; i++)
+  {
+    int rc;
+
+    if(pipe(send_fds) || pipe(recv_fds))
+    {
+      fprintf(stderr, "Can't create pipe.\n");
+      exit(1);
+    }
+    worker_send_fds[i] = send_fds[1];
+    worker_recv_fds[i] = recv_fds[0];
+    worker_use_count[i] = 0;
+    max_worker_handle = MAX(max_worker_handle, MAX(worker_send_fds[i], worker_recv_fds[i]));
+    rc = fork();
+    if(rc == -1)
+    {
+      fprintf(stderr, "fork error\n");
+      exit(1);
+    }
+    if(!rc)
+    {
+      int j;
+      for(j = i - 1; j >= 0; j--)
+      {
+        close(worker_send_fds[j]);
+        close(worker_recv_fds[j]);
+      }
+      close(send_fds[1]);
+      close(recv_fds[0]);
+      worker(i, send_fds[0], recv_fds[1]);
+      exit(2); /* should never reach this code */
+    }
+    close(send_fds[0]);
+    close(recv_fds[1]);
+  }
+  max_worker_handle++;
+}
+
 void process(char *buf) {
       int rc;
+
       if (recurse) {
 	if(pipe(pipe_fds) == -1)
-	  rc = -1;
-	else
-	  rc = fork();
+        {
+          fprintf(stderr, "Can't create pipe.\n");
+          exit(1);
+        }
+	rc = fork();
 	if(rc == 0)
 	{
 	  close(pipe_fds[0]);
@@ -276,10 +498,25 @@
 	if(rc > 0)
 	  close(pipe_fds[1]);
 	if(rc == -1 || rc > 0) {
+          start_workers();
 	  if (nftw(buf, apply_spec, 1024, FTW_PHYS)) {
 	    fprintf(stderr, "%s:  error while labeling files under %s\n",
 		  progname, buf);
 	    errors++;
+          }
+          if(rc > 0)
+            wait(&rc);
+          if(num_workers)
+          {
+            int i;
+            for(i = 0; i < num_workers; i++)
+            {
+              while(worker_use_count[i])
+                get_worker_data(1);
+              close(worker_send_fds[i]);
+              close(worker_recv_fds[i]);
+              wait(&rc);
+            }
 	  }
 	}
       }
diff -ru policycoreutils-1.30.10.orig/setfiles/setfiles.c policycoreutils-1.30.10/setfiles/setfiles.c
--- policycoreutils-1.30.10.orig/setfiles/setfiles.c	2006-05-23 20:20:03.000000000 +1000
+++ policycoreutils-1.30.10/setfiles/setfiles.c	2006-07-30 23:29:07.000000000 +1000
@@ -75,6 +75,7 @@
 #include <selinux/selinux.h>
 #include <syslog.h> 
 #include <libgen.h>
+#include <sys/wait.h>
 
 static int add_assoc = 1;
 static FILE *outfile=NULL;
@@ -82,6 +83,15 @@
 #define STAT_BLOCK_SIZE 1
 static int pipe_fds[2] = { -1, -1 };
 
+#define WORKER_SUCCESS 'g'
+#define WORKER_FAILURE 'b'
+int *worker_send_fds = NULL;
+int *worker_recv_fds = NULL;
+int num_workers = 0;
+int max_worker_handle = 0;
+int *worker_use_count = NULL;
+#define MAX(XA, XB) ((XA) > (XB)) ? (XA) : (XB)
+
 #define MAX_EXCLUDES 100
 static int excludeCtr=0;
 struct edir {
@@ -232,27 +242,57 @@
 	return  (strcmp(rest_a, rest_b) == 0);
 }
 
-/*
- * Apply the last matching specification to a file.
- * This function is called by nftw on each file during
- * the directory traversal.
- */
-static int apply_spec(const char *file,
-		      const struct stat *sb_unused __attribute__((unused)),
-		      int flag,
-		      struct FTW *s_unused __attribute__((unused)))
+void get_worker_data(int finishing)
+{
+  int i, count;
+  fd_set read_set;
+  struct timeval tv;
+  tv.tv_sec = finishing ? 1 : 0;
+  tv.tv_usec = 0;
+
+  FD_ZERO(&read_set);
+  for(i = 0; i < num_workers; i++)
+    FD_SET(worker_recv_fds[i], &read_set);
+
+  count = select(max_worker_handle, &read_set, NULL, NULL, &tv);
+  if(count == 0)
+    return;
+  if(count == -1)
+  {
+    perror("Worker select error");
+    exit(1);
+  }
+  for(i = 0; i < num_workers; i++)
+  {
+    if(FD_ISSET(worker_recv_fds[i], &read_set))
+    {
+      char c;
+      int rc = read(worker_recv_fds[i], &c, 1);
+      if(rc != 1)
+      {
+        fprintf(stderr, "Error reading result from worker %d, aborting.\n", i);
+        exit(1);
+      }
+/*      if(c != WORKER_SUCCESS)
+        errors++;*/
+      worker_use_count[i]--;
+      count--;
+    }
+  }
+  if(count != 0)
+  {
+    fprintf(stderr, "Count should be 0!\n");
+    exit(1);
+  }
+}
+
+static int set_context(const char *file)
 {
 	const char *my_file;
 	struct stat my_sb;
 	int i, j, ret;
 	char *context, *newcon; 
 	int user_only_changed=0;
-	char buf[STAT_BLOCK_SIZE];
-	if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
-	{
-		fprintf(stderr, "Read error on pipe.\n");
-		pipe_fds[0] = -1;
-	}
 
 	/* Skip the extra slash at the beginning, if present. */
 	if (file[0] == '/' && file[1] == '/')
@@ -260,12 +300,6 @@
 	else
 		my_file = file;
 
-	if (flag == FTW_DNR) {
-		fprintf(stderr, "%s:  unable to read directory %s\n",
-			progname, my_file);
-		return 0;
-	}
-
 	i = match(my_file, &my_sb, &newcon);
 	if (i < 0)
 		/* No matching specification. */
@@ -382,6 +416,198 @@
 	return -1;
 }
 
+/*
+ * Apply the last matching specification to a file.
+ * This function is called by nftw on each file during
+ * the directory traversal.
+ */
+static int apply_spec(const char *file,
+		      const struct stat *sb_unused __attribute__((unused)),
+		      int flag,
+		      struct FTW *s_unused __attribute__((unused)))
+{
+  char buf[STAT_BLOCK_SIZE];
+  if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
+  {
+    fprintf(stderr, "Read error on pipe.\n");
+    pipe_fds[0] = -1;
+  }
+
+  if (flag == FTW_DNR)
+  {
+    fprintf(stderr, "%s:  unable to read directory %s\n",
+    progname, file);
+    return 0;
+  }
+
+  if(num_workers)
+  {
+    int i, rc;
+    get_worker_data(0);
+    for(i = 0; i < num_workers; i++)
+    {
+      /* Have two entries in the queue for each worker so that if the master
+         starts doing some regex work the workers don't starve */
+      if(worker_use_count[i] < 2)
+      {
+        int offset = 0, len = strlen(file) + 1;
+
+        worker_use_count[i]++;
+        /* Could hurt performance if FILE_MAX*2 > the max pipe buffer */
+        while(1)
+        {
+          rc = write(worker_send_fds[i], &file[offset], len - offset);
+          if(rc == -1)
+          {
+            fprintf(stderr, "Can't write to worker %d\n", i);
+            exit(1);
+          }
+          offset += rc;
+          if(offset == len)
+            return 0;
+        }
+      }
+    }
+  }
+  return set_context(file);
+}
+
+int num_cpus()
+{
+  FILE *fp = fopen("/proc/cpuinfo", "r");
+  char buf[1024];
+  int num = 0;
+
+  if(!fp)
+    return 1;
+  while(fgets(buf, sizeof(buf), fp))
+  {
+    if(!strncmp(buf, "processor", 9))
+      num++;
+  }
+  fclose(fp);
+  if(num < 1)
+    return 1;
+  return num;
+}
+
+void worker_restore(int id, const char * const file, int fd_out)
+{
+  char res = WORKER_SUCCESS;
+  if(set_context(file))
+  {
+    res = WORKER_FAILURE;
+  }
+  if(write(fd_out, &res, 1) != 1)
+  {
+    fprintf(stderr, "Worker %d can't write to result pipe, aborting.\n", id);
+    exit(1);
+  }
+}
+
+void worker(int id, int fd_in, int fd_out)
+{
+  char buf[PATH_MAX+1];
+  int len = 0;
+
+  while(1)
+  {
+    while(len == 0 || buf[len - 1] != '\0')
+    {
+      int string_len;
+      int rc = read(fd_in, buf + len, PATH_MAX - len);
+      if(rc == -1)
+      {
+        fprintf(stderr, "Worker %d exiting on read error.\n", id);
+        exit(1);
+      }
+      if(rc == 0)
+        exit(0); /* parent has exited */
+      len += rc;
+      buf[len] = '\0';
+      string_len = strlen(buf);
+      while(string_len != len)
+      {
+        worker_restore(id, buf, fd_out);
+        len = len - string_len - 1;
+        memmove(buf, buf + string_len + 1, len + 1);
+        string_len = strlen(buf);
+      }
+      if(len >= PATH_MAX)
+      {
+        fprintf(stderr, "Worker %d received excess data from parent.\n", id);
+        exit(1);
+      }
+    }
+    worker_restore(id, buf, fd_out);
+    len = 0;
+  }
+  if(len != 0)
+  {
+    fprintf(stderr, "Worker %d can't read from pipe, aborting.\n", id);
+    exit(1);
+  }
+  exit(0);
+}
+
+void start_workers()
+{
+  int send_fds[2], recv_fds[2], i;
+
+  if(add_assoc)
+    return;
+  num_workers = num_cpus() - 1;
+  if(!num_workers)
+    return;
+  worker_send_fds = malloc(sizeof(int) * num_workers);
+  worker_recv_fds = malloc(sizeof(int) * num_workers);
+  worker_use_count = malloc(sizeof(int) * num_workers);
+  if(!worker_send_fds || !worker_recv_fds || !worker_use_count)
+  {
+    fprintf(stderr, "Malloc failure.\n");
+    exit(1);
+  }
+
+  matchpathcon_init(NULL);
+
+  for(i = 0; i < num_workers; i++)
+  {
+    int rc;
+
+    if(pipe(send_fds) || pipe(recv_fds))
+    {
+      fprintf(stderr, "Can't create pipe.\n");
+      exit(1);
+    }
+    worker_send_fds[i] = send_fds[1];
+    worker_recv_fds[i] = recv_fds[0];
+    worker_use_count[i] = 0;
+    max_worker_handle = MAX(max_worker_handle, MAX(worker_send_fds[i], worker_recv_fds[i]));
+    rc = fork();
+    if(rc == -1)
+    {
+      fprintf(stderr, "fork error\n");
+      exit(1);
+    }
+    if(!rc)
+    {
+      int j;
+      for(j = i - 1; j >= 0; j--)
+      {
+        close(worker_send_fds[j]);
+        close(worker_recv_fds[j]);
+      }
+      close(send_fds[1]);
+      close(recv_fds[0]);
+      worker(i, send_fds[0], recv_fds[1]);
+      exit(2); /* should never reach this code */
+    }
+    close(send_fds[0]);
+    close(recv_fds[1]);
+  }
+  max_worker_handle++;
+}
+
 void set_rootpath(const char *arg)
 {
 	int len;
@@ -454,7 +680,7 @@
 	set_matchpathcon_flags(MATCHPATHCON_VALIDATE);
 
 	/* Process any options. */
-	while ((opt = getopt(argc, argv, "Fc:dlnqrsvWe:o:")) > 0) {
+	while ((opt = getopt(argc, argv, "aFc:dlnqrsvWe:o:")) > 0) {
 		switch (opt) {
 		case 'c':
 		{
@@ -529,6 +755,7 @@
 			break;
 		case 's':
 			use_stdin = 1;
+		case 'a':
 			add_assoc = 0;
 			break;
 		case 'v':
@@ -641,6 +868,7 @@
 			close(pipe_fds[1]);
 		if(rc == -1 || rc > 0) {
 
+			start_workers();
 		/* Walk the file tree, calling apply_spec on each file. */
 			if (nftw
 			    (argv[optind], apply_spec, 1024,
@@ -650,6 +878,20 @@
 				argv[0], argv[optind]);
 				exit(1);
 			}
+			if(rc > 0)
+				wait(&rc);
+			if(num_workers)
+			{
+				int i;
+				for(i = 0; i < num_workers; i++)
+				{
+					while(worker_use_count[i])
+						get_worker_data(1);
+					close(worker_send_fds[i]);
+					close(worker_recv_fds[i]);
+					wait(&rc);
+				}
+			}
 		}
 
 		/*

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: SMP enabled restorecon
  2006-07-30 14:12   ` Russell Coker
@ 2006-08-02 19:52     ` Daniel J Walsh
  2006-08-02 20:25       ` Klaus Weidner
  0 siblings, 1 reply; 7+ messages in thread
From: Daniel J Walsh @ 2006-08-02 19:52 UTC (permalink / raw)
  To: russell; +Cc: SE-Linux

Russell Coker wrote:
> On Sunday 30 July 2006 23:13, Russell Coker <russell@coker.com.au> wrote:
>   
>> On Sunday 30 July 2006 01:17, Russell Coker <russell@coker.com.au> wrote:
>>     
>>> Below are the results of running the FC5+updates version of restorecon vs
>>> my patched version with SMP support on a PentiumD-2.8GHz model 920.  The
>>> elapsed time is cut by 40%!  The user time reported appears to be a bug
>>> in the time command, when I observe the run via the "top" command I see
>>> two restorecon processes each taking about 26 seconds of CPU time before
>>> they complete.
>>>       
>> I've attached a similar patch for setfiles, this patch adds a "-a" option
>> to setfiles to skip the association check because it's really difficult to
>> preserve that check while supporting SMP and giving good performance, and
>> also because checking for associations isn't as useful when the results go
>> to /dev/null anyway.
>>     
>
> Sorry for replying to my own mail so much, I've been involved in discussion on 
> IRC and other lists.
>
> I've attached a new patch which calls wait() for each child process.  This 
> causes CPU time use to be correctly billed to parent processes, the results 
> are as follows:
>
> Modified setfiles:
> real    1m22.689s
> user    1m13.181s
> sys     0m12.641s
>
> Orig setfiles:
> real    1m23.697s
> user    0m59.916s
> sys     0m4.892s
>
> Modified restorecon on /usr/share:
> real    0m31.142s
> user    0m52.671s
> sys     0m7.728s
>
> Orig restorecon:
> real    0m51.429s
> user    0m45.467s
> sys     0m5.568s
>
> It seems that in both cases the amount of CPU time used is significantly 
> increased, and that the variance between test runs of setfiles is great 
> enough that there can be little performance difference between my version and 
> the stock one.
>
> I think that I need to find a more CPU efficient method of IPC for the file 
> names.  One possibility might be to use shared memory regions for the data.  
> Another one might be to make the programs threaded.
>
> A final possibility is that the current design of setfiles and restorecon may 
> be a dead-end as far as SMP is concerned.  Having one process to stat the 
> files and another to check the contexts when the cache is hot is efficient 
> for UP systems.  For SMP systems it might be better to have one process find 
> the files and then pass the names to worker processes that do the regex 
> stuff.  Currently processing a file involves two processes calling nftw() 
> (doing readdir() and stat()), the seeker writes a byte to a pipe and then the 
> master will either pipe the file name to a child or process it directly 
> before reading the byte from the seek process.  As I have the worker 
> acknowledging each file the average number of pipe writes to process a file 
> on a 2P system is two.
>
> If the parent process was to be the only process calling nftw() and it then 
> passed the file names via shared memory with specified minimum and maximum 
> sizes for the shared memory we could probably get an average of considerably 
> less than one IPC operation per file that is processes.
>
>   
> ------------------------------------------------------------------------
>
> diff -ru policycoreutils-1.30.10.orig/restorecon/restorecon.c policycoreutils-1.30.10/restorecon/restorecon.c
> --- policycoreutils-1.30.10.orig/restorecon/restorecon.c	2006-05-23 20:20:02.000000000 +1000
> +++ policycoreutils-1.30.10/restorecon/restorecon.c	2006-07-30 23:26:21.000000000 +1000
> @@ -34,6 +34,7 @@
>  #include <selinux/selinux.h>
>  #include <getopt.h>
>  #include <sys/types.h>
> +#include <sys/wait.h>
>  #include <sys/stat.h>
>  #include <fcntl.h>
>  #include <stdio.h>
> @@ -50,6 +51,15 @@
>  #define STAT_BLOCK_SIZE 1
>  static int pipe_fds[2] = { -1, -1 };
>  
> +#define WORKER_SUCCESS 'g'
> +#define WORKER_FAILURE 'b'
> +int *worker_send_fds = NULL;
> +int *worker_recv_fds = NULL;
> +int num_workers = 0;
> +int max_worker_handle = 0;
> +int *worker_use_count = NULL;
> +#define MAX(XA, XB) ((XA) > (XB)) ? (XA) : (XB)
> +
>  #define MAX_EXCLUDES 100
>  static int excludeCtr=0;
>  struct edir {
> @@ -241,32 +251,244 @@
>    return 0;
>  }
>  
> +void get_worker_data(int finishing)
> +{
> +  int i, count;
> +  fd_set read_set;
> +  struct timeval tv;
> +  tv.tv_sec = finishing ? 1 : 0;
> +  tv.tv_usec = 0;
> +
> +  FD_ZERO(&read_set);
> +  for(i = 0; i < num_workers; i++)
> +    FD_SET(worker_recv_fds[i], &read_set);
> +
> +  count = select(max_worker_handle, &read_set, NULL, NULL, &tv);
> +  if(count == 0)
> +    return;
> +  if(count == -1)
> +  {
> +    perror("Worker select error");
> +    exit(1);
> +  }
> +  for(i = 0; i < num_workers; i++)
> +  {
> +    if(FD_ISSET(worker_recv_fds[i], &read_set))
> +    {
> +      char c;
> +      int rc = read(worker_recv_fds[i], &c, 1);
> +      if(rc != 1)
> +      {
> +        fprintf(stderr, "Error reading result from worker %d, aborting.\n", i);
> +        exit(1);
> +      }
> +      if(c != WORKER_SUCCESS)
> +        errors++;
> +      worker_use_count[i]--;
> +      count--;
> +    }
> +  }
> +  if(count != 0)
> +  {
> +    fprintf(stderr, "Count should be 0!\n");
> +    exit(1);
> +  }
> +}
> +
>  static int apply_spec(const char *file,
>  		      const struct stat *sb_unused __attribute__((unused)),
>  		      int flag,
>  		      struct FTW *s_unused __attribute__((unused)))
>  {
> -	char buf[STAT_BLOCK_SIZE];
> -	if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
> -	{
> -		fprintf(stderr, "Read error on pipe.\n");
> -		pipe_fds[0] = -1;
> -	}
> -	if (flag == FTW_DNR) {
> -		fprintf(stderr, "%s:  unable to read directory %s\n",
> -			progname, file);
> -		return 0;
> -	}
> -	errors=errors+restore(file);
> -	return 0;
> +  char buf[STAT_BLOCK_SIZE];
> +
> +  if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
> +  {
> +    fprintf(stderr, "Read error on pipe.\n");
> +    pipe_fds[0] = -1;
> +  }
> +  if (flag == FTW_DNR) {
> +    fprintf(stderr, "%s:  unable to read directory %s\n",
> +    progname, file);
> +    return 0;
> +  }
> +  if(num_workers)
> +  {
> +    int i, rc;
> +    get_worker_data(0);
> +    for(i = 0; i < num_workers; i++)
> +    {
> +      /* Have two entries in the queue for each worker so that if the master
> +         starts doing some regex work the workers don't starve */
> +      if(worker_use_count[i] < 2)
> +      {
> +        int offset = 0, len = strlen(file) + 1;
> +
> +        worker_use_count[i]++;
> +        /* Could hurt performance if FILE_MAX*2 > the max pipe buffer */
> +        while(1)
> +        {
> +          rc = write(worker_send_fds[i], &file[offset], len - offset);
> +          if(rc == -1)
> +          {
> +            fprintf(stderr, "Can't write to worker %d\n", i);
> +            exit(1);
> +          }
> +          offset += rc;
> +          if(offset == len)
> +            return 0;
> +        }
> +      }
> +    }
> +  }
> +  errors=errors+restore(file);
> +  return 0;
>  }
> +
> +int num_cpus()
> +{
> +  FILE *fp = fopen("/proc/cpuinfo", "r");
> +  char buf[1024];
> +  int num = 0;
> +
> +  if(!fp)
> +    return 1;
> +  while(fgets(buf, sizeof(buf), fp))
> +  {
> +    if(!strncmp(buf, "processor", 9))
> +      num++;
> +  }
> +  fclose(fp);
> +  if(num < 1)
> +    return 1;
> +  return num;
> +}
> +
> +void worker_restore(int id, const char * const file, int fd_out)
> +{
> +  char res = WORKER_SUCCESS;
> +  if(restore(file))
> +  {
> +    res = WORKER_FAILURE;
> +  }
> +  if(write(fd_out, &res, 1) != 1)
> +  {
> +    fprintf(stderr, "Worker %d can't write to result pipe, aborting.\n", id);
> +    exit(1);
> +  }
> +}
> +
> +void worker(int id, int fd_in, int fd_out)
> +{
> +  char buf[PATH_MAX+1];
> +  int len = 0;
> +
> +  while(1)
> +  {
> +    while(len == 0 || buf[len - 1] != '\0')
> +    {
> +      int string_len;
> +      int rc = read(fd_in, buf + len, PATH_MAX - len);
> +      if(rc == -1)
> +      {
> +        fprintf(stderr, "Worker %d exiting on read error.\n", id);
> +        exit(1);
> +      }
> +      if(rc == 0)
> +        exit(0); /* parent has exited */
> +      len += rc;
> +      buf[len] = '\0';
> +      string_len = strlen(buf);
> +      while(string_len != len)
> +      {
> +        worker_restore(id, buf, fd_out);
> +        len = len - string_len - 1;
> +        memmove(buf, buf + string_len + 1, len + 1);
> +        string_len = strlen(buf);
> +      }
> +      if(len >= PATH_MAX)
> +      {
> +        fprintf(stderr, "Worker %d received excess data from parent.\n", id);
> +        exit(1);
> +      }
> +    }
> +    worker_restore(id, buf, fd_out);
> +    len = 0;
> +  }
> +  if(len != 0)
> +  {
> +    fprintf(stderr, "Worker %d can't read from pipe, aborting.\n", id);
> +    exit(1);
> +  }
> +  exit(0);
> +}
> +
> +void start_workers()
> +{
> +  int send_fds[2], recv_fds[2], i;
> +
> +  num_workers = num_cpus() - 1;
> +  if(!num_workers)
> +    return;
> +  worker_send_fds = malloc(sizeof(int) * num_workers);
> +  worker_recv_fds = malloc(sizeof(int) * num_workers);
> +  worker_use_count = malloc(sizeof(int) * num_workers);
> +  if(!worker_send_fds || !worker_recv_fds || !worker_use_count)
> +  {
> +    fprintf(stderr, "Malloc failure.\n");
> +    exit(1);
> +  }
> +
> +  matchpathcon_init(NULL);
> +
> +  for(i = 0; i < num_workers; i++)
> +  {
> +    int rc;
> +
> +    if(pipe(send_fds) || pipe(recv_fds))
> +    {
> +      fprintf(stderr, "Can't create pipe.\n");
> +      exit(1);
> +    }
> +    worker_send_fds[i] = send_fds[1];
> +    worker_recv_fds[i] = recv_fds[0];
> +    worker_use_count[i] = 0;
> +    max_worker_handle = MAX(max_worker_handle, MAX(worker_send_fds[i], worker_recv_fds[i]));
> +    rc = fork();
> +    if(rc == -1)
> +    {
> +      fprintf(stderr, "fork error\n");
> +      exit(1);
> +    }
> +    if(!rc)
> +    {
> +      int j;
> +      for(j = i - 1; j >= 0; j--)
> +      {
> +        close(worker_send_fds[j]);
> +        close(worker_recv_fds[j]);
> +      }
> +      close(send_fds[1]);
> +      close(recv_fds[0]);
> +      worker(i, send_fds[0], recv_fds[1]);
> +      exit(2); /* should never reach this code */
> +    }
> +    close(send_fds[0]);
> +    close(recv_fds[1]);
> +  }
> +  max_worker_handle++;
> +}
> +
>  void process(char *buf) {
>        int rc;
> +
>        if (recurse) {
>  	if(pipe(pipe_fds) == -1)
> -	  rc = -1;
> -	else
> -	  rc = fork();
> +        {
> +          fprintf(stderr, "Can't create pipe.\n");
> +          exit(1);
> +        }
> +	rc = fork();
>  	if(rc == 0)
>  	{
>  	  close(pipe_fds[0]);
> @@ -276,10 +498,25 @@
>  	if(rc > 0)
>  	  close(pipe_fds[1]);
>  	if(rc == -1 || rc > 0) {
> +          start_workers();
>  	  if (nftw(buf, apply_spec, 1024, FTW_PHYS)) {
>  	    fprintf(stderr, "%s:  error while labeling files under %s\n",
>  		  progname, buf);
>  	    errors++;
> +          }
> +          if(rc > 0)
> +            wait(&rc);
> +          if(num_workers)
> +          {
> +            int i;
> +            for(i = 0; i < num_workers; i++)
> +            {
> +              while(worker_use_count[i])
> +                get_worker_data(1);
> +              close(worker_send_fds[i]);
> +              close(worker_recv_fds[i]);
> +              wait(&rc);
> +            }
>  	  }
>  	}
>        }
> diff -ru policycoreutils-1.30.10.orig/setfiles/setfiles.c policycoreutils-1.30.10/setfiles/setfiles.c
> --- policycoreutils-1.30.10.orig/setfiles/setfiles.c	2006-05-23 20:20:03.000000000 +1000
> +++ policycoreutils-1.30.10/setfiles/setfiles.c	2006-07-30 23:29:07.000000000 +1000
> @@ -75,6 +75,7 @@
>  #include <selinux/selinux.h>
>  #include <syslog.h> 
>  #include <libgen.h>
> +#include <sys/wait.h>
>  
>  static int add_assoc = 1;
>  static FILE *outfile=NULL;
> @@ -82,6 +83,15 @@
>  #define STAT_BLOCK_SIZE 1
>  static int pipe_fds[2] = { -1, -1 };
>  
> +#define WORKER_SUCCESS 'g'
> +#define WORKER_FAILURE 'b'
> +int *worker_send_fds = NULL;
> +int *worker_recv_fds = NULL;
> +int num_workers = 0;
> +int max_worker_handle = 0;
> +int *worker_use_count = NULL;
> +#define MAX(XA, XB) ((XA) > (XB)) ? (XA) : (XB)
> +
>  #define MAX_EXCLUDES 100
>  static int excludeCtr=0;
>  struct edir {
> @@ -232,27 +242,57 @@
>  	return  (strcmp(rest_a, rest_b) == 0);
>  }
>  
> -/*
> - * Apply the last matching specification to a file.
> - * This function is called by nftw on each file during
> - * the directory traversal.
> - */
> -static int apply_spec(const char *file,
> -		      const struct stat *sb_unused __attribute__((unused)),
> -		      int flag,
> -		      struct FTW *s_unused __attribute__((unused)))
> +void get_worker_data(int finishing)
> +{
> +  int i, count;
> +  fd_set read_set;
> +  struct timeval tv;
> +  tv.tv_sec = finishing ? 1 : 0;
> +  tv.tv_usec = 0;
> +
> +  FD_ZERO(&read_set);
> +  for(i = 0; i < num_workers; i++)
> +    FD_SET(worker_recv_fds[i], &read_set);
> +
> +  count = select(max_worker_handle, &read_set, NULL, NULL, &tv);
> +  if(count == 0)
> +    return;
> +  if(count == -1)
> +  {
> +    perror("Worker select error");
> +    exit(1);
> +  }
> +  for(i = 0; i < num_workers; i++)
> +  {
> +    if(FD_ISSET(worker_recv_fds[i], &read_set))
> +    {
> +      char c;
> +      int rc = read(worker_recv_fds[i], &c, 1);
> +      if(rc != 1)
> +      {
> +        fprintf(stderr, "Error reading result from worker %d, aborting.\n", i);
> +        exit(1);
> +      }
> +/*      if(c != WORKER_SUCCESS)
> +        errors++;*/
> +      worker_use_count[i]--;
> +      count--;
> +    }
> +  }
> +  if(count != 0)
> +  {
> +    fprintf(stderr, "Count should be 0!\n");
> +    exit(1);
> +  }
> +}
> +
> +static int set_context(const char *file)
>  {
>  	const char *my_file;
>  	struct stat my_sb;
>  	int i, j, ret;
>  	char *context, *newcon; 
>  	int user_only_changed=0;
> -	char buf[STAT_BLOCK_SIZE];
> -	if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
> -	{
> -		fprintf(stderr, "Read error on pipe.\n");
> -		pipe_fds[0] = -1;
> -	}
>  
>  	/* Skip the extra slash at the beginning, if present. */
>  	if (file[0] == '/' && file[1] == '/')
> @@ -260,12 +300,6 @@
>  	else
>  		my_file = file;
>  
> -	if (flag == FTW_DNR) {
> -		fprintf(stderr, "%s:  unable to read directory %s\n",
> -			progname, my_file);
> -		return 0;
> -	}
> -
>  	i = match(my_file, &my_sb, &newcon);
>  	if (i < 0)
>  		/* No matching specification. */
> @@ -382,6 +416,198 @@
>  	return -1;
>  }
>  
> +/*
> + * Apply the last matching specification to a file.
> + * This function is called by nftw on each file during
> + * the directory traversal.
> + */
> +static int apply_spec(const char *file,
> +		      const struct stat *sb_unused __attribute__((unused)),
> +		      int flag,
> +		      struct FTW *s_unused __attribute__((unused)))
> +{
> +  char buf[STAT_BLOCK_SIZE];
> +  if(pipe_fds[0] != -1 && read(pipe_fds[0], buf, STAT_BLOCK_SIZE) != STAT_BLOCK_SIZE)
> +  {
> +    fprintf(stderr, "Read error on pipe.\n");
> +    pipe_fds[0] = -1;
> +  }
> +
> +  if (flag == FTW_DNR)
> +  {
> +    fprintf(stderr, "%s:  unable to read directory %s\n",
> +    progname, file);
> +    return 0;
> +  }
> +
> +  if(num_workers)
> +  {
> +    int i, rc;
> +    get_worker_data(0);
> +    for(i = 0; i < num_workers; i++)
> +    {
> +      /* Have two entries in the queue for each worker so that if the master
> +         starts doing some regex work the workers don't starve */
> +      if(worker_use_count[i] < 2)
> +      {
> +        int offset = 0, len = strlen(file) + 1;
> +
> +        worker_use_count[i]++;
> +        /* Could hurt performance if FILE_MAX*2 > the max pipe buffer */
> +        while(1)
> +        {
> +          rc = write(worker_send_fds[i], &file[offset], len - offset);
> +          if(rc == -1)
> +          {
> +            fprintf(stderr, "Can't write to worker %d\n", i);
> +            exit(1);
> +          }
> +          offset += rc;
> +          if(offset == len)
> +            return 0;
> +        }
> +      }
> +    }
> +  }
> +  return set_context(file);
> +}
> +
> +int num_cpus()
> +{
> +  FILE *fp = fopen("/proc/cpuinfo", "r");
> +  char buf[1024];
> +  int num = 0;
> +
> +  if(!fp)
> +    return 1;
> +  while(fgets(buf, sizeof(buf), fp))
> +  {
> +    if(!strncmp(buf, "processor", 9))
> +      num++;
> +  }
> +  fclose(fp);
> +  if(num < 1)
> +    return 1;
> +  return num;
> +}
> +
> +void worker_restore(int id, const char * const file, int fd_out)
> +{
> +  char res = WORKER_SUCCESS;
> +  if(set_context(file))
> +  {
> +    res = WORKER_FAILURE;
> +  }
> +  if(write(fd_out, &res, 1) != 1)
> +  {
> +    fprintf(stderr, "Worker %d can't write to result pipe, aborting.\n", id);
> +    exit(1);
> +  }
> +}
> +
> +void worker(int id, int fd_in, int fd_out)
> +{
> +  char buf[PATH_MAX+1];
> +  int len = 0;
> +
> +  while(1)
> +  {
> +    while(len == 0 || buf[len - 1] != '\0')
> +    {
> +      int string_len;
> +      int rc = read(fd_in, buf + len, PATH_MAX - len);
> +      if(rc == -1)
> +      {
> +        fprintf(stderr, "Worker %d exiting on read error.\n", id);
> +        exit(1);
> +      }
> +      if(rc == 0)
> +        exit(0); /* parent has exited */
> +      len += rc;
> +      buf[len] = '\0';
> +      string_len = strlen(buf);
> +      while(string_len != len)
> +      {
> +        worker_restore(id, buf, fd_out);
> +        len = len - string_len - 1;
> +        memmove(buf, buf + string_len + 1, len + 1);
> +        string_len = strlen(buf);
> +      }
> +      if(len >= PATH_MAX)
> +      {
> +        fprintf(stderr, "Worker %d received excess data from parent.\n", id);
> +        exit(1);
> +      }
> +    }
> +    worker_restore(id, buf, fd_out);
> +    len = 0;
> +  }
> +  if(len != 0)
> +  {
> +    fprintf(stderr, "Worker %d can't read from pipe, aborting.\n", id);
> +    exit(1);
> +  }
> +  exit(0);
> +}
> +
> +void start_workers()
> +{
> +  int send_fds[2], recv_fds[2], i;
> +
> +  if(add_assoc)
> +    return;
> +  num_workers = num_cpus() - 1;
> +  if(!num_workers)
> +    return;
> +  worker_send_fds = malloc(sizeof(int) * num_workers);
> +  worker_recv_fds = malloc(sizeof(int) * num_workers);
> +  worker_use_count = malloc(sizeof(int) * num_workers);
> +  if(!worker_send_fds || !worker_recv_fds || !worker_use_count)
> +  {
> +    fprintf(stderr, "Malloc failure.\n");
> +    exit(1);
> +  }
> +
> +  matchpathcon_init(NULL);
> +
> +  for(i = 0; i < num_workers; i++)
> +  {
> +    int rc;
> +
> +    if(pipe(send_fds) || pipe(recv_fds))
> +    {
> +      fprintf(stderr, "Can't create pipe.\n");
> +      exit(1);
> +    }
> +    worker_send_fds[i] = send_fds[1];
> +    worker_recv_fds[i] = recv_fds[0];
> +    worker_use_count[i] = 0;
> +    max_worker_handle = MAX(max_worker_handle, MAX(worker_send_fds[i], worker_recv_fds[i]));
> +    rc = fork();
> +    if(rc == -1)
> +    {
> +      fprintf(stderr, "fork error\n");
> +      exit(1);
> +    }
> +    if(!rc)
> +    {
> +      int j;
> +      for(j = i - 1; j >= 0; j--)
> +      {
> +        close(worker_send_fds[j]);
> +        close(worker_recv_fds[j]);
> +      }
> +      close(send_fds[1]);
> +      close(recv_fds[0]);
> +      worker(i, send_fds[0], recv_fds[1]);
> +      exit(2); /* should never reach this code */
> +    }
> +    close(send_fds[0]);
> +    close(recv_fds[1]);
> +  }
> +  max_worker_handle++;
> +}
> +
>  void set_rootpath(const char *arg)
>  {
>  	int len;
> @@ -454,7 +680,7 @@
>  	set_matchpathcon_flags(MATCHPATHCON_VALIDATE);
>  
>  	/* Process any options. */
> -	while ((opt = getopt(argc, argv, "Fc:dlnqrsvWe:o:")) > 0) {
> +	while ((opt = getopt(argc, argv, "aFc:dlnqrsvWe:o:")) > 0) {
>  		switch (opt) {
>  		case 'c':
>  		{
> @@ -529,6 +755,7 @@
>  			break;
>  		case 's':
>  			use_stdin = 1;
> +		case 'a':
>  			add_assoc = 0;
>  			break;
>  		case 'v':
> @@ -641,6 +868,7 @@
>  			close(pipe_fds[1]);
>  		if(rc == -1 || rc > 0) {
>  
> +			start_workers();
>  		/* Walk the file tree, calling apply_spec on each file. */
>  			if (nftw
>  			    (argv[optind], apply_spec, 1024,
> @@ -650,6 +878,20 @@
>  				argv[0], argv[optind]);
>  				exit(1);
>  			}
> +			if(rc > 0)
> +				wait(&rc);
> +			if(num_workers)
> +			{
> +				int i;
> +				for(i = 0; i < num_workers; i++)
> +				{
> +					while(worker_use_count[i])
> +						get_worker_data(1);
> +					close(worker_send_fds[i]);
> +					close(worker_recv_fds[i]);
> +					wait(&rc);
> +				}
> +			}
>  		}
>  
>  		/*
>   
You might want to look at what find is doing also, since it seems more 
efficient that restorecon

time restorecon -R /etc

real    0m5.729s
user    0m1.532s
sys     0m3.576s

time find /etc/ | restorecon -f -

real    0m5.153s
user    0m1.560s
sys     0m3.564s


--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: SMP enabled restorecon
  2006-08-02 19:52     ` Daniel J Walsh
@ 2006-08-02 20:25       ` Klaus Weidner
  2006-08-02 20:38         ` Stephen Smalley
  0 siblings, 1 reply; 7+ messages in thread
From: Klaus Weidner @ 2006-08-02 20:25 UTC (permalink / raw)
  To: Daniel J Walsh; +Cc: russell, SE-Linux

On Wed, Aug 02, 2006 at 03:52:01PM -0400, Daniel J Walsh wrote:
> You might want to look at what find is doing also, since it seems more 
> efficient that restorecon
> 
> time restorecon -R /etc
> real    0m5.729s
> 
> time find /etc/ | restorecon -f -
> real    0m5.153s

This might be the leaf optimization (see find(1), -noleaf), "find" uses
the directory hard link count to avoid stat()ing files when it knows that
they cannot be directories.

-Klaus

--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: SMP enabled restorecon
  2006-08-02 20:25       ` Klaus Weidner
@ 2006-08-02 20:38         ` Stephen Smalley
  0 siblings, 0 replies; 7+ messages in thread
From: Stephen Smalley @ 2006-08-02 20:38 UTC (permalink / raw)
  To: Klaus Weidner; +Cc: Daniel J Walsh, russell, SE-Linux

On Wed, 2006-08-02 at 15:25 -0500, Klaus Weidner wrote:
> On Wed, Aug 02, 2006 at 03:52:01PM -0400, Daniel J Walsh wrote:
> > You might want to look at what find is doing also, since it seems more 
> > efficient that restorecon
> > 
> > time restorecon -R /etc
> > real    0m5.729s
> > 
> > time find /etc/ | restorecon -f -
> > real    0m5.153s
> 
> This might be the leaf optimization (see find(1), -noleaf), "find" uses
> the directory hard link count to avoid stat()ing files when it knows that
> they cannot be directories.

Given that coreutils selinux support is being upstreamed and Jim
Meyering has rewritten chcon to use fts and openat-style functions, I
have to wonder whether we shouldn't just seek to merge
restorecon/setfiles-like functionality into chcon as an option and drop
them as separate entities.  IOW, have an option to chcon that says to
use matchpathcon to get the context instead of having the user specify
it as an argument.

-- 
Stephen Smalley
National Security Agency


--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2006-08-02 20:38 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-07-29 15:17 SMP enabled restorecon Russell Coker
2006-07-29 15:20 ` Russell Coker
2006-07-30 13:13 ` Russell Coker
2006-07-30 14:12   ` Russell Coker
2006-08-02 19:52     ` Daniel J Walsh
2006-08-02 20:25       ` Klaus Weidner
2006-08-02 20:38         ` Stephen Smalley

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.