1 /*
  2  * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package sun.nio.fs;
 27 
 28 import java.nio.file.*;
 29 import java.util.Iterator;
 30 import java.util.NoSuchElementException;
 31 import java.util.concurrent.locks.*;
 32 import java.io.IOException;
 33 import static sun.nio.fs.UnixNativeDispatcher.*;
 34 
 35 /**
 36  * Unix implementation of java.nio.file.DirectoryStream
 37  */
 38 
 39 class UnixDirectoryStream
 40     implements DirectoryStream<Path>
 41 {
 42     // path to directory when originally opened
 43     private final UnixPath dir;
 44 
 45     // directory pointer (returned by opendir)
 46     private final long dp;
 47 
 48     // filter (may be null)
 49     private final DirectoryStream.Filter<? super Path> filter;
 50 
 51     // used to coordinate closing of directory stream
 52     private final ReentrantReadWriteLock streamLock = new ReentrantReadWriteLock();
 53 
 54     // indicates if directory stream is open
 55     private volatile boolean isClosed;
 56 
 57     // directory iterator
 58     private Iterator<Path> iterator;
 59 
 60     /**
 61      * Initializes a new instance
 62      */
 63     UnixDirectoryStream(UnixPath dir, long dp, DirectoryStream.Filter<? super Path> filter) {
 64         this.dir = dir;
 65         this.dp = dp;
 66         this.filter = filter;
 67     }
 68 
 69     protected final UnixPath directory() {
 70         return dir;
 71     }
 72 
 73     protected final Lock readLock() {
 74         return streamLock.readLock();
 75     }
 76 
 77     protected final Lock writeLock() {
 78         return streamLock.writeLock();
 79     }
 80 
 81     protected final boolean isOpen() {
 82         return !isClosed;
 83     }
 84 
 85     protected final boolean closeImpl() throws IOException {
 86         if (!isClosed) {
 87             isClosed = true;
 88             try {
 89                 closedir(dp);
 90             } catch (UnixException x) {
 91                 throw new IOException(x.errorString());
 92             }
 93             return true;
 94         } else {
 95             return false;
 96         }
 97     }
 98 
 99     @Override
100     public void close()
101         throws IOException
102     {
103         writeLock().lock();
104         try {
105             closeImpl();
106         } finally {
107             writeLock().unlock();
108         }
109     }
110 
111     protected final Iterator<Path> iterator(DirectoryStream<Path> ds) {
112         if (isClosed) {
113             throw new IllegalStateException("Directory stream is closed");
114         }
115         synchronized (this) {
116             if (iterator != null)
117                 throw new IllegalStateException("Iterator already obtained");
118             iterator = new UnixDirectoryIterator();
119             return iterator;
120         }
121     }
122 
123     @Override
124     public Iterator<Path> iterator() {
125         return iterator(this);
126     }
127 
128     /**
129      * Iterator implementation
130      */
131     private class UnixDirectoryIterator implements Iterator<Path> {
132         private final ReentrantLock iteratorLock = new ReentrantLock();
133 
134         // true when at EOF
135         private boolean atEof;
136 
137         // next entry to return
138         private Path nextEntry;
139 
140         UnixDirectoryIterator() {
141             atEof = false;
142         }
143 
144         // Return true if file name is "." or ".."
145         private boolean isSelfOrParent(byte[] nameAsBytes) {
146             if (nameAsBytes[0] == '.') {
147                 if ((nameAsBytes.length == 1) ||
148                     (nameAsBytes.length == 2 && nameAsBytes[1] == '.')) {
149                     return true;
150                 }
151             }
152             return false;
153         }
154 
155         // Returns next entry (or null)
156         private Path readNextEntry() {
157             assert iteratorLock.isHeldByCurrentThread();
158 
159             for (;;) {
160                 byte[] nameAsBytes = null;
161 
162                 // prevent close while reading
163                 readLock().lock();
164                 try {
165                     if (isOpen()) {
166                         nameAsBytes = readdir(dp);
167                     }
168                 } catch (UnixException x) {
169                     IOException ioe = x.asIOException(dir);
170                     throw new DirectoryIteratorException(ioe);
171                 } finally {
172                     readLock().unlock();
173                 }
174 
175                 // EOF
176                 if (nameAsBytes == null) {
177                     atEof = true;
178                     return null;
179                 }
180 
181                 // ignore "." and ".."
182                 if (!isSelfOrParent(nameAsBytes)) {
183                     Path entry = dir.resolve(nameAsBytes);
184 
185                     // return entry if no filter or filter accepts it
186                     try {
187                         if (filter == null || filter.accept(entry))
188                             return entry;
189                     } catch (IOException ioe) {
190                         throw new DirectoryIteratorException(ioe);
191                     }
192                 }
193             }
194         }
195 
196         @Override
197         public boolean hasNext() {
198             iteratorLock.tryLock();
199             try {
200                 if (nextEntry == null && !atEof)
201                     nextEntry = readNextEntry();
202                 return nextEntry != null;
203             } finally {
204                 iteratorLock.unlock();
205             }
206         }
207 
208         @Override
209         public Path next() {
210             iteratorLock.tryLock();
211             try {
212                 Path result;
213                 if (nextEntry == null && !atEof) {
214                     result = readNextEntry();
215                 } else {
216                     result = nextEntry;
217                     nextEntry = null;
218                 }
219                 if (result == null)
220                     throw new NoSuchElementException();
221                 return result;
222             } finally {
223                 iteratorLock.unlock();
224             }
225         }
226 
227         @Override
228         public void remove() {
229             throw new UnsupportedOperationException();
230         }
231     }
232 }