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 =
 53         new ReentrantReadWriteLock(true);
 54 
 55     // indicates if directory stream is open (synchronize on closeLock)
 56     private volatile boolean isClosed;
 57 
 58     // directory iterator
 59     private Iterator<Path> iterator;
 60 
 61     /**
 62      * Initializes a new instance
 63      */
 64     UnixDirectoryStream(UnixPath dir, long dp, DirectoryStream.Filter<? super Path> filter) {
 65         this.dir = dir;
 66         this.dp = dp;
 67         this.filter = filter;
 68     }
 69 
 70     protected final UnixPath directory() {
 71         return dir;
 72     }
 73 
 74     protected final Lock readLock() {
 75         return streamLock.readLock();
 76     }
 77 
 78     protected final Lock writeLock() {
 79         return streamLock.writeLock();
 80     }
 81 
 82     protected final boolean isOpen() {
 83         return !isClosed;
 84     }
 85 
 86     protected final boolean closeImpl() throws IOException {
 87         if (!isClosed) {
 88             isClosed = true;
 89             try {
 90                 closedir(dp);
 91             } catch (UnixException x) {
 92                 throw new IOException(x.errorString());
 93             }
 94             return true;
 95         } else {
 96             return false;
 97         }
 98     }
 99 
100     @Override
101     public void close()
102         throws IOException
103     {
104         writeLock().lock();
105         try {
106             closeImpl();
107         } finally {
108             writeLock().unlock();
109         }
110     }
111 
112     protected final Iterator<Path> iterator(DirectoryStream<Path> ds) {
113         if (isClosed) {
114             throw new IllegalStateException("Directory stream is closed");
115         }
116         synchronized (this) {
117             if (iterator != null)
118                 throw new IllegalStateException("Iterator already obtained");
119             iterator = new UnixDirectoryIterator();
120             return iterator;
121         }
122     }
123 
124     @Override
125     public Iterator<Path> iterator() {
126         return iterator(this);
127     }
128 
129     /**
130      * Iterator implementation
131      */
132     private class UnixDirectoryIterator implements Iterator<Path> {
133         // true when at EOF
134         private boolean atEof;
135 
136         // next entry to return
137         private Path nextEntry;
138 
139         UnixDirectoryIterator() {
140             atEof = false;
141         }
142 
143         // Return true if file name is "." or ".."
144         private boolean isSelfOrParent(byte[] nameAsBytes) {
145             if (nameAsBytes[0] == '.') {
146                 if ((nameAsBytes.length == 1) ||
147                     (nameAsBytes.length == 2 && nameAsBytes[1] == '.')) {
148                     return true;
149                 }
150             }
151             return false;
152         }
153 
154         // Returns next entry (or null)
155         private Path readNextEntry() {
156             assert Thread.holdsLock(this);
157 
158             for (;;) {
159                 byte[] nameAsBytes = null;
160 
161                 // prevent close while reading
162                 readLock().lock();
163                 try {
164                     if (isOpen()) {
165                         nameAsBytes = readdir(dp);
166                     }
167                 } catch (UnixException x) {
168                     IOException ioe = x.asIOException(dir);
169                     throw new DirectoryIteratorException(ioe);
170                 } finally {
171                     readLock().unlock();
172                 }
173 
174                 // EOF
175                 if (nameAsBytes == null) {
176                     atEof = true;
177                     return null;
178                 }
179 
180                 // ignore "." and ".."
181                 if (!isSelfOrParent(nameAsBytes)) {
182                     Path entry = dir.resolve(nameAsBytes);
183 
184                     // return entry if no filter or filter accepts it
185                     try {
186                         if (filter == null || filter.accept(entry))
187                             return entry;
188                     } catch (IOException ioe) {
189                         throw new DirectoryIteratorException(ioe);
190                     }
191                 }
192             }
193         }
194 
195         @Override
196         public synchronized boolean hasNext() {
197             if (nextEntry == null && !atEof)
198                 nextEntry = readNextEntry();
199             return nextEntry != null;
200         }
201 
202         @Override
203         public synchronized Path next() {
204             Path result;
205             if (nextEntry == null && !atEof) {
206                 result = readNextEntry();
207             } else {
208                 result = nextEntry;
209                 nextEntry = null;
210             }
211             if (result == null)
212                 throw new NoSuchElementException();
213             return result;
214         }
215 
216         @Override
217         public void remove() {
218             throw new UnsupportedOperationException();
219         }
220     }
221 }