~ballot/wordpress/openstack-objectstorage-breaking-insight

« back to all changes in this revision

Viewing changes to src/OpenStack/ObjectStore/v1/Resource/StreamWrapper.php

  • Committer: Jacek Nykis
  • Date: 2015-02-11 15:35:31 UTC
  • Revision ID: jacek.nykis@canonical.com-20150211153531-hmy6zi0ov2qfkl0b
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/*
 
4
 * (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
 
5
 * (c) Copyright 2014      Rackspace US, Inc.
 
6
 *
 
7
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 
8
 * not use this file except in compliance with the License. You may obtain
 
9
 * a copy of the License at
 
10
 *
 
11
 *      http://www.apache.org/licenses/LICENSE-2.0
 
12
 *
 
13
 * Unless required by applicable law or agreed to in writing, software
 
14
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
15
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
16
 * License for the specific language governing permissions and limitations
 
17
 * under the License.
 
18
 */
 
19
 
 
20
namespace OpenStack\ObjectStore\v1\Resource;
 
21
 
 
22
use \OpenStack\Bootstrap;
 
23
use OpenStack\Common\Transport\Exception\ResourceNotFoundException;
 
24
use \OpenStack\ObjectStore\v1\ObjectStorage;
 
25
use OpenStack\Common\Exception;
 
26
 
 
27
/**
 
28
 * Provides stream wrapping for Swift.
 
29
 *
 
30
 * This provides a full stream wrapper to expose `swift://` URLs to the
 
31
 * PHP stream system.
 
32
 *
 
33
 * Swift streams provide authenticated and priviledged access to the
 
34
 * swift data store. These URLs are not generally used for granting
 
35
 * unauthenticated access to files (which can be done using the HTTP
 
36
 * stream wrapper -- no need for swift-specific logic).
 
37
 *
 
38
 * URL Structure
 
39
 *
 
40
 * This takes URLs of the following form:
 
41
 *
 
42
 * `swift://CONTAINER/FILE`
 
43
 *
 
44
 * Example:
 
45
 *
 
46
 * `swift://public/example.txt`
 
47
 *
 
48
 * The example above would access the `public` container and attempt to
 
49
 * retrieve the file named `example.txt`.
 
50
 *
 
51
 * Slashes are legal in Swift filenames, so a pathlike URL can be constructed
 
52
 * like this:
 
53
 *
 
54
 * `swift://public/path/like/file/name.txt`
 
55
 *
 
56
 * The above would attempt to find a file in object storage named
 
57
 * `path/like/file/name.txt`.
 
58
 *
 
59
 * A note on UTF-8 and URLs: PHP does not yet natively support many UTF-8
 
60
 * characters in URLs. Thus, you ought to rawurlencode() your container name
 
61
 * and object name (path) if there is any possibility that it will contain
 
62
 * UTF-8 characters.
 
63
 *
 
64
 * Locking
 
65
 *
 
66
 * This library does not support locking (e.g. flock()). This is because the
 
67
 * OpenStack Object Storage implementation does not support locking. But there
 
68
 * are a few related things you should keep in mind:
 
69
 *
 
70
 * - Working with a stream is essentially working with a COPY OF a remote file.
 
71
 *   Local locking is not an issue.
 
72
 * - If you open two streams for the same object, you will be working with
 
73
 *   TWO COPIES of the object. This can, of course, lead to nasty race
 
74
 *   conditions if each copy is modified.
 
75
 *
 
76
 * Usage
 
77
 *
 
78
 * The principle purpose of this wrapper is to make it easy to access and
 
79
 * manipulate objects on a remote object storage instance. Managing
 
80
 * containers is a secondary concern (and can often better be managed using
 
81
 * the OpenStack API). Consequently, almost all actions done through the
 
82
 * stream wrapper are focused on objects, not containers, servers, etc.
 
83
 *
 
84
 * Retrieving an Existing Object
 
85
 *
 
86
 * Retrieving an object is done by opening a file handle to that object.
 
87
 *
 
88
 * Writing an Object
 
89
 *
 
90
 * Nothing is written to the remote storage until the file is closed. This
 
91
 * keeps network traffic at a minimum, and respects the more-or-less stateless
 
92
 * nature of ObjectStorage.
 
93
 *
 
94
 * USING FILE/STREAM RESOURCES
 
95
 *
 
96
 * In general, you should access files like this:
 
97
 *
 
98
 *     <?php
 
99
 *     \OpenStack\Bootstrap::useStreamWrappers();
 
100
 *     // Set up the context.
 
101
 *     $context = stream_context_create(
 
102
 *       array('swift' => array(
 
103
 *         'username' => USERNAME,
 
104
 *         'password' => PASSWORD,
 
105
 *         'tenantid' => TENANT_ID,
 
106
 *         'tenantname' => TENANT_NAME, // Optional instead of tenantid.
 
107
 *         'endpoint' => AUTH_ENDPOINT_URL,
 
108
 *       )
 
109
 *      )
 
110
 *     );
 
111
 *     // Open the file.
 
112
 *     $handle = fopen('swift://mycontainer/myobject.txt', 'r+', false, $context);
 
113
 *
 
114
 *     // You can get the entire file, or use fread() to loop through the file.
 
115
 *     $contents = stream_get_contents($handle);
 
116
 *
 
117
 *     fclose($handle);
 
118
 *     ?>
 
119
 *
 
120
 * Remarks
 
121
 * - file_get_contents() works fine.
 
122
 * - You can write to a stream, too. Nothing is pushed to the server until
 
123
 *   fflush() or fclose() is called.
 
124
 * - Mode strings (w, r, w+, r+, c, c+, a, a+, x, x+) all work, though certain
 
125
 *   constraints are slightly relaxed to accomodate efficient local buffering.
 
126
 * - Files are buffered locally.
 
127
 *
 
128
 * USING FILE-LEVEL FUNCTIONS
 
129
 *
 
130
 * PHP provides a number of file-level functions that stream wrappers can
 
131
 * optionally support. Here are a few such functions:
 
132
 *
 
133
 * - file_exists()
 
134
 * - is_readable()
 
135
 * - stat()
 
136
 * - filesize()
 
137
 * - fileperms()
 
138
 *
 
139
 * The OpenStack stream wrapper provides support for these file-level functions.
 
140
 * But there are a few things you should know:
 
141
 *
 
142
 * - Each call to one of these functions generates at least one request. It may
 
143
 *   be as many as three:
 
144
 *   * An auth request
 
145
 *   * A request for the container (to get container permissions)
 
146
 *   * A request for the object
 
147
 * - IMPORTANT: Unlike the fopen()/fclose()... functions NONE of these functions
 
148
 *   retrieves the body of the file. If you are working with large files, using
 
149
 *   these functions may be orders of magnitude faster than using fopen(), etc.
 
150
 *   (The crucial detail: These kick off a HEAD request, will fopen() does a
 
151
 *   GET request).
 
152
 * - You must use Bootstrap::setConfiguration() to pass in all of the values you
 
153
 *   would normally pass into a stream context:
 
154
 *   * endpoint
 
155
 *   * username
 
156
 *   * password
 
157
 * - Most of the information from this family of calls can also be obtained using
 
158
 *   fstat(). If you were going to open a stream anyway, you might as well use
 
159
 *   fopen()/fstat().
 
160
 * - stat() and fstat() fake the permissions and ownership as follows:
 
161
 *   * uid/gid are always sset to the current user. This basically assumes that if
 
162
 *     the current user can access the object, the current user has ownership over
 
163
 *     the file. As the OpenStack ACL system developers, this may change.
 
164
 *   * Mode is faked. Swift uses ACLs, not UNIX mode strings. So we fake the string:
 
165
 *     - 770: The ACL has the object marked as private.
 
166
 *     - 775: The ACL has the object marked as public.
 
167
 *     - ACLs are actually set on the container, so every file in a public container
 
168
 *       will return 775.
 
169
 * - stat/fstat provide only one timestamp. Swift only tracks mtime, so mtime, atime,
 
170
 *   and ctime are all set to the last modified time.
 
171
 *
 
172
 * DIRECTORIES
 
173
 *
 
174
 * OpenStack Swift does not really have directories. Rather, it allows
 
175
 * characters such as '/' to be used to designate namespaces on object
 
176
 * names. (For simplicity, this library uses only '/' as a separator).
 
177
 *
 
178
 * This allows for simulated directory listings. Requesting
 
179
 * `scandir('swift://foo/bar/')` is really a request to "find all of the items
 
180
 * in the 'foo' container whose names start with 'bar/'".
 
181
 *
 
182
 * Because of this...
 
183
 *
 
184
 * - Directory reading functions like scandir(), opendir(), readdir()
 
185
 *   and so forth are supported.
 
186
 * - Functions to create or remove directories (mkdir() and rmdir()) are
 
187
 *   meaningless, and thus not supported.
 
188
 *
 
189
 * Swift still has support for "directory markers" (special zero-byte files
 
190
 * that act like directories). However, since there are no standards for how
 
191
 * said markers ought to be created, they are not supported by the stream
 
192
 * wrapper.
 
193
 *
 
194
 * As usual, the underlying \OpenStack\ObjectStore\v1\Resource\Container class
 
195
 * supports the full range of Swift features.
 
196
 *
 
197
 * SUPPORTED CONTEXT PARAMETERS
 
198
 *
 
199
 * This section details paramters that can be passed either
 
200
 * through a stream context or through
 
201
 * \OpenStack\Bootstrap\setConfiguration().
 
202
 *
 
203
 * PHP functions that do not allow you to pass a context may still be supported
 
204
 * here IF you have set options using Bootstrap::setConfiguration().
 
205
 *
 
206
 * You are required to pass in authentication information. This
 
207
 * comes in one of three forms:
 
208
 *
 
209
 * -# User login: username, password, tenantid, endpoint
 
210
 * -# Existing (valid) token: token, swift_endpoint
 
211
 *
 
212
 * As of 1.0.0-beta6, you may use `tenantname` instead of `tenantid`.
 
213
 *
 
214
 * The third method (token) can be used when the application has already
 
215
 * authenticated. In this case, a token has been generated and assigned
 
216
 * to an user and tenant.
 
217
 *
 
218
 * The following parameters may be set either in the stream context
 
219
 * or through OpenStack\Bootstrap::setConfiguration():
 
220
 *
 
221
 * - token: An auth token. If this is supplied, authentication is skipped and
 
222
 *     this token is used. NOTE: You MUST set swift_endpoint if using this
 
223
 *     option.
 
224
 * - swift_endpoint: The URL to the swift instance. This is only necessary if
 
225
 *     'token' is set. Otherwise it is ignored.
 
226
 * - username: A username. MUST be accompanied by 'password' and 'tenantid' (or 'tenantname').
 
227
 * - password: A password. MUST be accompanied by 'username' and 'tenantid' (or 'tenantname').
 
228
 * - endpoint: The URL to the authentication endpoint. Necessary if you are not
 
229
 *     using a 'token' and 'swift_endpoint'.
 
230
 * - content_type: This is effective only when writing files. It will
 
231
 *     set the Content-Type of the file during upload.
 
232
 * - tenantid: The tenant ID for the services you will use. (A user may
 
233
 *     have multiple tenancies associated.)
 
234
 * - tenantname: The tenant name for the services you will use. You may use
 
235
 *     this in lieu of tenant ID.
 
236
 *
 
237
 * @see http://us3.php.net/manual/en/class.streamwrapper.php
 
238
 *
 
239
 * @todo The service catalog should be cached in the context like the token so that
 
240
 * it can be retrieved later.
 
241
 */
 
242
class StreamWrapper
 
243
{
 
244
    const DEFAULT_SCHEME = 'swift';
 
245
 
 
246
    /**
 
247
     * Cache of auth token -> service catalog.
 
248
     *
 
249
     * This will eventually be replaced by a better system, but for a system of
 
250
     * moderate complexity, many, many file operations may be run during the
 
251
     * course of a request. Caching the catalog can prevent numerous calls
 
252
     * to identity services.
 
253
     */
 
254
    protected static $serviceCatalogCache = [];
 
255
 
 
256
    /**
 
257
     * The stream context.
 
258
     *
 
259
     * This is set automatically when the stream wrapper is created by
 
260
     * PHP. Note that it is not set through a constructor.
 
261
     */
 
262
    public $context;
 
263
    protected $contextArray = [];
 
264
 
 
265
    protected $schemeName = self::DEFAULT_SCHEME;
 
266
    protected $authToken;
 
267
 
 
268
    // File flags. These should probably be replaced by O_ const's at some point.
 
269
    protected $isBinary = false;
 
270
    protected $isText = true;
 
271
    protected $isWriting = false;
 
272
    protected $isReading = false;
 
273
    protected $isTruncating = false;
 
274
    protected $isAppending = false;
 
275
    protected $noOverwrite = false;
 
276
    protected $createIfNotFound = true;
 
277
 
 
278
    /**
 
279
     * If this is true, no data is ever sent to the remote server.
 
280
     */
 
281
    protected $isNeverDirty = false;
 
282
 
 
283
    protected $triggerErrors = false;
 
284
 
 
285
    /**
 
286
     * Indicate whether the local differs from remote.
 
287
     *
 
288
     * When the file is modified in such a way that
 
289
     * it needs to be written remotely, the isDirty flag
 
290
     * is set to true.
 
291
     */
 
292
    protected $isDirty = false;
 
293
 
 
294
    /**
 
295
     * Object storage instance.
 
296
     */
 
297
    protected $store;
 
298
 
 
299
    /**
 
300
     * The Container.
 
301
     */
 
302
    protected $container;
 
303
 
 
304
    /**
 
305
     * The Object.
 
306
     */
 
307
    protected $obj;
 
308
 
 
309
    /**
 
310
     * The IO stream for the Object.
 
311
     */
 
312
    protected $objStream;
 
313
 
 
314
    /**
 
315
     * Directory listing.
 
316
     *
 
317
     * Used for directory methods.
 
318
     */
 
319
    protected $dirListing = [];
 
320
    protected $dirIndex = 0;
 
321
    protected $dirPrefix = '';
 
322
 
 
323
    /**
 
324
     * Close a directory.
 
325
     *
 
326
     * This closes a directory handle, freeing up the resources.
 
327
     *
 
328
     *     <?php
 
329
     *
 
330
     *     // Assuming a valid context in $cxt...
 
331
     *
 
332
     *     // Get the container as if it were a directory.
 
333
     *     $dir = opendir('swift://mycontainer', $cxt);
 
334
     *
 
335
     *     // Do something with $dir
 
336
     *
 
337
     *     closedir($dir);
 
338
     *     ?>
 
339
     *
 
340
     * NB: Some versions of PHP 5.3 don't clear all buffers when
 
341
     * closing, and the handle can occasionally remain accessible for
 
342
     * some period of time.
 
343
     */
 
344
    public function dir_closedir()
 
345
    {
 
346
        $this->dirIndex = 0;
 
347
        $this->dirListing = [];
 
348
 
 
349
        //syslog(LOG_WARNING, "CLOSEDIR called.");
 
350
        return true;
 
351
    }
 
352
 
 
353
    /**
 
354
     * Open a directory for reading.
 
355
     *
 
356
     *     <?php
 
357
     *
 
358
     *     // Assuming a valid context in $cxt...
 
359
     *
 
360
     *     // Get the container as if it were a directory.
 
361
     *     $dir = opendir('swift://mycontainer', $cxt);
 
362
     *
 
363
     *     // Do something with $dir
 
364
     *
 
365
     *     closedir($dir);
 
366
     *     ?>
 
367
     *
 
368
     * See opendir() and scandir().
 
369
     *
 
370
     * @param string $path    The URL to open.
 
371
     * @param int    $options Unused.
 
372
     *
 
373
     * @return boolean true if the directory is opened, false otherwise.
 
374
     */
 
375
    public function dir_opendir($path, $options)
 
376
    {
 
377
        $url = $this->parseUrl($path);
 
378
 
 
379
        if (empty($url['host'])) {
 
380
            trigger_error('Container name is required.' , E_USER_WARNING);
 
381
 
 
382
            return false;
 
383
        }
 
384
 
 
385
        try {
 
386
            $this->initializeObjectStorage();
 
387
            $container = $this->store->container($url['host']);
 
388
 
 
389
            if (empty($url['path'])) {
 
390
                $this->dirPrefix = '';
 
391
            } else {
 
392
                $this->dirPrefix = $url['path'];
 
393
            }
 
394
 
 
395
            $sep = '/';
 
396
 
 
397
            $this->dirListing = $container->objectsWithPrefix($this->dirPrefix, $sep);
 
398
        } catch (\OpenStack\Common\Exception $e) {
 
399
            trigger_error('Directory could not be opened: ' . $e->getMessage(), E_USER_WARNING);
 
400
 
 
401
            return false;
 
402
        }
 
403
 
 
404
        return true;
 
405
    }
 
406
 
 
407
    /**
 
408
     * Read an entry from the directory.
 
409
     *
 
410
     * This gets a single line from the directory.
 
411
     *
 
412
     *     <?php
 
413
     *
 
414
     *     // Assuming a valid context in $cxt...
 
415
     *
 
416
     *     // Get the container as if it were a directory.
 
417
     *     $dir = opendir('swift://mycontainer', $cxt);
 
418
     *
 
419
     *     while (($entry = readdir($dir)) !== false) {
 
420
     *       print $entry . PHP_EOL;
 
421
     *     }
 
422
     *
 
423
     *     closedir($dir);
 
424
     *     ?>
 
425
     *
 
426
     * @return string The name of the resource or false when the directory has no
 
427
     *                more entries.
 
428
     */
 
429
    public function dir_readdir()
 
430
    {
 
431
        // If we are at the end of the listing, return false.
 
432
        if (count($this->dirListing) <= $this->dirIndex) {
 
433
            return false;
 
434
        }
 
435
 
 
436
        $curr = $this->dirListing[$this->dirIndex];
 
437
        $this->dirIndex++;
 
438
 
 
439
        if ($curr instanceof \OpenStack\ObjectStore\v1\Resource\Subdir) {
 
440
            $fullpath = $curr->path();
 
441
        } else {
 
442
             $fullpath = $curr->name();
 
443
        }
 
444
 
 
445
        if (!empty($this->dirPrefix)) {
 
446
            $len = strlen($this->dirPrefix);
 
447
            $fullpath = substr($fullpath, $len);
 
448
        }
 
449
 
 
450
        return $fullpath;
 
451
    }
 
452
 
 
453
    /**
 
454
     * Rewind to the beginning of the listing.
 
455
     *
 
456
     * This repositions the read pointer at the first entry in the directory.
 
457
     *
 
458
     *     <?php
 
459
     *
 
460
     *     // Assuming a valid context in $cxt...
 
461
     *
 
462
     *     // Get the container as if it were a directory.
 
463
     *     $dir = opendir('swift://mycontainer', $cxt);
 
464
     *
 
465
     *     while (($entry = readdir($dir)) !== false) {
 
466
     *       print $entry . PHP_EOL;
 
467
     *     }
 
468
     *
 
469
     *     rewinddir($dir);
 
470
     *
 
471
     *     $first = readdir($dir);
 
472
     *
 
473
     *     closedir($dir);
 
474
     *     ?>
 
475
     */
 
476
    public function dir_rewinddir()
 
477
    {
 
478
        $this->dirIndex = 0;
 
479
    }
 
480
 
 
481
    /*
 
482
    public function mkdir($path, $mode, $options)
 
483
    {
 
484
    }
 
485
 
 
486
    public function rmdir($path, $options)
 
487
    {
 
488
    }
 
489
     */
 
490
 
 
491
    /**
 
492
     * Rename a swift object.
 
493
     *
 
494
     * This works by copying the object (metadata) and
 
495
     * then removing the original version.
 
496
     *
 
497
     * This DOES support cross-container renaming.
 
498
     *
 
499
     * @see Container::copy().
 
500
     *
 
501
     *     <?php
 
502
     *     Bootstrap::setConfiguration(array(
 
503
     *       'tenantname' => 'foo@example.com',
 
504
     *       // 'tenantid' => '1234', // You can use this instead of tenantname
 
505
     *       'username' => 'foobar',
 
506
     *       'password' => 'baz',
 
507
     *       'endpoint' => 'https://auth.example.com',
 
508
     *     ));
 
509
     *
 
510
     *     $from = 'swift://containerOne/file.txt';
 
511
     *     $to = 'swift://containerTwo/file.txt';
 
512
     *
 
513
     *      // Rename can also take a context as a third param.
 
514
     *     rename($from, $to);
 
515
     *
 
516
     *     ?>
 
517
     *
 
518
     * @param string $path_from A swift URL that exists on the remote.
 
519
     * @param string $path_to   A swift URL to another path.
 
520
     *
 
521
     * @return boolean true on success, false otherwise.
 
522
     */
 
523
    public function rename($path_from, $path_to)
 
524
    {
 
525
        $this->initializeObjectStorage();
 
526
        $src = $this->parseUrl($path_from);
 
527
        $dest = $this->parseUrl($path_to);
 
528
 
 
529
        if ($src['scheme'] != $dest['scheme']) {
 
530
            trigger_error("I'm too stupid to copy across protocols.", E_USER_WARNING);
 
531
        }
 
532
 
 
533
        if ( empty($src['host'])  || empty($src['path'])
 
534
            || empty($dest['host']) || empty($dest['path'])) {
 
535
                trigger_error('Container and path are required for both source and destination URLs.', E_USER_WARNING);
 
536
 
 
537
                return false;
 
538
        }
 
539
 
 
540
        try {
 
541
            $container = $this->store->container($src['host']);
 
542
 
 
543
            $object = $container->proxyObject($src['path']);
 
544
 
 
545
            $ret = $container->copy($object, $dest['path'], $dest['host']);
 
546
            if ($ret) {
 
547
                return $container->delete($src['path']);
 
548
            }
 
549
        } catch (\OpenStack\Common\Exception $e) {
 
550
            trigger_error('Rename was not completed: ' . $e->getMessage(), E_USER_WARNING);
 
551
 
 
552
            return false;
 
553
        }
 
554
    }
 
555
 
 
556
    /*
 
557
    public function copy($path_from, $path_to)
 
558
    {
 
559
        throw new \Exception("UNDOCUMENTED.");
 
560
    }
 
561
     */
 
562
 
 
563
    /**
 
564
     * Cast stream into a lower-level stream.
 
565
     *
 
566
     * This is used for stream_select() and perhaps others.Because it exposes
 
567
     * the lower-level buffer objects, this function can have unexpected
 
568
     * side effects.
 
569
     *
 
570
     * @return resource this returns the underlying stream.
 
571
     */
 
572
    public function stream_cast($cast_as)
 
573
    {
 
574
        return $this->objStream;
 
575
    }
 
576
 
 
577
    /**
 
578
     * Close a stream, writing if necessary.
 
579
     *
 
580
     *     <?php
 
581
     *
 
582
     *     // Assuming $cxt has a valid context.
 
583
     *
 
584
     *     $file = fopen('swift://container/file.txt', 'r', false, $cxt);
 
585
     *
 
586
     *     fclose($file);
 
587
     *
 
588
     * ?>
 
589
     *
 
590
     * This will close the present stream. Importantly,
 
591
     * this will also write to the remote object storage if
 
592
     * any changes have been made locally.
 
593
     *
 
594
     * @see stream_open().
 
595
     */
 
596
    public function stream_close()
 
597
    {
 
598
        try {
 
599
            $this->writeRemote();
 
600
        } catch (\OpenStack\Common\Exception $e) {
 
601
            trigger_error('Error while closing: ' . $e->getMessage(), E_USER_NOTICE);
 
602
 
 
603
            return false;
 
604
        }
 
605
 
 
606
        // Force-clear the memory hogs.
 
607
        unset($this->obj);
 
608
 
 
609
        fclose($this->objStream);
 
610
    }
 
611
 
 
612
    /**
 
613
     * Check whether the stream has reached its end.
 
614
     *
 
615
     * This checks whether the stream has reached the
 
616
     * end of the object's contents.
 
617
     *
 
618
     * Called when `feof()` is called on a stream.
 
619
     *
 
620
     * @see stream_seek().
 
621
     *
 
622
     * @return boolean true if it has reached the end, false otherwise.
 
623
     */
 
624
    public function stream_eof()
 
625
    {
 
626
        return feof($this->objStream);
 
627
    }
 
628
 
 
629
    /**
 
630
     * Initiate saving data on the remote object storage.
 
631
     *
 
632
     * If the local copy of this object has been modified,
 
633
     * it is written remotely.
 
634
     *
 
635
     * Called when `fflush()` is called on a stream.
 
636
     */
 
637
    public function stream_flush()
 
638
    {
 
639
        try {
 
640
            $this->writeRemote();
 
641
        } catch (\OpenStack\Common\Exception $e) {
 
642
            syslog(LOG_WARNING, $e);
 
643
            trigger_error('Error while flushing: ' . $e->getMessage(), E_USER_NOTICE);
 
644
 
 
645
            return false;
 
646
        }
 
647
    }
 
648
 
 
649
    /**
 
650
     * Write data to the remote object storage.
 
651
     *
 
652
     * Internally, this is used by flush and close.
 
653
     */
 
654
    protected function writeRemote()
 
655
    {
 
656
        $contentType = $this->cxt('content_type');
 
657
        if (!empty($contentType)) {
 
658
            $this->obj->setContentType($contentType);
 
659
        }
 
660
 
 
661
        // Skip debug streams.
 
662
        if ($this->isNeverDirty) {
 
663
            return;
 
664
        }
 
665
 
 
666
        // Stream is dirty and needs a write.
 
667
        if ($this->isDirty) {
 
668
            $position = ftell($this->objStream);
 
669
 
 
670
            rewind($this->objStream);
 
671
            $this->container->save($this->obj, $this->objStream);
 
672
 
 
673
            fseek($this->objStream, SEEK_SET, $position);
 
674
 
 
675
        }
 
676
        $this->isDirty = false;
 
677
    }
 
678
 
 
679
    /*
 
680
     * Locking is currently unsupported.
 
681
     *
 
682
     * There is no remote support for locking a
 
683
     * file.
 
684
    public function stream_lock($operation)
 
685
    {
 
686
    }
 
687
     */
 
688
 
 
689
    /**
 
690
     * Open a stream resource.
 
691
     *
 
692
     * This opens a given stream resource and prepares it for reading or writing.
 
693
     *
 
694
     *     <?php
 
695
     *     $cxt = stream_context_create(array(
 
696
     *       'username' => 'foobar',
 
697
     *       'tenantid' => '987654321',
 
698
     *       'password' => 'eieio',
 
699
     *       'endpoint' => 'https://auth.example.com',
 
700
     *     ));
 
701
     *     ?>
 
702
     *
 
703
     *     $file = fopen('swift://myContainer/myObject.csv', 'rb', false, $cxt);
 
704
     *     while ($bytes = fread($file, 8192)) {
 
705
     *       print $bytes;
 
706
     *     }
 
707
     *     fclose($file);
 
708
     *     ?>
 
709
     *
 
710
     * If a file is opened in write mode, its contents will be retrieved from the
 
711
     * remote storage and cached locally for manipulation. If the file is opened
 
712
     * in a write-only mode, the contents will be created locally and then pushed
 
713
     * remotely as necessary.
 
714
     *
 
715
     * During this operation, the remote host may need to be contacted for
 
716
     * authentication as well as for file retrieval.
 
717
     *
 
718
     * @param string $path        The URL to the resource. See the class description for
 
719
     *                            details, but typically this expects URLs in the form `swift://CONTAINER/OBJECT`.
 
720
     * @param string $mode        Any of the documented mode strings. See fopen(). For
 
721
     *                            any file that is in a writing mode, the file will be saved remotely on
 
722
     *                            flush or close. Note that there is an extra mode: 'nope'. It acts like
 
723
     *                            'c+' except that it is never written remotely. This is useful for
 
724
     *                            debugging the stream locally without sending that data to object storage.
 
725
     *                            (Note that data is still fetched -- just never written.)
 
726
     * @param int    $options     An OR'd list of options. Only STREAM_REPORT_ERRORS has
 
727
     *                            any meaning to this wrapper, as it is not working with local files.
 
728
     * @param string $opened_path This is not used, as this wrapper deals only
 
729
     *                            with remote objects.
 
730
     */
 
731
    public function stream_open($path, $mode, $options, &$opened_path)
 
732
    {
 
733
        //syslog(LOG_WARNING, "I received this URL: " . $path);
 
734
 
 
735
        // If STREAM_REPORT_ERRORS is set, we are responsible for
 
736
        // all error handling while opening the stream.
 
737
        if (STREAM_REPORT_ERRORS & $options) {
 
738
            $this->triggerErrors = true;
 
739
        }
 
740
 
 
741
        // Using the mode string, set the internal mode.
 
742
        $this->setMode($mode);
 
743
 
 
744
        // Parse the URL.
 
745
        $url = $this->parseUrl($path);
 
746
        //syslog(LOG_WARNING, print_r($url, true));
 
747
 
 
748
        // Container name is required.
 
749
        if (empty($url['host'])) {
 
750
            //if ($this->triggerErrors) {
 
751
                trigger_error('No container name was supplied in ' . $path, E_USER_WARNING);
 
752
            //}
 
753
            return false;
 
754
        }
 
755
 
 
756
        // A path to an object is required.
 
757
        if (empty($url['path'])) {
 
758
            //if ($this->triggerErrors) {
 
759
                trigger_error('No object name was supplied in ' . $path, E_USER_WARNING);
 
760
            //}
 
761
            return false;
 
762
        }
 
763
 
 
764
        // We set this because it is possible to bind another scheme name,
 
765
        // and we need to know that name if it's changed.
 
766
        //$this->schemeName = isset($url['scheme']) ? $url['scheme'] : self::DEFAULT_SCHEME;
 
767
        if (isset($url['scheme'])) {
 
768
            $this->schemeName == $url['scheme'];
 
769
        }
 
770
 
 
771
        // Now we find out the container name. We walk a fine line here, because we don't
 
772
        // create a new container, but we don't want to incur heavy network
 
773
        // traffic, either. So we have to assume that we have a valid container
 
774
        // until we issue our first request.
 
775
        $containerName = $url['host'];
 
776
 
 
777
        // Object name.
 
778
        $objectName = $url['path'];
 
779
 
 
780
        // XXX: We reserve the query string for passing additional params.
 
781
 
 
782
        try {
 
783
            $this->initializeObjectStorage();
 
784
        } catch (\OpenStack\Common\Exception $e) {
 
785
            trigger_error('Failed to init object storage: ' . $e->getMessage(), E_USER_WARNING);
 
786
 
 
787
            return false;
 
788
        }
 
789
 
 
790
        //syslog(LOG_WARNING, "Container: " . $containerName);
 
791
 
 
792
        // Now we need to get the container. Doing a server round-trip here gives
 
793
        // us the peace of mind that we have an actual container.
 
794
        // XXX: Should we make it possible to get a container blindly, without the
 
795
        // server roundtrip?
 
796
        try {
 
797
            $this->container = $this->store->container($containerName);
 
798
        } catch (ResourceNotFoundException $e) {
 
799
            trigger_error('Container not found.', E_USER_WARNING);
 
800
            return false;
 
801
        }
 
802
 
 
803
        try {
 
804
            // Now we fetch the file. Only under certain circumstances do we generate
 
805
            // an error if the file is not found.
 
806
            // FIXME: We should probably allow a context param that can be set to
 
807
            // mark the file as lazily fetched.
 
808
            $this->obj = $this->container->object($objectName);
 
809
            $stream = $this->obj->stream();
 
810
            $streamMeta = stream_get_meta_data($stream);
 
811
 
 
812
            // Support 'x' and 'x+' modes.
 
813
            if ($this->noOverwrite) {
 
814
                //if ($this->triggerErrors) {
 
815
                    trigger_error('File exists and cannot be overwritten.', E_USER_WARNING);
 
816
                //}
 
817
                return false;
 
818
            }
 
819
 
 
820
            // If we need to write to it, we need a writable
 
821
            // stream. Also, if we need to block reading, this
 
822
            // will require creating an alternate stream.
 
823
            if ($this->isWriting && ($streamMeta['mode'] == 'r' || !$this->isReading)) {
 
824
                $newMode = $this->isReading ? 'rb+' : 'wb';
 
825
                $tmpStream = fopen('php://temp', $newMode);
 
826
                stream_copy_to_stream($stream, $tmpStream);
 
827
 
 
828
                // Skip rewinding if we can.
 
829
                if (!$this->isAppending) {
 
830
                    rewind($tmpStream);
 
831
                }
 
832
 
 
833
                $this->objStream = $tmpStream;
 
834
            } else {
 
835
                $this->objStream = $this->obj->stream();
 
836
            }
 
837
 
 
838
            // Append mode requires seeking to the end.
 
839
            if ($this->isAppending) {
 
840
                fseek($this->objStream, -1, SEEK_END);
 
841
            }
 
842
        } catch (ResourceNotFoundException $nf) {
 
843
            // If a 404 is thrown, we need to determine whether
 
844
            // or not a new file should be created.
 
845
 
 
846
            // For many modes, we just go ahead and create.
 
847
            if ($this->createIfNotFound) {
 
848
                $this->obj = new Object($objectName);
 
849
                $this->objStream = fopen('php://temp', 'rb+');
 
850
 
 
851
                $this->isDirty = true;
 
852
            } else {
 
853
                //if ($this->triggerErrors) {
 
854
                    trigger_error($nf->getMessage(), E_USER_WARNING);
 
855
                //}
 
856
                return false;
 
857
            }
 
858
 
 
859
        } catch (Exception $e) {
 
860
            // All other exceptions are fatal.
 
861
            //if ($this->triggerErrors) {
 
862
                trigger_error('Failed to fetch object: ' . $e->getMessage(), E_USER_WARNING);
 
863
            //}
 
864
            return false;
 
865
        }
 
866
 
 
867
        // At this point, we have a file that may be read-only. It also may be
 
868
        // reading off of a socket. It will be positioned at the beginning of
 
869
        // the stream.
 
870
        return true;
 
871
    }
 
872
 
 
873
    /**
 
874
     * Read N bytes from the stream.
 
875
     *
 
876
     * This will read up to the requested number of bytes. Or, upon
 
877
     * hitting the end of the file, it will return null.
 
878
     *
 
879
     * @see fread(), fgets(), and so on for examples.
 
880
     *
 
881
     *     <?php
 
882
     *     $cxt = stream_context_create(array(
 
883
     *       'tenantname' => 'me@example.com',
 
884
     *       'username' => 'me@example.com',
 
885
     *       'password' => 'secret',
 
886
     *       'endpoint' => 'https://auth.example.com',
 
887
     *     ));
 
888
     *
 
889
     *     $content = file_get_contents('swift://public/myfile.txt', false, $cxt);
 
890
     *     ?>
 
891
     *
 
892
     * @param int $count The number of bytes to read (usually 8192).
 
893
     *
 
894
     * @return string The data read.
 
895
     */
 
896
    public function stream_read($count)
 
897
    {
 
898
        return fread($this->objStream, $count);
 
899
    }
 
900
 
 
901
    /**
 
902
     * Perform a seek.
 
903
     *
 
904
     * This is called whenever `fseek()` or `rewind()` is called on a
 
905
     * Swift stream.
 
906
     *
 
907
     * IMPORTANT: Unlike the PHP core, this library
 
908
     * allows you to `fseek()` inside of a file opened
 
909
     * in append mode ('a' or 'a+').
 
910
     */
 
911
    public function stream_seek($offset, $whence)
 
912
    {
 
913
        $ret = fseek($this->objStream, $offset, $whence);
 
914
 
 
915
        // fseek returns 0 for success, -1 for failure.
 
916
        // We need to return true for success, false for failure.
 
917
        return $ret === 0;
 
918
    }
 
919
 
 
920
    /**
 
921
     * Set options on the underlying stream.
 
922
     *
 
923
     * The settings here do not trickle down to the network socket, which is
 
924
     * left open for only a brief period of time. Instead, they impact the middle
 
925
     * buffer stream, where the file is read and written to between flush/close
 
926
     * operations. Thus, tuning these will not have any impact on network
 
927
     * performance.
 
928
     *
 
929
     * See stream_set_blocking(), stream_set_timeout(), and stream_write_buffer().
 
930
     */
 
931
    public function stream_set_option($option, $arg1, $arg2)
 
932
    {
 
933
        switch ($option) {
 
934
            case STREAM_OPTION_BLOCKING:
 
935
                return stream_set_blocking($this->objStream, $arg1);
 
936
            case STREAM_OPTION_READ_TIMEOUT:
 
937
                // XXX: Should this have any effect on the lower-level
 
938
                // socket, too? Or just the buffered tmp storage?
 
939
                return stream_set_timeout($this->objStream, $arg1, $arg2);
 
940
            case STREAM_OPTION_WRITE_BUFFER:
 
941
                return stream_set_write_buffer($this->objStream, $arg2);
 
942
        }
 
943
 
 
944
    }
 
945
 
 
946
    /**
 
947
     * Perform stat()/lstat() operations.
 
948
     *
 
949
     *     <?php
 
950
     *       $file = fopen('swift://foo/bar', 'r+', false, $cxt);
 
951
     *       $stats = fstat($file);
 
952
     *     ?>
 
953
     *
 
954
     * To use standard `stat()` on a Swift stream, you will
 
955
     * need to set account information (tenant ID, username, password,
 
956
     * etc.) through \OpenStack\Bootstrap::setConfiguration().
 
957
     *
 
958
     * @return array The stats array.
 
959
     */
 
960
    public function stream_stat()
 
961
    {
 
962
        $stat = fstat($this->objStream);
 
963
 
 
964
        // FIXME: Need to calculate the length of the $objStream.
 
965
        //$contentLength = $this->obj->contentLength();
 
966
        $contentLength = $stat['size'];
 
967
 
 
968
        return $this->generateStat($this->obj, $this->container, $contentLength);
 
969
    }
 
970
 
 
971
    /**
 
972
     * Get the current position in the stream.
 
973
     *
 
974
     * @see `ftell()` and `fseek()`.
 
975
     *
 
976
     * @return int The current position in the stream.
 
977
     */
 
978
    public function stream_tell()
 
979
    {
 
980
        return ftell($this->objStream);
 
981
    }
 
982
 
 
983
    /**
 
984
     * Write data to stream.
 
985
     *
 
986
     * This writes data to the local stream buffer. Data
 
987
     * is not pushed remotely until stream_close() or
 
988
     * stream_flush() is called.
 
989
     *
 
990
     * @param string $data Data to write to the stream.
 
991
     *
 
992
     * @return int The number of bytes written. 0 indicates and error.
 
993
     */
 
994
    public function stream_write($data)
 
995
    {
 
996
        $this->isDirty = true;
 
997
 
 
998
        return fwrite($this->objStream, $data);
 
999
    }
 
1000
 
 
1001
    /**
 
1002
     * Unlink a file.
 
1003
     *
 
1004
     * This removes the remote copy of the file. Like a normal unlink operation,
 
1005
     * it does not destroy the (local) file handle until the file is closed.
 
1006
     * Therefore you can continue accessing the object locally.
 
1007
     *
 
1008
     * Note that OpenStack Swift does not draw a distinction between file objects
 
1009
     * and "directory" objects (where the latter is a 0-byte object). This will
 
1010
     * delete either one. If you are using directory markers, not that deleting
 
1011
     * a marker will NOT delete the contents of the "directory".
 
1012
     *
 
1013
     * You will need to use \OpenStack\Bootstrap::setConfiguration() to set the
 
1014
     * necessary stream configuration, since `unlink()` does not take a context.
 
1015
     *
 
1016
     * @param string $path The URL.
 
1017
     *
 
1018
     * @return boolean true if the file was deleted, false otherwise.
 
1019
     */
 
1020
    public function unlink($path)
 
1021
    {
 
1022
        $url = $this->parseUrl($path);
 
1023
 
 
1024
        // Host is required.
 
1025
        if (empty($url['host'])) {
 
1026
            trigger_error('Container name is required.', E_USER_WARNING);
 
1027
 
 
1028
            return false;
 
1029
        }
 
1030
 
 
1031
        // I suppose we could allow deleting containers,
 
1032
        // but that isn't really the purpose of the
 
1033
        // stream wrapper.
 
1034
        if (empty($url['path'])) {
 
1035
            trigger_error('Path is required.', E_USER_WARNING);
 
1036
 
 
1037
            return false;
 
1038
        }
 
1039
 
 
1040
        try {
 
1041
            $this->initializeObjectStorage();
 
1042
            // $container = $this->store->container($url['host']);
 
1043
            $name = $url['host'];
 
1044
            $token = $this->store->token();
 
1045
            $endpoint_url = $this->store->url() . '/' . rawurlencode($name);
 
1046
            $client = $this->cxt('transport_client', null);
 
1047
            $container = new \OpenStack\ObjectStore\v1\Resource\Container($name, $endpoint_url, $token, $client);
 
1048
 
 
1049
            return $container->delete($url['path']);
 
1050
        } catch (\OpenStack\Common\Exception $e) {
 
1051
            trigger_error('Error during unlink: ' . $e->getMessage(), E_USER_WARNING);
 
1052
 
 
1053
            return false;
 
1054
        }
 
1055
 
 
1056
    }
 
1057
 
 
1058
    /**
 
1059
     * @see stream_stat().
 
1060
     */
 
1061
    public function url_stat($path, $flags)
 
1062
    {
 
1063
        $url = $this->parseUrl($path);
 
1064
 
 
1065
        if (empty($url['host']) || empty($url['path'])) {
 
1066
            if ($flags & STREAM_URL_STAT_QUIET) {
 
1067
                trigger_error('Container name (host) and path are required.', E_USER_WARNING);
 
1068
            }
 
1069
 
 
1070
            return false;
 
1071
        }
 
1072
 
 
1073
        try {
 
1074
            $this->initializeObjectStorage();
 
1075
 
 
1076
            // Since we are throwing the $container away without really using its
 
1077
            // internals, we create an unchecked container. It may not actually
 
1078
            // exist on the server, which will cause a 404 error.
 
1079
            //$container = $this->store->container($url['host']);
 
1080
            $name = $url['host'];
 
1081
            $token = $this->store->token();
 
1082
            $endpoint_url = $this->store->url() . '/' . rawurlencode($name);
 
1083
            $client = $this->cxt('transport_client', null);
 
1084
            $container = new \OpenStack\ObjectStore\v1\Resource\Container($name, $endpoint_url, $token, $client);
 
1085
            $obj = $container->proxyObject($url['path']);
 
1086
        } catch (\OpenStack\Common\Exception $e) {
 
1087
            // Apparently file_exists does not set STREAM_URL_STAT_QUIET.
 
1088
            //if ($flags & STREAM_URL_STAT_QUIET) {
 
1089
                //trigger_error('Could not stat remote file: ' . $e->getMessage(), E_USER_WARNING);
 
1090
            //}
 
1091
            return false;
 
1092
        }
 
1093
 
 
1094
        if ($flags & STREAM_URL_STAT_QUIET) {
 
1095
            try {
 
1096
                return @$this->generateStat($obj, $container, $obj->contentLength());
 
1097
            } catch (\OpenStack\Common\Exception $e) {
 
1098
                return false;
 
1099
            }
 
1100
        }
 
1101
 
 
1102
        return $this->generateStat($obj, $container, $obj->contentLength());
 
1103
    }
 
1104
 
 
1105
    /**
 
1106
     * Get the Object.
 
1107
     *
 
1108
     * This provides low-level access to the
 
1109
     * \OpenStack\ObjectStore\v1\ObjectStorage::Object instance in which the content
 
1110
     * is stored.
 
1111
     *
 
1112
     * Accessing the object's payload (Object::content()) is strongly
 
1113
     * discouraged, as it will modify the pointers in the stream that the
 
1114
     * stream wrapper is using.
 
1115
     *
 
1116
     * HOWEVER, accessing the Object's metadata properties, content type,
 
1117
     * and so on is okay. Changes to this data will be written on the next
 
1118
     * flush, provided that the file stream data has also been changed.
 
1119
     *
 
1120
     * To access this:
 
1121
     *
 
1122
     *     <?php
 
1123
     *      $handle = fopen('swift://container/test.txt', 'rb', $cxt);
 
1124
     *      $md = stream_get_meta_data($handle);
 
1125
     *      $obj = $md['wrapper_data']->object();
 
1126
     *     ?>
 
1127
     */
 
1128
    public function object()
 
1129
    {
 
1130
        return $this->obj;
 
1131
    }
 
1132
 
 
1133
    /**
 
1134
     * EXPERT: Get the ObjectStorage for this wrapper.
 
1135
     *
 
1136
     * @return object \OpenStack\ObjectStorage An ObjectStorage object.
 
1137
     *                @see object()
 
1138
     */
 
1139
    public function objectStorage()
 
1140
    {
 
1141
        return $this->store;
 
1142
    }
 
1143
 
 
1144
    /**
 
1145
     * EXPERT: Get the auth token for this wrapper.
 
1146
     *
 
1147
     * @return string A token.
 
1148
     *                @see object()
 
1149
     */
 
1150
    public function token()
 
1151
    {
 
1152
        return $this->store->token();
 
1153
    }
 
1154
 
 
1155
    /**
 
1156
     * EXPERT: Get the service catalog (IdentityService) for this wrapper.
 
1157
     *
 
1158
     * This is only available when a file is opened via fopen().
 
1159
     *
 
1160
     * @return array A service catalog.
 
1161
     *               @see object()
 
1162
     */
 
1163
    public function serviceCatalog()
 
1164
    {
 
1165
        return self::$serviceCatalogCache[$this->token()];
 
1166
    }
 
1167
 
 
1168
    /**
 
1169
     * Generate a reasonably accurate STAT array.
 
1170
     *
 
1171
     * Notes on mode:
 
1172
     * - All modes are of the (octal) form 100XXX, where
 
1173
     *   XXX is replaced by the permission string. Thus,
 
1174
     *   this always reports that the type is "file" (100).
 
1175
     * - Currently, only two permission sets are generated:
 
1176
     *   - 770: Represents the ACL::makePrivate() perm.
 
1177
     *   - 775: Represents the ACL::makePublic() perm.
 
1178
     *
 
1179
     * Notes on mtime/atime/ctime:
 
1180
     * - For whatever reason, Swift only stores one timestamp.
 
1181
     *   We use that for mtime, atime, and ctime.
 
1182
     *
 
1183
     * Notes on size:
 
1184
     * - Size must be calculated externally, as it will sometimes
 
1185
     *   be the remote's Content-Length, and it will sometimes be
 
1186
     *   the cached stat['size'] for the underlying buffer.
 
1187
     */
 
1188
    protected function generateStat($object, $container, $size)
 
1189
    {
 
1190
        // This is not entirely accurate. Basically, if the
 
1191
        // file is marked public, it gets 100775, and if
 
1192
        // it is private, it gets 100770.
 
1193
        //
 
1194
        // Mode is always set to file (100XXX) because there
 
1195
        // is no alternative that is more sensible. PHP docs
 
1196
        // do not recommend an alternative.
 
1197
        //
 
1198
        // octdec(100770) == 33272
 
1199
        // octdec(100775) == 33277
 
1200
        $mode = $container->acl()->isPublic() ? 33277 : 33272;
 
1201
 
 
1202
        // We have to fake the UID value in order for is_readible()/is_writable()
 
1203
        // to work. Note that on Windows systems, stat does not look for a UID.
 
1204
        if (function_exists('posix_geteuid')) {
 
1205
            $uid = posix_geteuid();
 
1206
            $gid = posix_getegid();
 
1207
        } else {
 
1208
            $uid = 0;
 
1209
            $gid = 0;
 
1210
        }
 
1211
 
 
1212
        if ($object instanceof \OpenStack\ObjectStore\v1\Resource\RemoteObject) {
 
1213
            $modTime = $object->lastModified();
 
1214
        } else {
 
1215
            $modTime = 0;
 
1216
        }
 
1217
        $values = [
 
1218
            'dev' => 0,
 
1219
            'ino' => 0,
 
1220
            'mode' => $mode,
 
1221
            'nlink' => 0,
 
1222
            'uid' => $uid,
 
1223
            'gid' => $gid,
 
1224
            'rdev' => 0,
 
1225
            'size' => $size,
 
1226
            'atime' => $modTime,
 
1227
            'mtime' => $modTime,
 
1228
            'ctime' => $modTime,
 
1229
            'blksize' => -1,
 
1230
            'blocks' => -1,
 
1231
        ];
 
1232
 
 
1233
        $final = array_values($values) + $values;
 
1234
 
 
1235
        return $final;
 
1236
 
 
1237
    }
 
1238
 
 
1239
    ///////////////////////////////////////////////////////////////////
 
1240
    // INTERNAL METHODS
 
1241
    // All methods beneath this line are not part of the Stream API.
 
1242
    ///////////////////////////////////////////////////////////////////
 
1243
 
 
1244
    /**
 
1245
     * Set the fopen mode.
 
1246
     *
 
1247
     * @param string $mode The mode string, e.g. `r+` or `wb`.
 
1248
     *
 
1249
     * @return \OpenStack\ObjectStore\v1\Resource\StreamWrapper $this so the method
 
1250
     *                                                        can be used in chaining.
 
1251
     */
 
1252
    protected function setMode($mode)
 
1253
    {
 
1254
        $mode = strtolower($mode);
 
1255
 
 
1256
        // These are largely ignored, as the remote
 
1257
        // object storage does not distinguish between
 
1258
        // text and binary files. Per the PHP recommendation
 
1259
        // files are treated as binary.
 
1260
        $this->isBinary = strpos($mode, 'b') !== false;
 
1261
        $this->isText = strpos($mode, 't') !== false;
 
1262
 
 
1263
        // Rewrite mode to remove b or t:
 
1264
        $mode = preg_replace('/[bt]?/', '', $mode);
 
1265
 
 
1266
        switch ($mode) {
 
1267
            case 'r+':
 
1268
                $this->isWriting = true;
 
1269
            case 'r':
 
1270
                $this->isReading = true;
 
1271
                $this->createIfNotFound = false;
 
1272
                break;
 
1273
 
 
1274
 
 
1275
            case 'w+':
 
1276
                $this->isReading = true;
 
1277
            case 'w':
 
1278
                $this->isTruncating = true;
 
1279
                $this->isWriting = true;
 
1280
                break;
 
1281
 
 
1282
 
 
1283
            case 'a+':
 
1284
                $this->isReading = true;
 
1285
            case 'a':
 
1286
                $this->isAppending = true;
 
1287
                $this->isWriting = true;
 
1288
                break;
 
1289
 
 
1290
 
 
1291
            case 'x+':
 
1292
                $this->isReading = true;
 
1293
            case 'x':
 
1294
                $this->isWriting = true;
 
1295
                $this->noOverwrite = true;
 
1296
                break;
 
1297
 
 
1298
            case 'c+':
 
1299
                $this->isReading = true;
 
1300
            case 'c':
 
1301
                $this->isWriting = true;
 
1302
                break;
 
1303
 
 
1304
            // nope mode: Mock read/write support,
 
1305
            // but never write to the remote server.
 
1306
            // (This is accomplished by never marking
 
1307
            // the stream as dirty.)
 
1308
            case 'nope':
 
1309
                $this->isReading = true;
 
1310
                $this->isWriting = true;
 
1311
                $this->isNeverDirty = true;
 
1312
                break;
 
1313
 
 
1314
            // Default case is read/write
 
1315
            // like c+.
 
1316
            default:
 
1317
                $this->isReading = true;
 
1318
                $this->isWriting = true;
 
1319
                break;
 
1320
 
 
1321
        }
 
1322
 
 
1323
        return $this;
 
1324
    }
 
1325
 
 
1326
    /**
 
1327
     * Get an item out of the context.
 
1328
     *
 
1329
     * @todo Should there be an option to NOT query the Bootstrap::conf()?
 
1330
     *
 
1331
     * @param string $name    The name to look up. First look it up in the context,
 
1332
     *                        then look it up in the Bootstrap config.
 
1333
     * @param mixed  $default The default value to return if no config param was
 
1334
     *                        found.
 
1335
     *
 
1336
     * @return mixed The discovered result, or $default if specified, or null if
 
1337
     *               no $default is specified.
 
1338
     */
 
1339
    protected function cxt($name, $default = null)
 
1340
    {
 
1341
        // Lazilly populate the context array.
 
1342
        if (is_resource($this->context) && empty($this->contextArray)) {
 
1343
            $cxt = stream_context_get_options($this->context);
 
1344
 
 
1345
            // If a custom scheme name has been set, use that.
 
1346
            if (!empty($cxt[$this->schemeName])) {
 
1347
                $this->contextArray = $cxt[$this->schemeName];
 
1348
            }
 
1349
            // We fall back to this just in case.
 
1350
            elseif (!empty($cxt[self::DEFAULT_SCHEME])) {
 
1351
                $this->contextArray = $cxt[self::DEFAULT_SCHEME];
 
1352
            }
 
1353
        }
 
1354
 
 
1355
        // Should this be array_key_exists()?
 
1356
        if (isset($this->contextArray[$name])) {
 
1357
            return $this->contextArray[$name];
 
1358
        }
 
1359
 
 
1360
        // Check to see if the value can be gotten from
 
1361
        // \OpenStack\Bootstrap.
 
1362
        $val = \OpenStack\Bootstrap::config($name, null);
 
1363
        if (isset($val)) {
 
1364
            return $val;
 
1365
        }
 
1366
 
 
1367
        return $default;
 
1368
    }
 
1369
 
 
1370
    /**
 
1371
     * Parse a URL.
 
1372
     *
 
1373
     * In order to provide full UTF-8 support, URLs must be
 
1374
     * rawurlencoded before they are passed into the stream wrapper.
 
1375
     *
 
1376
     * This parses the URL and urldecodes the container name and
 
1377
     * the object name.
 
1378
     *
 
1379
     * @param string $url A Swift URL.
 
1380
     *
 
1381
     * @return array An array as documented in parse_url().
 
1382
     */
 
1383
    protected function parseUrl($url)
 
1384
    {
 
1385
        $res = parse_url($url);
 
1386
 
 
1387
 
 
1388
        // These have to be decode because they will later
 
1389
        // be encoded.
 
1390
        foreach ($res as $key => $val) {
 
1391
            if ($key == 'host') {
 
1392
                $res[$key] = urldecode($val);
 
1393
            } elseif ($key == 'path') {
 
1394
                if (strpos($val, '/') === 0) {
 
1395
                    $val = substr($val, 1);
 
1396
                }
 
1397
                $res[$key] = urldecode($val);
 
1398
 
 
1399
            }
 
1400
        }
 
1401
 
 
1402
        return $res;
 
1403
    }
 
1404
 
 
1405
    /**
 
1406
     * Based on the context, initialize the ObjectStorage.
 
1407
     *
 
1408
     * The following parameters may be set either in the stream context
 
1409
     * or through \OpenStack\Bootstrap::setConfiguration():
 
1410
     *
 
1411
     * - token: An auth token. If this is supplied, authentication is skipped and
 
1412
     *     this token is used. NOTE: You MUST set swift_endpoint if using this
 
1413
     *     option.
 
1414
     * - swift_endpoint: The URL to the swift instance. This is only necessary if
 
1415
     *     'token' is set. Otherwise it is ignored.
 
1416
     * - username: A username. MUST be accompanied by 'password' and 'tenantname'.
 
1417
     * - password: A password. MUST be accompanied by 'username' and 'tenantname'.
 
1418
     * - endpoint: The URL to the authentication endpoint. Necessary if you are not
 
1419
     *     using a 'token' and 'swift_endpoint'.
 
1420
     * - transport_client: A transport client for the HTTP requests.
 
1421
     *
 
1422
     * To find these params, the method first checks the supplied context. If the
 
1423
     * key is not found there, it checks the Bootstrap::conf().
 
1424
     */
 
1425
    protected function initializeObjectStorage()
 
1426
    {
 
1427
        $token = $this->cxt('token');
 
1428
 
 
1429
        $tenantId = $this->cxt('tenantid');
 
1430
        $tenantName = $this->cxt('tenantname');
 
1431
        $authUrl = $this->cxt('endpoint');
 
1432
        $endpoint = $this->cxt('swift_endpoint');
 
1433
        $client = $this->cxt('transport_client', null);
 
1434
 
 
1435
        $serviceCatalog = null;
 
1436
 
 
1437
        if (!empty($token) && isset(self::$serviceCatalogCache[$token])) {
 
1438
            $serviceCatalog = self::$serviceCatalogCache[$token];
 
1439
        }
 
1440
 
 
1441
        // FIXME: If a token is invalidated, we should try to re-authenticate.
 
1442
        // If context has the info we need, start from there.
 
1443
        if (!empty($token) && !empty($endpoint)) {
 
1444
            $this->store = new \OpenStack\ObjectStore\v1\ObjectStorage($token, $endpoint, $client);
 
1445
        }
 
1446
        // If we get here and tenant ID is not set, we can't get a container.
 
1447
        elseif (empty($tenantId) && empty($tenantName)) {
 
1448
            throw new \OpenStack\Common\Exception('Either Tenant ID (tenantid) or Tenant Name (tenantname) is required.');
 
1449
        } elseif (empty($authUrl)) {
 
1450
            throw new \OpenStack\Common\Exception('An Identity Service Endpoint (endpoint) is required.');
 
1451
        }
 
1452
        // Try to authenticate and get a new token.
 
1453
        else {
 
1454
            $ident = $this->authenticate();
 
1455
 
 
1456
            // Update token and service catalog. The old pair may be out of date.
 
1457
            $token = $ident->token();
 
1458
            $serviceCatalog = $ident->serviceCatalog();
 
1459
            self::$serviceCatalogCache[$token] = $serviceCatalog;
 
1460
 
 
1461
            $region = $this->cxt('openstack.swift.region');
 
1462
 
 
1463
            $this->store = ObjectStorage::newFromServiceCatalog($serviceCatalog, $token, $region, $client);
 
1464
        }
 
1465
 
 
1466
        return !empty($this->store);
 
1467
    }
 
1468
 
 
1469
    protected function authenticate()
 
1470
    {
 
1471
        $username = $this->cxt('username');
 
1472
        $password = $this->cxt('password');
 
1473
 
 
1474
        $tenantId = $this->cxt('tenantid');
 
1475
        $tenantName = $this->cxt('tenantname');
 
1476
        $authUrl = $this->cxt('endpoint');
 
1477
 
 
1478
        $client = $this->cxt('transport_client', null);
 
1479
 
 
1480
        $ident = new \OpenStack\Identity\v2\IdentityService($authUrl, $client);
 
1481
 
 
1482
        // Frustrated? Go burninate. http://www.homestarrunner.com/trogdor.html
 
1483
 
 
1484
        if (!empty($username) && !empty($password)) {
 
1485
            $token = $ident->authenticateAsUser($username, $password, $tenantId, $tenantName);
 
1486
        } else {
 
1487
            throw new \OpenStack\Common\Exception('Username/password must be provided.');
 
1488
        }
 
1489
        // Cache the service catalog.
 
1490
        self::$serviceCatalogCache[$token] = $ident->serviceCatalog();
 
1491
 
 
1492
        return $ident;
 
1493
    }
 
1494
 
 
1495
}